Skip to content

Commit d3ce114

Browse files
committed
Change operation IDs to hashed values
1 parent 482ff31 commit d3ce114

6 files changed

Lines changed: 48 additions & 19 deletions

File tree

sdk-integration-tests/src/test/java/software/amazon/lambda/durable/IntegrationTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,12 @@ void testFullWaitOperation() {
110110

111111
assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus());
112112
assertEquals(3, result.getSucceededOperations().size());
113-
assertEquals("Step 1 done", result.getSucceededOperations().get(0).getStepResult(String.class));
113+
assertEquals("Step 1 done", result.getOperation("step1").getStepResult(String.class));
114114
assertEquals(OperationType.WAIT, result.getSucceededOperations().get(1).getType());
115115
assertEquals(
116116
OperationStatus.SUCCEEDED,
117117
result.getSucceededOperations().get(1).getStatus());
118-
assertEquals("Step 2 done", result.getSucceededOperations().get(2).getStepResult(String.class));
118+
assertEquals("Step 2 done", result.getOperation("step2").getStepResult(String.class));
119119
assertEquals("Step 1 done + Step 2 done", result.getResult(TestOutput.class).result);
120120
}
121121

sdk/src/main/java/software/amazon/lambda/durable/DurableContext.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
package software.amazon.lambda.durable;
44

55
import com.amazonaws.services.lambda.runtime.Context;
6+
import java.nio.charset.StandardCharsets;
7+
import java.security.MessageDigest;
8+
import java.security.NoSuchAlgorithmException;
69
import java.time.Duration;
10+
import java.util.HexFormat;
711
import java.util.Objects;
812
import java.util.concurrent.atomic.AtomicInteger;
913
import java.util.function.Function;
@@ -327,12 +331,20 @@ public void close() {
327331
}
328332

329333
/**
330-
* Get the next operationId. For root contexts, returns sequential IDs like "1", "2", "3". For child contexts,
331-
* prefixes with the contextId to ensure global uniqueness, e.g. "1-1", "1-2" for operations inside child context
332-
* "1". This matches the JavaScript SDK's stepPrefix convention and prevents ID collisions in checkpoint batches.
334+
* Get the next operationId. Returns a globally unique operation ID by hashing a sequential operation counter. For
335+
* root contexts, the counter value is hashed directly (e.g. "1", "2", "3"). For child contexts, the values are
336+
* prefixed with the parent hashed contextId (e.g. "<hash>-1", "<hash>-2" inside parent context <hash>). This
337+
* matches the JavaScript SDK's stepPrefix convention and prevents ID collisions in checkpoint batches.
333338
*/
334339
private String nextOperationId() {
335340
var counter = String.valueOf(operationCounter.incrementAndGet());
336-
return getContextId() != null ? getContextId() + "-" + counter : counter;
341+
var rawId = getContextId() != null ? getContextId() + "-" + counter : counter;
342+
try {
343+
var messageDigest = MessageDigest.getInstance("SHA-256");
344+
var hash = messageDigest.digest(rawId.getBytes(StandardCharsets.UTF_8));
345+
return HexFormat.of().formatHex(hash);
346+
} catch (NoSuchAlgorithmException e) {
347+
throw new RuntimeException("failed to get next operation id, SHA-256 not available", e);
348+
}
337349
}
338350
}

