Skip to content

Commit dd22d48

Browse files
Avoid loading the last vm error when the state machine has been closed anyway.
1 parent 152b712 commit dd22d48

2 files changed

Lines changed: 32 additions & 42 deletions

File tree

sdk-core/src/main/java/dev/restate/sdk/core/HandlerContextImpl.java

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ public void pollAsyncResult(AsyncResultInternal<?> asyncResult) {
417417
try {
418418
this.pumpOutput();
419419
this.pollAsyncResultInner(asyncResult);
420-
} catch (Exception e) {
420+
} catch (Throwable e) {
421421
this.failWithoutContextSwitch(e);
422422
}
423423
}
@@ -428,10 +428,7 @@ private void pollAsyncResultInner(AsyncResultInternal<?> asyncResult) {
428428
try {
429429
asyncResult.tryComplete(this::takeNotification);
430430
} catch (Throwable e) {
431-
// This can happen if the state machine was closed in the meantime.
432-
if (!ExceptionUtils.containsAbortedExecutionException(e)) {
433-
this.failWithoutContextSwitch(e);
434-
}
431+
this.failWithoutContextSwitch(e);
435432
asyncResult.publicFuture().completeExceptionally(AbortedExecutionException.INSTANCE);
436433
return;
437434
}
@@ -447,11 +444,7 @@ private void pollAsyncResultInner(AsyncResultInternal<?> asyncResult) {
447444
try {
448445
response = this.stateMachine.doAwait(asyncResult);
449446
} catch (Throwable e) {
450-
// doAwait sneaky-throws AbortedExecutionException on suspension. In this case, no need to
451-
// fail twice.
452-
if (!ExceptionUtils.containsAbortedExecutionException(e)) {
453-
this.failWithoutContextSwitch(e);
454-
}
447+
this.failWithoutContextSwitch(e);
455448
asyncResult.publicFuture().completeExceptionally(AbortedExecutionException.INSTANCE);
456449
return;
457450
}
@@ -480,7 +473,7 @@ Optional<StateMachine.NotificationValue> takeNotification(int handle) {
480473
public void proposeRunSuccess(int runHandle, Slice toWrite) {
481474
try {
482475
this.stateMachine.proposeRunCompletion(runHandle, toWrite);
483-
} catch (Exception e) {
476+
} catch (Throwable e) {
484477
this.failWithoutContextSwitch(e);
485478
}
486479
this.pumpOutput();
@@ -499,7 +492,7 @@ public void proposeRunFailure(
499492
} else {
500493
this.stateMachine.proposeRunCompletion(runHandle, throwable, attemptDuration, retryPolicy);
501494
}
502-
} catch (Exception e) {
495+
} catch (Throwable e) {
503496
this.failWithoutContextSwitch(e);
504497
}
505498
this.pumpOutput();

sdk-core/src/main/java23/dev/restate/sdk/core/statemachine/ffm/FfmStateMachine.java

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@
4040
* <p>Each call is a direct FFM downcall. Results come back either register-packed into the scalar
4141
* return value (the invocation state, plus a handle for handle-returning calls) or, when too wide
4242
* to pack, written into a caller-provided out-param struct whose first field is the state. Owned
43-
* {@link Slice}s are copied out and freed by the caller. On error the state field carries the
44-
* {@link SharedCoreNative#ERROR_STATE()} sentinel; the detail is then fetched via {@code
45-
* vm_take_last_vm_error} and thrown as a {@link ProtocolException}.
43+
* {@link Slice}s are copied out and freed by the caller. On the {@link
44+
* SharedCoreNative#ERROR_STATE()} sentinel the core has already written its terminal message and
45+
* closed, so we sneaky-throw {@link AbortedExecutionException} ({@link #abortAfterCoreClosed()});
46+
* construction and {@code isReadyToExecute} errors surface as a {@link ProtocolException}.
4647
*
4748
* <p>An instance is driven by a single thread at a time (no reentrancy, per the contract); only
4849
* {@link #state()} — a volatile read — is safe from any thread. Each downcall allocates its
@@ -186,12 +187,9 @@ public AwaitResult doAwait(UnresolvedFuture future) {
186187
packed = SharedCoreNative.vm_do_await(vmHandle, futureSeg);
187188
}
188189

189-
// Register-packed result (a bare u64, no out-param struct): the low byte is the variant tag,
190-
// the
191-
// high 32 bits carry the ExecuteRun run handle. On error the core stashed the detail and we
192-
// fetch
193-
// it via vm_take_last_vm_error. Suspension isn't a variant here — the driver surfaces it by
194-
// sneaky-throwing AbortedExecutionException per the StateMachine contract.
190+
// Register-packed u64: low byte = variant tag, high 32 bits = ExecuteRun handle. The ERROR
191+
// variant also covers suspension (the core folds it in); either way the core already wrote its
192+
// terminal message and closed, so we abort cleanly rather than fetch the discarded detail.
195193
int variant = (int) packed;
196194
if (variant == SharedCoreNative.AWAIT_VARIANT_ANY_COMPLETED()) {
197195
return AwaitResult.ANY_COMPLETED;
@@ -202,22 +200,11 @@ public AwaitResult doAwait(UnresolvedFuture future) {
202200
} else if (variant == SharedCoreNative.AWAIT_VARIANT_CANCEL_SIGNAL_RECEIVED()) {
203201
return AwaitResult.CANCEL_SIGNAL_RECEIVED;
204202
} else if (variant == SharedCoreNative.AWAIT_VARIANT_ERROR()) {
205-
throw takeLastVmError();
203+
abortAfterCoreClosed();
206204
}
207205
throw new IllegalStateException("Unknown do_await variant: " + variant);
208206
}
209207

210-
/**
211-
* Fetches + maps the error the core stashed on the last failing packed-result call (cold path).
212-
*/
213-
private ProtocolException takeLastVmError() {
214-
try (Arena arena = Arena.ofConfined()) {
215-
MemorySegment err = VmError.allocate(arena);
216-
SharedCoreNative.vm_take_last_vm_error(vmHandle, err);
217-
return closeVmAndMapError(err);
218-
}
219-
}
220-
221208
@Override
222209
public @Nullable NotificationValue takeNotification(int handle) {
223210
verifyNotFreed();
@@ -293,7 +280,7 @@ public Input input() {
293280
SharedCoreNative.vm_sys_input(vmHandle, out);
294281
int state = InputResult.state(out);
295282
if (state == SharedCoreNative.ERROR_STATE()) {
296-
throw takeLastVmError();
283+
abortAfterCoreClosed();
297284
}
298285
updateState(state);
299286

@@ -429,7 +416,7 @@ public CallHandle call(
429416
SharedCoreNative.vm_sys_call(vmHandle, args, out);
430417
int state = CallResult.state(out);
431418
if (state == SharedCoreNative.ERROR_STATE()) {
432-
throw takeLastVmError();
419+
abortAfterCoreClosed();
433420
}
434421
updateState(state);
435422
return new CallHandle(CallResult.invocation_id_handle(out), CallResult.result_handle(out));
@@ -469,7 +456,7 @@ public Awakeable awakeable() {
469456
SharedCoreNative.vm_sys_awakeable(vmHandle, out);
470457
int state = AwakeableResult.state(out);
471458
if (state == SharedCoreNative.ERROR_STATE()) {
472-
throw takeLastVmError();
459+
abortAfterCoreClosed();
473460
}
474461
updateState(state);
475462
return new Awakeable(
@@ -592,7 +579,7 @@ public RunResultHandle run(String name) {
592579
SharedCoreNative.vm_sys_run(vmHandle, FfmEncoding.foreignUtf8(arena, name), out);
593580
int state = RunResult.state(out);
594581
if (state == SharedCoreNative.ERROR_STATE()) {
595-
throw takeLastVmError();
582+
abortAfterCoreClosed();
596583
}
597584
updateState(state);
598585
return new RunResultHandle(RunResult.replayed(out) != 0, RunResult.handle(out));
@@ -715,25 +702,25 @@ public void end() {
715702
/**
716703
* Decodes a register-packed handle result ({@code u64}): low 32 bits = state (or the {@link
717704
* SharedCoreNative#ERROR_STATE()} sentinel), high 32 bits = handle. On the error sentinel we
718-
* fetch + throw the stashed error; otherwise update the cached state and return the handle.
705+
* {@link #abortAfterCoreClosed()}; otherwise update the cached state and return the handle.
719706
*/
720707
private int parseHandleResult(long packed) {
721708
int state = (int) packed;
722709
if (state == SharedCoreNative.ERROR_STATE()) {
723-
throw takeLastVmError();
710+
abortAfterCoreClosed();
724711
}
725712
updateState(state);
726713
return (int) (packed >>> 32);
727714
}
728715

729716
/**
730717
* Applies a register-packed empty result (a bare {@code u32}): the new state, or the {@link
731-
* SharedCoreNative#ERROR_STATE()} sentinel. On the error sentinel we fetch + throw the stashed
732-
* error; otherwise update the cached state.
718+
* SharedCoreNative#ERROR_STATE()} sentinel. On the error sentinel we {@link
719+
* #abortAfterCoreClosed()}; otherwise update the cached state.
733720
*/
734721
private void consumeEmptyResult(int packed) {
735722
if (packed == SharedCoreNative.ERROR_STATE()) {
736-
throw takeLastVmError();
723+
abortAfterCoreClosed();
737724
}
738725
updateState(packed);
739726
}
@@ -766,6 +753,16 @@ private void verifyNotFreed() {
766753
}
767754
}
768755

756+
/**
757+
* Mark the state machine as closed and throw AbortedExecutionException.
758+
*
759+
* <p>A subsequent notifyError will no-op.
760+
*/
761+
private void abortAfterCoreClosed() {
762+
cachedState = InvocationState.CLOSED;
763+
AbortedExecutionException.sneakyThrow();
764+
}
765+
769766
private static String formatThrowableMessage(Throwable throwable) {
770767
return throwable.toString();
771768
}

0 commit comments

Comments
 (0)