restate-sdk 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Cargo.lock +1 -1
- data/README.md +15 -10
- data/ext/restate_internal/Cargo.toml +1 -1
- data/lib/restate/client.rb +5 -29
- data/lib/restate/config.rb +4 -11
- data/lib/restate/context.rb +13 -128
- data/lib/restate/discovery.rb +6 -15
- data/lib/restate/durable_future.rb +15 -39
- data/lib/restate/endpoint.rb +61 -48
- data/lib/restate/errors.rb +2 -15
- data/lib/restate/handler.rb +21 -21
- data/lib/restate/serde.rb +14 -130
- data/lib/restate/server.rb +12 -27
- data/lib/restate/server_context.rb +111 -276
- data/lib/restate/service.rb +2 -3
- data/lib/restate/service_dsl.rb +8 -8
- data/lib/restate/service_proxy.rb +2 -13
- data/lib/restate/testing.rb +1 -1
- data/lib/restate/version.rb +2 -2
- data/lib/restate/virtual_object.rb +3 -4
- data/lib/restate/vm.rb +9 -74
- data/lib/restate/workflow.rb +3 -4
- data/lib/restate.rb +4 -93
- data/sig/restate.rbs +202 -0
- metadata +3 -18
- data/lib/tapioca/dsl/compilers/restate.rb +0 -115
- data/rbi/restate-sdk.rbi +0 -582
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: false
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require 'async'
|
|
@@ -18,35 +18,27 @@ module Restate
|
|
|
18
18
|
class ServerContext
|
|
19
19
|
include WorkflowContext
|
|
20
20
|
include WorkflowSharedContext
|
|
21
|
-
extend T::Sig
|
|
22
21
|
|
|
23
|
-
LOGGER =
|
|
22
|
+
LOGGER = Logger.new($stdout, progname: 'Restate::ServerContext')
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
attr_reader :vm
|
|
24
|
+
attr_reader :vm, :invocation
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@
|
|
37
|
-
@
|
|
38
|
-
@
|
|
39
|
-
@send_output = T.let(send_output, T.untyped)
|
|
40
|
-
@input_queue = T.let(input_queue, Async::Queue)
|
|
41
|
-
@run_coros_to_execute = T.let({}, T::Hash[Integer, T.untyped])
|
|
42
|
-
@attempt_finished_event = T.let(AttemptFinishedEvent.new, AttemptFinishedEvent)
|
|
43
|
-
@middleware = T.let(middleware, T::Array[T.untyped])
|
|
26
|
+
def initialize(vm:, handler:, invocation:, send_output:, input_queue:, middleware: [],
|
|
27
|
+
outbound_middleware: [])
|
|
28
|
+
@vm = vm
|
|
29
|
+
@handler = handler
|
|
30
|
+
@invocation = invocation
|
|
31
|
+
@send_output = send_output
|
|
32
|
+
@input_queue = input_queue
|
|
33
|
+
@run_coros_to_execute = {}
|
|
34
|
+
@attempt_finished_event = AttemptFinishedEvent.new
|
|
35
|
+
@middleware = middleware
|
|
36
|
+
@outbound_middleware = outbound_middleware
|
|
44
37
|
end
|
|
45
38
|
|
|
46
39
|
# ── Main entry point ──
|
|
47
40
|
|
|
48
41
|
# Runs the handler to completion, writing the output (or failure) to the journal.
|
|
49
|
-
sig { void }
|
|
50
42
|
def enter
|
|
51
43
|
Thread.current[:restate_context] = self
|
|
52
44
|
Thread.current[:restate_service_kind] = @handler.service_tag.kind
|
|
@@ -54,7 +46,7 @@ module Restate
|
|
|
54
46
|
in_buffer = @invocation.input_buffer
|
|
55
47
|
out_buffer = Restate.invoke_handler(handler: @handler, ctx: self, in_buffer: in_buffer,
|
|
56
48
|
middleware: @middleware)
|
|
57
|
-
@vm.sys_write_output_success(out_buffer
|
|
49
|
+
@vm.sys_write_output_success(out_buffer)
|
|
58
50
|
@vm.sys_end
|
|
59
51
|
rescue TerminalError => e
|
|
60
52
|
failure = Failure.new(code: e.status_code, message: e.message)
|
|
@@ -66,8 +58,8 @@ module Restate
|
|
|
66
58
|
raise
|
|
67
59
|
rescue StandardError => e
|
|
68
60
|
# Walk the cause chain for TerminalError or internal exceptions
|
|
69
|
-
cause =
|
|
70
|
-
handled =
|
|
61
|
+
cause = e
|
|
62
|
+
handled = false
|
|
71
63
|
while cause
|
|
72
64
|
if cause.is_a?(TerminalError)
|
|
73
65
|
f = Failure.new(code: cause.status_code, message: cause.message)
|
|
@@ -95,7 +87,6 @@ module Restate
|
|
|
95
87
|
# Called by the server when the attempt ends (handler completed, disconnected,
|
|
96
88
|
# or transient error). Signals the attempt_finished_event so that user code
|
|
97
89
|
# and background pool jobs can clean up.
|
|
98
|
-
sig { void }
|
|
99
90
|
def on_attempt_finished
|
|
100
91
|
@attempt_finished_event.set!
|
|
101
92
|
end
|
|
@@ -103,44 +94,37 @@ module Restate
|
|
|
103
94
|
# ── State operations ──
|
|
104
95
|
|
|
105
96
|
# Durably retrieves a state entry by name. Returns nil if unset.
|
|
106
|
-
sig { override.params(name: String, serde: T.untyped).returns(T.untyped) }
|
|
107
97
|
def get(name, serde: JsonSerde)
|
|
108
98
|
get_async(name, serde: serde).await
|
|
109
99
|
end
|
|
110
100
|
|
|
111
101
|
# Returns a DurableFuture for a state entry. Resolves to nil if unset.
|
|
112
|
-
sig { override.params(name: String, serde: T.untyped).returns(DurableFuture) }
|
|
113
102
|
def get_async(name, serde: JsonSerde)
|
|
114
103
|
handle = @vm.sys_get_state(name)
|
|
115
104
|
DurableFuture.new(self, handle, serde: serde)
|
|
116
105
|
end
|
|
117
106
|
|
|
118
107
|
# Durably sets a state entry. The value is serialized via +serde+.
|
|
119
|
-
sig { override.params(name: String, value: T.untyped, serde: T.untyped).void }
|
|
120
108
|
def set(name, value, serde: JsonSerde)
|
|
121
|
-
@vm.sys_set_state(name, serde.serialize(value)
|
|
109
|
+
@vm.sys_set_state(name, serde.serialize(value))
|
|
122
110
|
end
|
|
123
111
|
|
|
124
112
|
# Durably removes a single state entry by name.
|
|
125
|
-
sig { override.params(name: String).void }
|
|
126
113
|
def clear(name)
|
|
127
114
|
@vm.sys_clear_state(name)
|
|
128
115
|
end
|
|
129
116
|
|
|
130
117
|
# Durably removes all state entries for this virtual object or workflow.
|
|
131
|
-
sig { override.void }
|
|
132
118
|
def clear_all
|
|
133
119
|
@vm.sys_clear_all_state
|
|
134
120
|
end
|
|
135
121
|
|
|
136
122
|
# Returns the list of all state entry names for this virtual object or workflow.
|
|
137
|
-
sig { override.returns(T.untyped) }
|
|
138
123
|
def state_keys
|
|
139
124
|
state_keys_async.await
|
|
140
125
|
end
|
|
141
126
|
|
|
142
127
|
# Returns a DurableFuture for the list of all state entry names.
|
|
143
|
-
sig { override.returns(DurableFuture) }
|
|
144
128
|
def state_keys_async
|
|
145
129
|
handle = @vm.sys_get_state_keys
|
|
146
130
|
DurableFuture.new(self, handle)
|
|
@@ -150,7 +134,6 @@ module Restate
|
|
|
150
134
|
|
|
151
135
|
# Returns a durable future that completes after the given duration.
|
|
152
136
|
# The timer survives handler restarts.
|
|
153
|
-
sig { params(seconds: Numeric).returns(DurableFuture) }
|
|
154
137
|
def sleep(seconds)
|
|
155
138
|
millis = (seconds * 1000).to_i
|
|
156
139
|
handle = @vm.sys_sleep(millis)
|
|
@@ -158,45 +141,31 @@ module Restate
|
|
|
158
141
|
end
|
|
159
142
|
|
|
160
143
|
# Block until a previously created handle completes. Returns the value.
|
|
161
|
-
sig { params(handle: Integer).returns(T.untyped) }
|
|
162
144
|
def resolve_handle(handle)
|
|
163
145
|
poll_and_take(handle)
|
|
164
146
|
end
|
|
165
147
|
|
|
166
148
|
# Wait until any of the given handles completes. Does not take notifications.
|
|
167
|
-
sig { params(handles: T::Array[Integer]).void }
|
|
168
149
|
def wait_any_handle(handles)
|
|
169
150
|
poll_or_cancel(handles) unless handles.any? { |h| @vm.is_completed(h) }
|
|
170
151
|
end
|
|
171
152
|
|
|
172
153
|
# Check if a handle is completed (non-blocking).
|
|
173
|
-
sig { params(handle: Integer).returns(T::Boolean) }
|
|
174
154
|
def completed?(handle)
|
|
175
155
|
@vm.is_completed(handle)
|
|
176
156
|
end
|
|
177
157
|
|
|
178
158
|
# Take a completed handle's notification, returning the value.
|
|
179
159
|
# Raises TerminalError if the handle resolved to a failure.
|
|
180
|
-
sig { params(handle: Integer).returns(T.untyped) }
|
|
181
160
|
def take_completed(handle)
|
|
182
161
|
must_take_notification(handle)
|
|
183
162
|
end
|
|
184
163
|
|
|
185
164
|
# Wait until any of the given futures completes. Returns [completed, remaining].
|
|
186
|
-
sig { override.params(futures: DurableFuture).returns([T::Array[DurableFuture], T::Array[DurableFuture]]) }
|
|
187
165
|
def wait_any(*futures)
|
|
188
166
|
handles = futures.map(&:handle)
|
|
189
167
|
wait_any_handle(handles)
|
|
190
|
-
completed
|
|
191
|
-
remaining = []
|
|
192
|
-
futures.each do |f|
|
|
193
|
-
if f.completed?
|
|
194
|
-
completed << f
|
|
195
|
-
else
|
|
196
|
-
remaining << f
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
|
-
[completed, remaining]
|
|
168
|
+
futures.partition(&:completed?)
|
|
200
169
|
end
|
|
201
170
|
|
|
202
171
|
# ── Durable run (side effect) ──
|
|
@@ -207,20 +176,15 @@ module Restate
|
|
|
207
176
|
# Pass +background: true+ to run the block in a real OS Thread, keeping the
|
|
208
177
|
# fiber event loop responsive for other concurrent handlers. Use this for
|
|
209
178
|
# CPU-intensive work.
|
|
210
|
-
sig do
|
|
211
|
-
override.params(
|
|
212
|
-
name: String,
|
|
213
|
-
serde: T.untyped,
|
|
214
|
-
retry_policy: T.nilable(RunRetryPolicy),
|
|
215
|
-
background: T::Boolean,
|
|
216
|
-
action: T.proc.returns(T.untyped)
|
|
217
|
-
).returns(DurableFuture)
|
|
218
|
-
end
|
|
219
179
|
def run(name, serde: JsonSerde, retry_policy: nil, background: false, &action)
|
|
220
180
|
handle = @vm.sys_run(name)
|
|
221
181
|
|
|
222
|
-
|
|
223
|
-
|
|
182
|
+
@run_coros_to_execute[handle] =
|
|
183
|
+
if background
|
|
184
|
+
-> { execute_run_threaded(handle, action, serde, retry_policy) }
|
|
185
|
+
else
|
|
186
|
+
-> { execute_run(handle, action, serde, retry_policy) }
|
|
187
|
+
end
|
|
224
188
|
|
|
225
189
|
DurableFuture.new(self, handle, serde: serde)
|
|
226
190
|
end
|
|
@@ -229,15 +193,6 @@ module Restate
|
|
|
229
193
|
# and returns the result directly.
|
|
230
194
|
#
|
|
231
195
|
# Accepts all the same options as +run+, including +background: true+.
|
|
232
|
-
sig do
|
|
233
|
-
override.params(
|
|
234
|
-
name: String,
|
|
235
|
-
serde: T.untyped,
|
|
236
|
-
retry_policy: T.nilable(RunRetryPolicy),
|
|
237
|
-
background: T::Boolean,
|
|
238
|
-
action: T.proc.returns(T.untyped)
|
|
239
|
-
).returns(T.untyped)
|
|
240
|
-
end
|
|
241
196
|
def run_sync(name, serde: JsonSerde, retry_policy: nil, background: false, &action)
|
|
242
197
|
run(name, serde: serde, retry_policy: retry_policy, background: background, &action).await
|
|
243
198
|
end
|
|
@@ -245,124 +200,72 @@ module Restate
|
|
|
245
200
|
# ── Service calls ──
|
|
246
201
|
|
|
247
202
|
# Durably calls a handler on a Restate service and returns a future for its result.
|
|
248
|
-
sig do
|
|
249
|
-
override.params(
|
|
250
|
-
service: T.any(String, T::Class[T.anything]),
|
|
251
|
-
handler: T.any(String, Symbol),
|
|
252
|
-
arg: T.untyped,
|
|
253
|
-
key: T.nilable(String),
|
|
254
|
-
idempotency_key: T.nilable(String),
|
|
255
|
-
headers: T.nilable(T::Hash[String, String]),
|
|
256
|
-
input_serde: T.untyped,
|
|
257
|
-
output_serde: T.untyped
|
|
258
|
-
).returns(DurableCallFuture)
|
|
259
|
-
end
|
|
260
203
|
def service_call(service, handler, arg, key: nil, idempotency_key: nil, headers: nil,
|
|
261
204
|
input_serde: NOT_SET, output_serde: NOT_SET)
|
|
262
205
|
svc_name, handler_name, handler_meta = resolve_call_target(service, handler)
|
|
263
206
|
in_serde = resolve_serde(input_serde, handler_meta, :input_serde)
|
|
264
207
|
out_serde = resolve_serde(output_serde, handler_meta, :output_serde)
|
|
265
208
|
parameter = in_serde.serialize(arg)
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
209
|
+
with_outbound_middleware(svc_name, handler_name, headers) do |hdrs|
|
|
210
|
+
call_handle = @vm.sys_call(
|
|
211
|
+
service: svc_name, handler: handler_name, parameter: parameter,
|
|
212
|
+
key: key, idempotency_key: idempotency_key, headers: hdrs
|
|
213
|
+
)
|
|
214
|
+
DurableCallFuture.new(self, call_handle.result_handle, call_handle.invocation_id_handle,
|
|
215
|
+
output_serde: out_serde)
|
|
216
|
+
end
|
|
272
217
|
end
|
|
273
218
|
|
|
274
219
|
# Sends a one-way invocation to a Restate service handler (fire-and-forget).
|
|
275
|
-
sig do
|
|
276
|
-
override.params(
|
|
277
|
-
service: T.any(String, T::Class[T.anything]),
|
|
278
|
-
handler: T.any(String, Symbol),
|
|
279
|
-
arg: T.untyped,
|
|
280
|
-
key: T.nilable(String),
|
|
281
|
-
delay: T.nilable(Numeric),
|
|
282
|
-
idempotency_key: T.nilable(String),
|
|
283
|
-
headers: T.nilable(T::Hash[String, String]),
|
|
284
|
-
input_serde: T.untyped
|
|
285
|
-
).returns(SendHandle)
|
|
286
|
-
end
|
|
287
220
|
def service_send(service, handler, arg, key: nil, delay: nil, idempotency_key: nil, headers: nil,
|
|
288
221
|
input_serde: NOT_SET)
|
|
289
222
|
svc_name, handler_name, handler_meta = resolve_call_target(service, handler)
|
|
290
223
|
in_serde = resolve_serde(input_serde, handler_meta, :input_serde)
|
|
291
224
|
parameter = in_serde.serialize(arg)
|
|
292
225
|
delay_ms = delay ? (delay * 1000).to_i : nil
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
226
|
+
with_outbound_middleware(svc_name, handler_name, headers) do |hdrs|
|
|
227
|
+
invocation_id_handle = @vm.sys_send(
|
|
228
|
+
service: svc_name, handler: handler_name, parameter: parameter,
|
|
229
|
+
key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: hdrs
|
|
230
|
+
)
|
|
231
|
+
SendHandle.new(self, invocation_id_handle)
|
|
232
|
+
end
|
|
298
233
|
end
|
|
299
234
|
|
|
300
235
|
# Durably calls a handler on a Restate virtual object, keyed by +key+.
|
|
301
|
-
sig do
|
|
302
|
-
override.params(
|
|
303
|
-
service: T.any(String, T::Class[T.anything]),
|
|
304
|
-
handler: T.any(String, Symbol),
|
|
305
|
-
key: String,
|
|
306
|
-
arg: T.untyped,
|
|
307
|
-
idempotency_key: T.nilable(String),
|
|
308
|
-
headers: T.nilable(T::Hash[String, String]),
|
|
309
|
-
input_serde: T.untyped,
|
|
310
|
-
output_serde: T.untyped
|
|
311
|
-
).returns(DurableCallFuture)
|
|
312
|
-
end
|
|
313
236
|
def object_call(service, handler, key, arg, idempotency_key: nil, headers: nil,
|
|
314
237
|
input_serde: NOT_SET, output_serde: NOT_SET)
|
|
315
238
|
svc_name, handler_name, handler_meta = resolve_call_target(service, handler)
|
|
316
239
|
in_serde = resolve_serde(input_serde, handler_meta, :input_serde)
|
|
317
240
|
out_serde = resolve_serde(output_serde, handler_meta, :output_serde)
|
|
318
241
|
parameter = in_serde.serialize(arg)
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
242
|
+
with_outbound_middleware(svc_name, handler_name, headers) do |hdrs|
|
|
243
|
+
call_handle = @vm.sys_call(
|
|
244
|
+
service: svc_name, handler: handler_name, parameter: parameter,
|
|
245
|
+
key: key, idempotency_key: idempotency_key, headers: hdrs
|
|
246
|
+
)
|
|
247
|
+
DurableCallFuture.new(self, call_handle.result_handle, call_handle.invocation_id_handle,
|
|
248
|
+
output_serde: out_serde)
|
|
249
|
+
end
|
|
325
250
|
end
|
|
326
251
|
|
|
327
252
|
# Sends a one-way invocation to a Restate virtual object handler (fire-and-forget).
|
|
328
|
-
sig do
|
|
329
|
-
override.params(
|
|
330
|
-
service: T.any(String, T::Class[T.anything]),
|
|
331
|
-
handler: T.any(String, Symbol),
|
|
332
|
-
key: String,
|
|
333
|
-
arg: T.untyped,
|
|
334
|
-
delay: T.nilable(Numeric),
|
|
335
|
-
idempotency_key: T.nilable(String),
|
|
336
|
-
headers: T.nilable(T::Hash[String, String]),
|
|
337
|
-
input_serde: T.untyped
|
|
338
|
-
).returns(SendHandle)
|
|
339
|
-
end
|
|
340
253
|
def object_send(service, handler, key, arg, delay: nil, idempotency_key: nil, headers: nil,
|
|
341
254
|
input_serde: NOT_SET)
|
|
342
255
|
svc_name, handler_name, handler_meta = resolve_call_target(service, handler)
|
|
343
256
|
in_serde = resolve_serde(input_serde, handler_meta, :input_serde)
|
|
344
257
|
parameter = in_serde.serialize(arg)
|
|
345
258
|
delay_ms = delay ? (delay * 1000).to_i : nil
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
259
|
+
with_outbound_middleware(svc_name, handler_name, headers) do |hdrs|
|
|
260
|
+
invocation_id_handle = @vm.sys_send(
|
|
261
|
+
service: svc_name, handler: handler_name, parameter: parameter,
|
|
262
|
+
key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: hdrs
|
|
263
|
+
)
|
|
264
|
+
SendHandle.new(self, invocation_id_handle)
|
|
265
|
+
end
|
|
351
266
|
end
|
|
352
267
|
|
|
353
268
|
# Durably calls a handler on a Restate workflow, keyed by +key+.
|
|
354
|
-
sig do
|
|
355
|
-
override.params(
|
|
356
|
-
service: T.any(String, T::Class[T.anything]),
|
|
357
|
-
handler: T.any(String, Symbol),
|
|
358
|
-
key: String,
|
|
359
|
-
arg: T.untyped,
|
|
360
|
-
idempotency_key: T.nilable(String),
|
|
361
|
-
headers: T.nilable(T::Hash[String, String]),
|
|
362
|
-
input_serde: T.untyped,
|
|
363
|
-
output_serde: T.untyped
|
|
364
|
-
).returns(DurableCallFuture)
|
|
365
|
-
end
|
|
366
269
|
def workflow_call(service, handler, key, arg, idempotency_key: nil, headers: nil,
|
|
367
270
|
input_serde: NOT_SET, output_serde: NOT_SET)
|
|
368
271
|
object_call(service, handler, key, arg, idempotency_key: idempotency_key, headers: headers,
|
|
@@ -370,18 +273,6 @@ module Restate
|
|
|
370
273
|
end
|
|
371
274
|
|
|
372
275
|
# Sends a one-way invocation to a Restate workflow handler (fire-and-forget).
|
|
373
|
-
sig do
|
|
374
|
-
override.params(
|
|
375
|
-
service: T.any(String, T::Class[T.anything]),
|
|
376
|
-
handler: T.any(String, Symbol),
|
|
377
|
-
key: String,
|
|
378
|
-
arg: T.untyped,
|
|
379
|
-
delay: T.nilable(Numeric),
|
|
380
|
-
idempotency_key: T.nilable(String),
|
|
381
|
-
headers: T.nilable(T::Hash[String, String]),
|
|
382
|
-
input_serde: T.untyped
|
|
383
|
-
).returns(SendHandle)
|
|
384
|
-
end
|
|
385
276
|
def workflow_send(service, handler, key, arg, delay: nil, idempotency_key: nil, headers: nil,
|
|
386
277
|
input_serde: NOT_SET)
|
|
387
278
|
object_send(service, handler, key, arg, delay: delay, idempotency_key: idempotency_key, headers: headers,
|
|
@@ -391,20 +282,17 @@ module Restate
|
|
|
391
282
|
# ── Awakeables ──
|
|
392
283
|
|
|
393
284
|
# Creates an awakeable and returns [awakeable_id, DurableFuture].
|
|
394
|
-
sig { override.params(serde: T.untyped).returns([String, DurableFuture]) }
|
|
395
285
|
def awakeable(serde: JsonSerde)
|
|
396
286
|
id, handle = @vm.sys_awakeable
|
|
397
287
|
[id, DurableFuture.new(self, handle, serde: serde)]
|
|
398
288
|
end
|
|
399
289
|
|
|
400
290
|
# Resolves an awakeable with a success value.
|
|
401
|
-
sig { override.params(awakeable_id: String, payload: T.untyped, serde: T.untyped).void }
|
|
402
291
|
def resolve_awakeable(awakeable_id, payload, serde: JsonSerde)
|
|
403
|
-
@vm.sys_complete_awakeable_success(awakeable_id, serde.serialize(payload)
|
|
292
|
+
@vm.sys_complete_awakeable_success(awakeable_id, serde.serialize(payload))
|
|
404
293
|
end
|
|
405
294
|
|
|
406
295
|
# Rejects an awakeable with a terminal failure.
|
|
407
|
-
sig { override.params(awakeable_id: String, message: String, code: Integer).void }
|
|
408
296
|
def reject_awakeable(awakeable_id, message, code: 500)
|
|
409
297
|
failure = Failure.new(code: code, message: message)
|
|
410
298
|
@vm.sys_complete_awakeable_failure(awakeable_id, failure)
|
|
@@ -413,7 +301,6 @@ module Restate
|
|
|
413
301
|
# ── Promises (Workflow API) ──
|
|
414
302
|
|
|
415
303
|
# Gets a durable promise value, blocking until resolved.
|
|
416
|
-
sig { override.params(name: String, serde: T.untyped).returns(T.untyped) }
|
|
417
304
|
def promise(name, serde: JsonSerde)
|
|
418
305
|
handle = @vm.sys_get_promise(name)
|
|
419
306
|
poll_and_take(handle) do |raw|
|
|
@@ -422,7 +309,6 @@ module Restate
|
|
|
422
309
|
end
|
|
423
310
|
|
|
424
311
|
# Peeks at a durable promise value without blocking. Returns nil if not yet resolved.
|
|
425
|
-
sig { override.params(name: String, serde: T.untyped).returns(T.untyped) }
|
|
426
312
|
def peek_promise(name, serde: JsonSerde)
|
|
427
313
|
handle = @vm.sys_peek_promise(name)
|
|
428
314
|
poll_and_take(handle) do |raw|
|
|
@@ -431,15 +317,13 @@ module Restate
|
|
|
431
317
|
end
|
|
432
318
|
|
|
433
319
|
# Resolves a durable promise with a success value.
|
|
434
|
-
sig { override.params(name: String, payload: T.untyped, serde: T.untyped).void }
|
|
435
320
|
def resolve_promise(name, payload, serde: JsonSerde)
|
|
436
|
-
handle = @vm.sys_complete_promise_success(name, serde.serialize(payload)
|
|
321
|
+
handle = @vm.sys_complete_promise_success(name, serde.serialize(payload))
|
|
437
322
|
poll_and_take(handle)
|
|
438
323
|
nil
|
|
439
324
|
end
|
|
440
325
|
|
|
441
326
|
# Rejects a durable promise with a terminal failure.
|
|
442
|
-
sig { override.params(name: String, message: String, code: Integer).void }
|
|
443
327
|
def reject_promise(name, message, code: 500)
|
|
444
328
|
failure = Failure.new(code: code, message: message)
|
|
445
329
|
handle = @vm.sys_complete_promise_failure(name, failure)
|
|
@@ -450,7 +334,6 @@ module Restate
|
|
|
450
334
|
# ── Cancel invocation ──
|
|
451
335
|
|
|
452
336
|
# Requests cancellation of another invocation by its id.
|
|
453
|
-
sig { override.params(invocation_id: String).void }
|
|
454
337
|
def cancel_invocation(invocation_id)
|
|
455
338
|
@vm.sys_cancel_invocation(invocation_id)
|
|
456
339
|
end
|
|
@@ -458,50 +341,32 @@ module Restate
|
|
|
458
341
|
# ── Generic calls (raw bytes, no serde) ──
|
|
459
342
|
|
|
460
343
|
# Durably calls a handler using raw bytes (no serialization). Useful for proxying.
|
|
461
|
-
sig do
|
|
462
|
-
override.params(
|
|
463
|
-
service: String,
|
|
464
|
-
handler: String,
|
|
465
|
-
arg: String,
|
|
466
|
-
key: T.nilable(String),
|
|
467
|
-
idempotency_key: T.nilable(String),
|
|
468
|
-
headers: T.nilable(T::Hash[String, String])
|
|
469
|
-
).returns(DurableCallFuture)
|
|
470
|
-
end
|
|
471
344
|
def generic_call(service, handler, arg, key: nil, idempotency_key: nil, headers: nil)
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
345
|
+
with_outbound_middleware(service, handler, headers) do |hdrs|
|
|
346
|
+
call_handle = @vm.sys_call(
|
|
347
|
+
service: service, handler: handler, parameter: arg,
|
|
348
|
+
key: key, idempotency_key: idempotency_key, headers: hdrs
|
|
349
|
+
)
|
|
350
|
+
DurableCallFuture.new(self, call_handle.result_handle, call_handle.invocation_id_handle,
|
|
351
|
+
output_serde: nil)
|
|
352
|
+
end
|
|
478
353
|
end
|
|
479
354
|
|
|
480
355
|
# Sends a one-way invocation using raw bytes (no serialization). Useful for proxying.
|
|
481
|
-
sig do
|
|
482
|
-
override.params(
|
|
483
|
-
service: String,
|
|
484
|
-
handler: String,
|
|
485
|
-
arg: String,
|
|
486
|
-
key: T.nilable(String),
|
|
487
|
-
delay: T.nilable(Numeric),
|
|
488
|
-
idempotency_key: T.nilable(String),
|
|
489
|
-
headers: T.nilable(T::Hash[String, String])
|
|
490
|
-
).returns(SendHandle)
|
|
491
|
-
end
|
|
492
356
|
def generic_send(service, handler, arg, key: nil, delay: nil, idempotency_key: nil, headers: nil)
|
|
493
357
|
delay_ms = delay ? (delay * 1000).to_i : nil
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
358
|
+
with_outbound_middleware(service, handler, headers) do |hdrs|
|
|
359
|
+
invocation_id_handle = @vm.sys_send(
|
|
360
|
+
service: service, handler: handler, parameter: arg,
|
|
361
|
+
key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: hdrs
|
|
362
|
+
)
|
|
363
|
+
SendHandle.new(self, invocation_id_handle)
|
|
364
|
+
end
|
|
499
365
|
end
|
|
500
366
|
|
|
501
367
|
# ── Request metadata ──
|
|
502
368
|
|
|
503
369
|
# Returns metadata about the current invocation (id, headers, raw body).
|
|
504
|
-
sig { override.returns(T.untyped) }
|
|
505
370
|
def request
|
|
506
371
|
@request ||= Request.new(
|
|
507
372
|
id: @invocation.invocation_id,
|
|
@@ -512,7 +377,6 @@ module Restate
|
|
|
512
377
|
end
|
|
513
378
|
|
|
514
379
|
# Returns the key for this virtual object or workflow invocation.
|
|
515
|
-
sig { override.returns(String) }
|
|
516
380
|
def key
|
|
517
381
|
@invocation.key
|
|
518
382
|
end
|
|
@@ -522,18 +386,11 @@ module Restate
|
|
|
522
386
|
# ── Progress loop ──
|
|
523
387
|
|
|
524
388
|
# Polls until the given handle(s) complete, then takes the notification.
|
|
525
|
-
|
|
526
|
-
params(
|
|
527
|
-
handle: Integer,
|
|
528
|
-
block: T.nilable(T.proc.params(arg0: T.untyped).returns(T.untyped))
|
|
529
|
-
).returns(T.untyped)
|
|
530
|
-
end
|
|
531
|
-
def poll_and_take(handle, &block)
|
|
389
|
+
def poll_and_take(handle, &)
|
|
532
390
|
poll_or_cancel([handle]) unless @vm.is_completed(handle)
|
|
533
|
-
must_take_notification(handle, &
|
|
391
|
+
must_take_notification(handle, &)
|
|
534
392
|
end
|
|
535
393
|
|
|
536
|
-
sig { params(handles: T::Array[Integer]).void }
|
|
537
394
|
def poll_or_cancel(handles)
|
|
538
395
|
loop do
|
|
539
396
|
flush_output
|
|
@@ -579,12 +436,6 @@ module Restate
|
|
|
579
436
|
end
|
|
580
437
|
end
|
|
581
438
|
|
|
582
|
-
sig do
|
|
583
|
-
params(
|
|
584
|
-
handle: Integer,
|
|
585
|
-
block: T.nilable(T.proc.params(arg0: T.untyped).returns(T.untyped))
|
|
586
|
-
).returns(T.untyped)
|
|
587
|
-
end
|
|
588
439
|
def must_take_notification(handle, &block)
|
|
589
440
|
result = @vm.take_notification(handle)
|
|
590
441
|
|
|
@@ -607,7 +458,6 @@ module Restate
|
|
|
607
458
|
end
|
|
608
459
|
end
|
|
609
460
|
|
|
610
|
-
sig { void }
|
|
611
461
|
def flush_output
|
|
612
462
|
loop do
|
|
613
463
|
output = @vm.take_output
|
|
@@ -617,82 +467,72 @@ module Restate
|
|
|
617
467
|
end
|
|
618
468
|
end
|
|
619
469
|
|
|
470
|
+
# ── Outbound middleware ──
|
|
471
|
+
|
|
472
|
+
# Runs outbound middleware chain (Sidekiq client middleware pattern).
|
|
473
|
+
# Each middleware gets +call(service, handler, headers)+ and must +yield+
|
|
474
|
+
# to continue the chain. The block at the end performs the actual VM call.
|
|
475
|
+
def with_outbound_middleware(service, handler, headers, &action)
|
|
476
|
+
if @outbound_middleware.empty?
|
|
477
|
+
action.call(headers)
|
|
478
|
+
else
|
|
479
|
+
h = headers || {}
|
|
480
|
+
chain = ->(hdrs) { action.call(hdrs) }
|
|
481
|
+
@outbound_middleware.reverse_each do |mw|
|
|
482
|
+
prev = chain
|
|
483
|
+
chain = ->(hdrs) { mw.call(service, handler, hdrs) { prev.call(hdrs) } }
|
|
484
|
+
end
|
|
485
|
+
chain.call(h)
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
|
|
620
489
|
# ── Call target resolution ──
|
|
621
490
|
|
|
622
491
|
# Resolves a service+handler pair from class/symbol or string/string.
|
|
623
492
|
# Returns [service_name, handler_name, handler_metadata_or_nil].
|
|
624
|
-
sig do
|
|
625
|
-
params(
|
|
626
|
-
service: T.any(String, T::Class[T.anything]),
|
|
627
|
-
handler: T.any(String, Symbol)
|
|
628
|
-
).returns([String, String, T.nilable(Handler)])
|
|
629
|
-
end
|
|
630
493
|
def resolve_call_target(service, handler)
|
|
494
|
+
handler_name = handler.is_a?(Symbol) ? handler.name : handler.to_s
|
|
631
495
|
if service.is_a?(Class) && service.respond_to?(:service_name)
|
|
632
|
-
svc_name =
|
|
633
|
-
|
|
634
|
-
handler_meta = service.respond_to?(:handlers) ? T.unsafe(service).handlers[handler_name] : nil
|
|
496
|
+
svc_name = service.service_name
|
|
497
|
+
handler_meta = service.respond_to?(:handlers) ? service.handlers[handler_name] : nil
|
|
635
498
|
[svc_name, handler_name, handler_meta]
|
|
636
499
|
else
|
|
637
|
-
[service.to_s,
|
|
500
|
+
[service.to_s, handler_name, nil]
|
|
638
501
|
end
|
|
639
502
|
end
|
|
640
503
|
|
|
641
504
|
# Resolves a serde value: if the caller passed NOT_SET, fall back to handler metadata, then JsonSerde.
|
|
642
|
-
sig { params(caller_serde: T.untyped, handler_meta: T.nilable(Handler), field: Symbol).returns(T.untyped) }
|
|
643
505
|
def resolve_serde(caller_serde, handler_meta, field)
|
|
644
506
|
return caller_serde unless caller_serde.equal?(NOT_SET)
|
|
507
|
+
return JsonSerde unless handler_meta
|
|
645
508
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
509
|
+
io = handler_meta.handler_io
|
|
510
|
+
case field
|
|
511
|
+
when :input_serde then io.input_serde
|
|
512
|
+
when :output_serde then io.output_serde
|
|
513
|
+
else JsonSerde
|
|
650
514
|
end
|
|
651
515
|
end
|
|
652
516
|
|
|
653
517
|
# ── Run execution ──
|
|
654
518
|
|
|
655
|
-
sig do
|
|
656
|
-
params(
|
|
657
|
-
handle: Integer,
|
|
658
|
-
action: T.proc.returns(T.untyped),
|
|
659
|
-
serde: T.untyped,
|
|
660
|
-
retry_policy: T.nilable(RunRetryPolicy)
|
|
661
|
-
).void
|
|
662
|
-
end
|
|
663
519
|
def execute_run(handle, action, serde, retry_policy)
|
|
664
520
|
propose_run_result(handle, action, serde, retry_policy)
|
|
665
521
|
end
|
|
666
522
|
|
|
667
523
|
# Like execute_run, but offloads the action to a real OS Thread.
|
|
668
524
|
# The fiber yields (via IO.pipe) while the thread runs, keeping the event loop responsive.
|
|
669
|
-
sig do
|
|
670
|
-
params(
|
|
671
|
-
handle: Integer,
|
|
672
|
-
action: T.proc.returns(T.untyped),
|
|
673
|
-
serde: T.untyped,
|
|
674
|
-
retry_policy: T.nilable(RunRetryPolicy)
|
|
675
|
-
).void
|
|
676
|
-
end
|
|
677
525
|
def execute_run_threaded(handle, action, serde, retry_policy)
|
|
678
526
|
propose_run_result(handle, -> { offload_to_thread(action) }, serde, retry_policy)
|
|
679
527
|
end
|
|
680
528
|
|
|
681
529
|
# Runs the action and proposes the result (success/failure/transient) to the VM.
|
|
682
|
-
sig do
|
|
683
|
-
params(
|
|
684
|
-
handle: Integer,
|
|
685
|
-
action: T.proc.returns(T.untyped),
|
|
686
|
-
serde: T.untyped,
|
|
687
|
-
retry_policy: T.nilable(RunRetryPolicy)
|
|
688
|
-
).void
|
|
689
|
-
end
|
|
690
530
|
def propose_run_result(handle, action, serde, retry_policy)
|
|
691
531
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
692
532
|
begin
|
|
693
533
|
result = action.call
|
|
694
534
|
buffer = serde.serialize(result)
|
|
695
|
-
@vm.propose_run_completion_success(handle, buffer
|
|
535
|
+
@vm.propose_run_completion_success(handle, buffer)
|
|
696
536
|
rescue TerminalError => e
|
|
697
537
|
failure = Failure.new(code: e.status_code, message: e.message)
|
|
698
538
|
@vm.propose_run_completion_failure(handle, failure)
|
|
@@ -734,11 +574,10 @@ module Restate
|
|
|
734
574
|
# most blocking I/O (Net::HTTP, TCPSocket, etc.) and yields the fiber
|
|
735
575
|
# automatically. +background: true+ is only needed for CPU-heavy native
|
|
736
576
|
# extensions that release the GVL (e.g., image processing, crypto).
|
|
737
|
-
sig { params(action: T.proc.returns(T.untyped)).returns(T.untyped) }
|
|
738
577
|
def offload_to_thread(action)
|
|
739
578
|
read_io, write_io = IO.pipe
|
|
740
|
-
result =
|
|
741
|
-
error =
|
|
579
|
+
result = nil
|
|
580
|
+
error = nil
|
|
742
581
|
event = @attempt_finished_event
|
|
743
582
|
|
|
744
583
|
begin
|
|
@@ -772,25 +611,21 @@ module Restate
|
|
|
772
611
|
# Avoids creating a new Thread per call (~1ms + ~1MB stack each).
|
|
773
612
|
# Workers are daemon threads that do not prevent process exit.
|
|
774
613
|
module BackgroundPool
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
@
|
|
778
|
-
@
|
|
779
|
-
@mutex = T.let(Mutex.new, Mutex)
|
|
780
|
-
@size = T.let(0, Integer)
|
|
614
|
+
@queue = Queue.new
|
|
615
|
+
@workers = []
|
|
616
|
+
@mutex = Mutex.new
|
|
617
|
+
@size = 0
|
|
781
618
|
|
|
782
|
-
POOL_SIZE =
|
|
619
|
+
POOL_SIZE = Integer(ENV.fetch('RESTATE_BACKGROUND_POOL_SIZE', 8))
|
|
783
620
|
|
|
784
621
|
module_function
|
|
785
622
|
|
|
786
623
|
# Submit a block to be executed by a pool worker.
|
|
787
|
-
sig { params(block: T.proc.void).void }
|
|
788
624
|
def submit(&block)
|
|
789
625
|
ensure_started
|
|
790
626
|
@queue.push(block)
|
|
791
627
|
end
|
|
792
628
|
|
|
793
|
-
sig { void }
|
|
794
629
|
def ensure_started
|
|
795
630
|
return if @size >= POOL_SIZE
|
|
796
631
|
|