sdk/src/test/java/software/amazon/lambda/durable/DurableContextTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ class DurableContextTest {
2424
.type(OperationType.EXECUTION)
2525
.status(OperationStatus.STARTED)
2626
.build();
27-
private static final String OPERATION_ID1 = "1";
28-
private static final String OPERATION_ID2 = "2";
29-
private static final String OPERATION_ID3 = "3";
27+
private static final String OPERATION_ID1 = TestUtils.hashOperationId("1");
28+
private static final String OPERATION_ID2 = TestUtils.hashOperationId("2");
29+
private static final String OPERATION_ID3 = TestUtils.hashOperationId("3");
3030

3131
private DurableContext createTestContext() {
3232
return createTestContext(List.of());

sdk/src/test/java/software/amazon/lambda/durable/DurableExecutionTest.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323

2424
class DurableExecutionTest {
2525

26+
private static final String OPERATION_ID0 = TestUtils.hashOperationId("0");
27+
private static final String OPERATION_ID1 = TestUtils.hashOperationId("1");
28+
2629
private DurableConfig configWithMockClient() {
2730
return DurableConfig.builder()
2831
.withDurableExecutionClient(TestUtils.createMockClient())
@@ -32,7 +35,7 @@ private DurableConfig configWithMockClient() {
3235
@Test
3336
void testExecuteSuccess() {
3437
var executionOp = Operation.builder()
35-
.id("0")
38+
.id(OPERATION_ID0)
3639
.type(OperationType.EXECUTION)
3740
.status(OperationStatus.STARTED)
3841
.executionDetails(ExecutionDetails.builder()
@@ -62,7 +65,7 @@ void testExecuteSuccess() {
6265
@Test
6366
void testExecutePending() {
6467
var executionOp = Operation.builder()
65-
.id("0")
68+
.id(OPERATION_ID0)
6669
.type(OperationType.EXECUTION)
6770
.status(OperationStatus.STARTED)
6871
.executionDetails(ExecutionDetails.builder()
@@ -95,7 +98,7 @@ void testExecutePending() {
9598
@Test
9699
void testExecuteFailure() {
97100
var executionOp = Operation.builder()
98-
.id("0")
101+
.id(OPERATION_ID0)
99102
.type(OperationType.EXECUTION)
100103
.status(OperationStatus.STARTED)
101104
.executionDetails(ExecutionDetails.builder()
@@ -128,7 +131,7 @@ void testExecuteFailure() {
128131
@Test
129132
void testExecuteReplay() {
130133
var executionOp = Operation.builder()
131-
.id("0")
134+
.id(OPERATION_ID0)
132135
.type(OperationType.EXECUTION)
133136
.status(OperationStatus.STARTED)
134137
.executionDetails(ExecutionDetails.builder()
@@ -137,7 +140,7 @@ void testExecuteReplay() {
137140
.build();
138141

139142
var completedStep = Operation.builder()
140-
.id("1")
143+
.id(OPERATION_ID1)
141144
.name("step1")
142145
.type(OperationType.STEP)
143146
.status(OperationStatus.SUCCEEDED)
@@ -180,7 +183,7 @@ void testValidationNoOperations() {
180183
@Test
181184
void testValidationWrongFirstOperation() {
182185
var stepOp = Operation.builder()
183-
.id("1")
186+
.id(OPERATION_ID1)
184187
.type(OperationType.STEP)
185188
.status(OperationStatus.SUCCEEDED)
186189
.stepDetails(StepDetails.builder().result("\"result\"").build())
@@ -204,7 +207,7 @@ void testValidationWrongFirstOperation() {
204207
@Test
205208
void testValidationMissingExecutionDetails() {
206209
var executionOp = Operation.builder()
207-
.id("0")
210+
.id(OPERATION_ID0)
208211
.type(OperationType.EXECUTION)
209212
.status(OperationStatus.STARTED)
210213
.build();
@@ -234,7 +237,7 @@ void testExecutorNotShutdownAfterMultipleHandlerInvocations() {
234237
assertFalse(sharedExecutor.isShutdown(), "Executor should not be shutdown initially");
235238

236239
var executionOp = Operation.builder()
237-
.id("0")
240+
.id(OPERATION_ID0)
238241
.type(OperationType.EXECUTION)
239242
.status(OperationStatus.STARTED)
240243
.executionDetails(ExecutionDetails.builder()
@@ -262,7 +265,7 @@ void testExecutorNotShutdownAfterMultipleHandlerInvocations() {
262265

263266
// Create second input with different execution operation
264267
var executionOp2 = Operation.builder()
265-
.id("0")
268+
.id(OPERATION_ID0)
266269
.type(OperationType.EXECUTION)
267270
.status(OperationStatus.STARTED)
268271
.executionDetails(ExecutionDetails.builder()

sdk/src/test/java/software/amazon/lambda/durable/ReplayValidationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
class ReplayValidationTest {
2525
public static final String EXECUTION_NAME = "exec-name";
2626
public static final String INVOCATION_ID = "invocation-id";
27-
public static final String OPERATION_ID1 = "1";
27+
public static final String OPERATION_ID1 = TestUtils.hashOperationId("1");
2828

2929
private DurableContext createTestContext(List<Operation> initialOperations) {
3030
var client = TestUtils.createMockClient();

sdk/src/test/java/software/amazon/lambda/durable/TestUtils.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
import static org.mockito.ArgumentMatchers.*;
66
import static org.mockito.Mockito.*;
77

8+
import java.nio.charset.StandardCharsets;
9+
import java.security.MessageDigest;
10+
import java.security.NoSuchAlgorithmException;
811
import java.util.ArrayList;
12+
import java.util.HexFormat;
913
import java.util.List;
1014
import java.util.UUID;
1115
import software.amazon.awssdk.services.lambda.model.*;
@@ -61,4 +65,14 @@ public static DurableExecutionClient createMockClient() {
6165
});
6266
return client;
6367
}
68+
69+
public static String hashOperationId(String rawId) {
70+
try {
71+
var messageDigest = MessageDigest.getInstance("SHA-256");
72+
var hash = messageDigest.digest(rawId.getBytes(StandardCharsets.UTF_8));
73+
return HexFormat.of().formatHex(hash);
74+
} catch (NoSuchAlgorithmException e) {
75+
throw new AssertionError("SHA-256 not available", e);
76+
}
77+
}
6478
}

0 commit comments

Comments
 (0)