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