|
30 | 30 | import java.util.LinkedHashMap; |
31 | 31 | import java.util.List; |
32 | 32 | import java.util.Map; |
| 33 | +import org.apache.logging.log4j.Logger; |
33 | 34 | import org.jspecify.annotations.Nullable; |
34 | 35 |
|
35 | 36 | /** |
|
51 | 52 | */ |
52 | 53 | public final class FfmStateMachine implements StateMachine { |
53 | 54 |
|
| 55 | + // Same logger as the native tracing bridge, so SDK- and core-emitted lines interleave. |
| 56 | + private static final Logger LOG = NativeLogging.LOG; |
| 57 | + |
| 58 | + private static final int SUSPENDED_ERROR_CODE = 599; |
| 59 | + |
54 | 60 | private final MemorySegment vmHandle; |
55 | 61 | private final String responseContentType; |
56 | 62 | private boolean freed = false; |
@@ -756,10 +762,23 @@ private void verifyNotFreed() { |
756 | 762 | /** |
757 | 763 | * Mark the state machine as closed and throw AbortedExecutionException. |
758 | 764 | * |
759 | | - * <p>A subsequent notifyError will no-op. |
| 765 | + * <p>A subsequent notifyError will no-op. Before aborting we surface the failure the core stashed |
| 766 | + * (skipping the fetch when WARN is off, and skipping suspensions, which ride the same channel). |
760 | 767 | */ |
761 | 768 | private void abortAfterCoreClosed() { |
762 | 769 | cachedState = InvocationState.CLOSED; |
| 770 | + if (LOG.isWarnEnabled()) { |
| 771 | + try (Arena arena = Arena.ofConfined()) { |
| 772 | + MemorySegment err = VmError.allocate(arena); |
| 773 | + SharedCoreNative.vm_take_last_vm_error(vmHandle, err); |
| 774 | + int code = VmError.code(err); |
| 775 | + // takeSliceString also frees the owned message Slice, so decode it even when not logging. |
| 776 | + String message = FfmEncoding.takeSliceString(VmError.message(err)); |
| 777 | + if (code != SUSPENDED_ERROR_CODE) { |
| 778 | + LOG.warn("Invocation failed: {}", message); |
| 779 | + } |
| 780 | + } |
| 781 | + } |
763 | 782 | AbortedExecutionException.sneakyThrow(); |
764 | 783 | } |
765 | 784 |
|
|
0 commit comments