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