Skip to content

Commit 230bd21

Browse files
Make take output zero copy
1 parent afb5228 commit 230bd21

2 files changed

Lines changed: 12 additions & 10 deletions

File tree

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff 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

sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/HttpResponseFlowAdapter.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import io.netty.buffer.Unpooled;
1414
import io.vertx.core.buffer.Buffer;
1515
import io.vertx.core.http.HttpServerResponse;
16+
import java.lang.ref.Reference;
1617
import java.util.concurrent.Flow;
1718
import org.apache.logging.log4j.LogManager;
1819
import 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

0 commit comments

Comments
 (0)