File tree Expand file tree Collapse file tree
sdk-core/src/main/java23/dev/restate/sdk/core/statemachine/ffm
sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -138,15 +138,9 @@ public Slice takeOutput() {
138138 return Slice .EMPTY ;
139139 }
140140 try (Arena arena = Arena .ofConfined ()) {
141- // take_output never fails: it writes a bare Slice (not a BufferResult). Copy the buffer out
142- // into a heap byte[] and free the native buffer immediately (takeSliceBytes). This is the
143- // safe
144- // default while we chase a GC-tied-lifetime flake in the bidi path: the zero-copy
145- // wrapOwnedSlice variant handed the buffer to the async output stream and could be collected
146- // before the stream consumed it, intermittently dropping a flushed chunk.
147141 MemorySegment out = FfmEncoding .allocateSliceStruct (arena );
148142 SharedCoreNative .vm_take_output (vmHandle , out );
149- return Slice . wrap ( FfmEncoding .takeSliceBytes (out ) );
143+ return FfmEncoding .wrapOwnedSlice (out );
150144 }
151145 }
152146
Original file line number Diff line number Diff line change 1313import io .netty .buffer .Unpooled ;
1414import io .vertx .core .buffer .Buffer ;
1515import io .vertx .core .http .HttpServerResponse ;
16+ import java .lang .ref .Reference ;
1617import java .util .concurrent .Flow ;
1718import org .apache .logging .log4j .LogManager ;
1819import org .apache .logging .log4j .Logger ;
@@ -44,9 +45,16 @@ public void onNext(Slice slice) {
4445 return ;
4546 }
4647
47- // If HTTP HEADERS frame have not been sent, Vert.x will send them
48- this .httpServerResponse .write (
49- Buffer .buffer (Unpooled .wrappedBuffer (slice .asReadOnlyByteBuffer ())));
48+ // If HTTP HEADERS frame have not been sent, Vert.x will send them.
49+ //
50+ // The slice may be backed by native memory with a GC-tied free (FFM wrapOwnedSlice), and this
51+ // write is zero-copy + asynchronous — Vert.x flushes the wrapped buffer later. Keep the slice
52+ // reachable until the write completes so the cleanup can't free the native buffer mid-flush:
53+ // the completion callback captures `slice` (reachabilityFence is an un-elidable use), and the
54+ // future holds that callback until the flush is done.
55+ this .httpServerResponse
56+ .write (Buffer .buffer (Unpooled .wrappedBuffer (slice .asReadOnlyByteBuffer ())))
57+ .onComplete (ignored -> Reference .reachabilityFence (slice ));
5058 }
5159
5260 @ Override
You can’t perform that action at this time.
0 commit comments