|
12 | 12 | import dev.restate.sdk.common.RetryPolicy; |
13 | 13 | import dev.restate.sdk.common.TerminalException; |
14 | 14 | import dev.restate.sdk.core.statemachine.StateMachine; |
| 15 | +import dev.restate.sdk.core.statemachine.ffm.generated.CallArguments; |
| 16 | +import dev.restate.sdk.core.statemachine.ffm.generated.ForeignSlice; |
15 | 17 | import dev.restate.sdk.core.statemachine.ffm.generated.SharedCoreNative; |
16 | 18 | import dev.restate.sdk.core.statemachine.ffm.generated.Slice; |
17 | | -import dev.restate.sdk.core.statemachine.ffm.generated.TargetAbi; |
18 | 19 | import java.io.ByteArrayOutputStream; |
19 | 20 | import java.lang.foreign.Arena; |
20 | 21 | import java.lang.foreign.MemorySegment; |
@@ -57,39 +58,111 @@ static MemorySegment allocateBytes(SegmentAllocator alloc, byte @Nullable [] byt |
57 | 58 | } |
58 | 59 |
|
59 | 60 | /** |
60 | | - * Allocate a native segment holding the bytes of {@code slice}, copied straight from the slice's |
61 | | - * buffer (no intermediate heap {@code byte[]}), or {@link MemorySegment#NULL} when empty. |
| 61 | + * Allocate a native segment holding the UTF-8 bytes of {@code s}, or {@link MemorySegment#NULL} |
| 62 | + * when {@code s} is null. An empty (non-null) string yields a zero-length, non-null segment. |
62 | 63 | */ |
63 | | - static MemorySegment allocateSlice(SegmentAllocator alloc, dev.restate.common.Slice slice) { |
| 64 | + static MemorySegment allocateUtf8(SegmentAllocator alloc, @Nullable String s) { |
| 65 | + if (s == null) { |
| 66 | + return MemorySegment.NULL; |
| 67 | + } |
| 68 | + return allocateBytes(alloc, s.getBytes(StandardCharsets.UTF_8)); |
| 69 | + } |
| 70 | + |
| 71 | + /** |
| 72 | + * Allocate a Rust-owned native buffer (via {@code alloc_buffer}) and copy {@code slice} into it, |
| 73 | + * transferring ownership to the native callee. The returned segment is tied to no {@link Arena}, |
| 74 | + * so Java never frees it: the native side takes ownership on the call and frees it when the core |
| 75 | + * is done with it (see {@code MEMORY_MODEL.md}). Used for opaque payloads only. Returns {@link |
| 76 | + * MemorySegment#NULL} for an empty slice. |
| 77 | + */ |
| 78 | + static MemorySegment transferSlice(dev.restate.common.Slice slice) { |
64 | 79 | int len = slice.readableBytes(); |
65 | 80 | if (len == 0) { |
66 | 81 | return MemorySegment.NULL; |
67 | 82 | } |
68 | | - MemorySegment seg = alloc.allocate(len); |
69 | | - slice.copyTo(seg.asByteBuffer()); |
70 | | - return seg; |
| 83 | + MemorySegment buf = SharedCoreNative.alloc_buffer(len).reinterpret(len); |
| 84 | + slice.copyTo(buf.asByteBuffer()); |
| 85 | + return buf; |
71 | 86 | } |
72 | 87 |
|
73 | 88 | /** |
74 | | - * Allocate a native segment holding the UTF-8 bytes of {@code s}, or {@link MemorySegment#NULL} |
75 | | - * when {@code s} is null. An empty (non-null) string yields a zero-length, non-null segment. |
| 89 | + * Like {@link #transferSlice} but for the UTF-8 bytes of {@code s}: allocates a Rust-owned buffer |
| 90 | + * via {@code alloc_buffer}, copies the encoded bytes in, and transfers ownership to the native |
| 91 | + * callee (which re-owns it as a {@code String} zero-copy, see {@code take_string}). Used for the |
| 92 | + * {@code notify_error} message/stacktrace. Returns {@link MemorySegment#NULL} for a null/empty |
| 93 | + * string (the Rust side maps that to the empty string / absent stacktrace). |
76 | 94 | */ |
77 | | - static MemorySegment allocateUtf8(SegmentAllocator alloc, @Nullable String s) { |
| 95 | + static MemorySegment transferUtf8(@Nullable String s) { |
78 | 96 | if (s == null) { |
79 | 97 | return MemorySegment.NULL; |
80 | 98 | } |
81 | | - return allocateBytes(alloc, s.getBytes(StandardCharsets.UTF_8)); |
82 | | - } |
83 | | - |
84 | | - static long len(MemorySegment seg) { |
85 | | - return seg.byteSize(); |
| 99 | + byte[] bytes = s.getBytes(StandardCharsets.UTF_8); |
| 100 | + if (bytes.length == 0) { |
| 101 | + return MemorySegment.NULL; |
| 102 | + } |
| 103 | + MemorySegment buf = SharedCoreNative.alloc_buffer(bytes.length).reinterpret(bytes.length); |
| 104 | + MemorySegment.copy(bytes, 0, buf, ValueLayout.JAVA_BYTE, 0, bytes.length); |
| 105 | + return buf; |
86 | 106 | } |
87 | 107 |
|
88 | 108 | /** Allocates a result {@link Slice} struct out-param (for calls that write a bare Slice). */ |
89 | 109 | static MemorySegment allocateSliceStruct(SegmentAllocator alloc) { |
90 | 110 | return Slice.allocate(alloc); |
91 | 111 | } |
92 | 112 |
|
| 113 | + // ------------------------------------------------------------------------- |
| 114 | + // By-value boundary slices. A ForeignSlice borrows Java-arena memory (Rust reads it in-call and |
| 115 | + // never frees it); a Slice is alloc_buffer-backed memory whose ownership transfers into the |
| 116 | + // native callee. Both cross by value as a {ptr,len} struct; we keep the construction here so the |
| 117 | + // state machine never touches the (name-colliding) generated Slice type directly. |
| 118 | + // ------------------------------------------------------------------------- |
| 119 | + |
| 120 | + /** Writes {@code buf}'s ptr+len into an already-allocated ForeignSlice field segment. */ |
| 121 | + private static void setForeign(MemorySegment foreignSliceField, MemorySegment buf) { |
| 122 | + ForeignSlice.ptr(foreignSliceField, buf); |
| 123 | + ForeignSlice.len(foreignSliceField, buf.byteSize()); |
| 124 | + } |
| 125 | + |
| 126 | + /** Writes {@code buf}'s ptr+len into an already-allocated (ownership-transfer) Slice field. */ |
| 127 | + private static void setOwned(MemorySegment sliceField, MemorySegment buf) { |
| 128 | + Slice.ptr(sliceField, buf); |
| 129 | + Slice.len(sliceField, buf.byteSize()); |
| 130 | + } |
| 131 | + |
| 132 | + /** A by-value ForeignSlice borrowing a fresh UTF-8 copy of {@code s} (null/empty → null ptr). */ |
| 133 | + static MemorySegment foreignUtf8(SegmentAllocator alloc, @Nullable String s) { |
| 134 | + MemorySegment fs = ForeignSlice.allocate(alloc); |
| 135 | + setForeign(fs, allocateUtf8(alloc, s)); |
| 136 | + return fs; |
| 137 | + } |
| 138 | + |
| 139 | + /** A by-value ForeignSlice borrowing a fresh copy of {@code bytes} (null/empty → null ptr). */ |
| 140 | + static MemorySegment foreignBytes(SegmentAllocator alloc, byte @Nullable [] bytes) { |
| 141 | + MemorySegment fs = ForeignSlice.allocate(alloc); |
| 142 | + setForeign(fs, allocateBytes(alloc, bytes)); |
| 143 | + return fs; |
| 144 | + } |
| 145 | + |
| 146 | + /** |
| 147 | + * A by-value Slice transferring {@code slice}'s bytes (alloc_buffer buffer the callee re-owns). |
| 148 | + */ |
| 149 | + static MemorySegment transferToSlice(SegmentAllocator alloc, dev.restate.common.Slice slice) { |
| 150 | + MemorySegment buf = transferSlice(slice); |
| 151 | + MemorySegment s = Slice.allocate(alloc); |
| 152 | + Slice.ptr(s, buf); |
| 153 | + Slice.len(s, buf.byteSize()); |
| 154 | + return s; |
| 155 | + } |
| 156 | + |
| 157 | + /** A by-value Slice transferring the UTF-8 bytes of {@code s} (callee re-owns it as a String). */ |
| 158 | + static MemorySegment transferUtf8ToSlice(SegmentAllocator alloc, @Nullable String s) { |
| 159 | + MemorySegment buf = transferUtf8(s); |
| 160 | + MemorySegment slice = Slice.allocate(alloc); |
| 161 | + Slice.ptr(slice, buf); |
| 162 | + Slice.len(slice, buf.byteSize()); |
| 163 | + return slice; |
| 164 | + } |
| 165 | + |
93 | 166 | // ------------------------------------------------------------------------- |
94 | 167 | // Result Slice reading (outputs: owned by Rust, must be freed) |
95 | 168 | // ------------------------------------------------------------------------- |
@@ -121,12 +194,6 @@ static String takeSliceString(MemorySegment sliceStruct) { |
121 | 194 | return new String(bytes, StandardCharsets.UTF_8); |
122 | 195 | } |
123 | 196 |
|
124 | | - /** Like {@link #takeSliceString} but returns {@code null} for the empty/absent slice. */ |
125 | | - static @Nullable String takeSliceStringOrNull(MemorySegment sliceStruct) { |
126 | | - byte[] bytes = takeSliceBytes(sliceStruct); |
127 | | - return bytes.length == 0 ? null : new String(bytes, StandardCharsets.UTF_8); |
128 | | - } |
129 | | - |
130 | 197 | /** |
131 | 198 | * Wraps an owned result {@link Slice} (Rust-allocated; must be released via {@code free_buffer}) |
132 | 199 | * in a {@link MemorySegmentSlice} read zero-copy — no copy-out into a heap {@code byte[]}. The |
@@ -157,44 +224,26 @@ static dev.restate.common.Slice wrapOwnedSlice(MemorySegment sliceStruct) { |
157 | 224 | * (ptr,len)}; a null ptr means the optional field is absent. {@code headers} is the encoded |
158 | 225 | * header-list blob. |
159 | 226 | */ |
160 | | - static MemorySegment buildTarget( |
| 227 | + static MemorySegment buildCallArguments( |
161 | 228 | SegmentAllocator alloc, |
162 | 229 | Target target, |
| 230 | + dev.restate.common.Slice payload, |
163 | 231 | @Nullable String idempotencyKey, |
164 | 232 | @Nullable String scope, |
165 | 233 | @Nullable String limitKey, |
166 | 234 | @Nullable Collection<Map.Entry<String, String>> headers) { |
167 | | - MemorySegment t = TargetAbi.allocate(alloc); |
168 | | - |
169 | | - MemorySegment service = allocateUtf8(alloc, target.getService()); |
170 | | - TargetAbi.service_ptr(t, service); |
171 | | - TargetAbi.service_len(t, len(service)); |
172 | | - |
173 | | - MemorySegment handler = allocateUtf8(alloc, target.getHandler()); |
174 | | - TargetAbi.handler_ptr(t, handler); |
175 | | - TargetAbi.handler_len(t, len(handler)); |
176 | | - |
177 | | - MemorySegment key = allocateUtf8(alloc, target.getKey()); |
178 | | - TargetAbi.key_ptr(t, key); |
179 | | - TargetAbi.key_len(t, len(key)); |
180 | | - |
181 | | - MemorySegment idem = allocateUtf8(alloc, idempotencyKey); |
182 | | - TargetAbi.idempotency_key_ptr(t, idem); |
183 | | - TargetAbi.idempotency_key_len(t, len(idem)); |
184 | | - |
185 | | - // V7 optionals: a null/empty segment maps to `None` on the Rust side. |
186 | | - MemorySegment scopeSeg = allocateUtf8(alloc, scope); |
187 | | - TargetAbi.scope_ptr(t, scopeSeg); |
188 | | - TargetAbi.scope_len(t, len(scopeSeg)); |
189 | | - |
190 | | - MemorySegment limitKeySeg = allocateUtf8(alloc, limitKey); |
191 | | - TargetAbi.limit_key_ptr(t, limitKeySeg); |
192 | | - TargetAbi.limit_key_len(t, len(limitKeySeg)); |
193 | | - |
194 | | - MemorySegment headersBlob = allocateBytes(alloc, encodeHeaderList(headers)); |
195 | | - TargetAbi.headers_ptr(t, headersBlob); |
196 | | - TargetAbi.headers_len(t, len(headersBlob)); |
197 | | - |
| 235 | + MemorySegment t = CallArguments.allocate(alloc); |
| 236 | + // Target fields are nested ForeignSlices borrowing a fresh copy in `alloc`; a null/empty buffer |
| 237 | + // maps to `None` (V7 optionals) / the empty slice on the Rust side. |
| 238 | + setForeign(CallArguments.service(t), allocateUtf8(alloc, target.getService())); |
| 239 | + setForeign(CallArguments.handler(t), allocateUtf8(alloc, target.getHandler())); |
| 240 | + setForeign(CallArguments.key(t), allocateUtf8(alloc, target.getKey())); |
| 241 | + setForeign(CallArguments.idempotency_key(t), allocateUtf8(alloc, idempotencyKey)); |
| 242 | + setForeign(CallArguments.scope(t), allocateUtf8(alloc, scope)); |
| 243 | + setForeign(CallArguments.limit_key(t), allocateUtf8(alloc, limitKey)); |
| 244 | + setForeign(CallArguments.headers(t), allocateBytes(alloc, encodeHeaderList(headers))); |
| 245 | + // input is a Slice: ownership of the alloc_buffer-backed payload transfers into the core. |
| 246 | + setOwned(CallArguments.input(t), transferSlice(payload)); |
198 | 247 | return t; |
199 | 248 | } |
200 | 249 |
|
|
0 commit comments