restate-sdk 0.4.3-aarch64-linux

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.
@@ -0,0 +1,1094 @@
1
+ use magnus::{
2
+ function, method,
3
+ prelude::*,
4
+ value::ReprValue,
5
+ Error, ExceptionClass, Module, Object, RArray, RString, Ruby, Value,
6
+ };
7
+ use restate_sdk_shared_core::{
8
+ CallHandle, CoreVM, DoProgressResponse, Error as CoreError, Header, IdentityVerifier, Input,
9
+ NonEmptyValue, NotificationHandle, ResponseHead, RetryPolicy, RunExitResult,
10
+ TakeOutputResult, Target, TerminalFailure, VMOptions, Value as CoreValue, VM,
11
+ CANCEL_NOTIFICATION_HANDLE,
12
+ };
13
+ use std::cell::RefCell;
14
+ use std::fmt;
15
+ use std::sync::OnceLock;
16
+ use std::time::{Duration, SystemTime};
17
+
18
+ // Current crate version
19
+ const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION");
20
+
21
+ // ── Exception classes stored in OnceLock ──
22
+ // ExceptionClass contains NonNull<RBasic> which is !Send+!Sync,
23
+ // but Ruby exception classes are global and only accessed under the GVL.
24
+
25
+ struct SyncExceptionClass(ExceptionClass);
26
+ unsafe impl Send for SyncExceptionClass {}
27
+ unsafe impl Sync for SyncExceptionClass {}
28
+
29
+ static VM_ERROR_CLASS: OnceLock<SyncExceptionClass> = OnceLock::new();
30
+ static IDENTITY_KEY_ERROR_CLASS: OnceLock<SyncExceptionClass> = OnceLock::new();
31
+ static IDENTITY_VERIFICATION_ERROR_CLASS: OnceLock<SyncExceptionClass> = OnceLock::new();
32
+
33
+ fn vm_error_class() -> ExceptionClass {
34
+ VM_ERROR_CLASS.get().expect("VM_ERROR_CLASS not initialized").0
35
+ }
36
+
37
+ fn identity_key_error_class() -> ExceptionClass {
38
+ IDENTITY_KEY_ERROR_CLASS
39
+ .get()
40
+ .expect("IDENTITY_KEY_ERROR_CLASS not initialized")
41
+ .0
42
+ }
43
+
44
+ fn identity_verification_error_class() -> ExceptionClass {
45
+ IDENTITY_VERIFICATION_ERROR_CLASS
46
+ .get()
47
+ .expect("IDENTITY_VERIFICATION_ERROR_CLASS not initialized")
48
+ .0
49
+ }
50
+
51
+ // ── Data wrappers ──
52
+
53
+ #[magnus::wrap(class = "Restate::Internal::Header")]
54
+ #[derive(Clone)]
55
+ struct RbHeader {
56
+ key: String,
57
+ value: String,
58
+ }
59
+
60
+ impl RbHeader {
61
+ fn new(key: String, value: String) -> Self {
62
+ Self { key, value }
63
+ }
64
+ fn key(&self) -> &str {
65
+ &self.key
66
+ }
67
+ fn value(&self) -> &str {
68
+ &self.value
69
+ }
70
+ }
71
+
72
+ impl From<Header> for RbHeader {
73
+ fn from(h: Header) -> Self {
74
+ RbHeader {
75
+ key: h.key.into(),
76
+ value: h.value.into(),
77
+ }
78
+ }
79
+ }
80
+
81
+ impl From<RbHeader> for Header {
82
+ fn from(h: RbHeader) -> Self {
83
+ Header {
84
+ key: h.key.into(),
85
+ value: h.value.into(),
86
+ }
87
+ }
88
+ }
89
+
90
+ #[magnus::wrap(class = "Restate::Internal::ResponseHead")]
91
+ struct RbResponseHead {
92
+ status_code: u16,
93
+ headers: Vec<(String, String)>,
94
+ }
95
+
96
+ impl RbResponseHead {
97
+ fn status_code(&self) -> u16 {
98
+ self.status_code
99
+ }
100
+ fn headers_array(&self) -> Result<RArray, Error> {
101
+ let ruby = Ruby::get().map_err(|_| Error::new(vm_error_class(), "Ruby not available"))?;
102
+ let ary = ruby.ary_new_capa(self.headers.len());
103
+ for (k, v) in &self.headers {
104
+ let pair = ruby.ary_new_capa(2);
105
+ pair.push(ruby.str_new(k))?;
106
+ pair.push(ruby.str_new(v))?;
107
+ ary.push(pair)?;
108
+ }
109
+ Ok(ary)
110
+ }
111
+ }
112
+
113
+ impl From<ResponseHead> for RbResponseHead {
114
+ fn from(rh: ResponseHead) -> Self {
115
+ RbResponseHead {
116
+ status_code: rh.status_code,
117
+ headers: rh
118
+ .headers
119
+ .into_iter()
120
+ .map(|Header { key, value }| (key.into(), value.into()))
121
+ .collect(),
122
+ }
123
+ }
124
+ }
125
+
126
+ #[magnus::wrap(class = "Restate::Internal::Failure")]
127
+ #[derive(Clone)]
128
+ struct RbFailure {
129
+ code: u16,
130
+ message: String,
131
+ stacktrace: Option<String>,
132
+ }
133
+
134
+ impl RbFailure {
135
+ fn code(&self) -> u16 {
136
+ self.code
137
+ }
138
+ fn message(&self) -> &str {
139
+ &self.message
140
+ }
141
+ fn stacktrace(&self) -> Option<&str> {
142
+ self.stacktrace.as_deref()
143
+ }
144
+ }
145
+
146
+ impl From<TerminalFailure> for RbFailure {
147
+ fn from(f: TerminalFailure) -> Self {
148
+ RbFailure {
149
+ code: f.code,
150
+ message: f.message,
151
+ stacktrace: None,
152
+ }
153
+ }
154
+ }
155
+
156
+ impl From<RbFailure> for TerminalFailure {
157
+ fn from(f: RbFailure) -> Self {
158
+ TerminalFailure {
159
+ code: f.code,
160
+ message: f.message,
161
+ metadata: vec![],
162
+ }
163
+ }
164
+ }
165
+
166
+ impl From<RbFailure> for CoreError {
167
+ fn from(f: RbFailure) -> Self {
168
+ let mut e = Self::new(f.code, f.message);
169
+ if let Some(stacktrace) = f.stacktrace {
170
+ e = e.with_stacktrace(stacktrace);
171
+ }
172
+ e
173
+ }
174
+ }
175
+
176
+ #[magnus::wrap(class = "Restate::Internal::Void")]
177
+ struct RbVoid;
178
+
179
+ #[magnus::wrap(class = "Restate::Internal::Suspended")]
180
+ struct RbSuspended;
181
+
182
+ #[magnus::wrap(class = "Restate::Internal::StateKeys")]
183
+ #[derive(Clone)]
184
+ struct RbStateKeys {
185
+ keys: Vec<String>,
186
+ }
187
+
188
+ impl RbStateKeys {
189
+ fn keys_array(&self) -> Result<RArray, Error> {
190
+ let ruby = Ruby::get().map_err(|_| Error::new(vm_error_class(), "Ruby not available"))?;
191
+ let ary = ruby.ary_new_capa(self.keys.len());
192
+ for k in &self.keys {
193
+ ary.push(ruby.str_new(k))?;
194
+ }
195
+ Ok(ary)
196
+ }
197
+ }
198
+
199
+ #[magnus::wrap(class = "Restate::Internal::Input")]
200
+ struct RbInput {
201
+ invocation_id: String,
202
+ random_seed: u64,
203
+ key: String,
204
+ headers: Vec<RbHeader>,
205
+ input: Vec<u8>,
206
+ }
207
+
208
+ impl RbInput {
209
+ fn invocation_id(&self) -> &str {
210
+ &self.invocation_id
211
+ }
212
+ fn random_seed(&self) -> u64 {
213
+ self.random_seed
214
+ }
215
+ fn key(&self) -> &str {
216
+ &self.key
217
+ }
218
+ fn headers_array(&self) -> Result<RArray, Error> {
219
+ let ruby = Ruby::get().map_err(|_| Error::new(vm_error_class(), "Ruby not available"))?;
220
+ let ary = ruby.ary_new_capa(self.headers.len());
221
+ for h in &self.headers {
222
+ ary.push(RbHeader::new(h.key.clone(), h.value.clone()))?;
223
+ }
224
+ Ok(ary)
225
+ }
226
+ fn input_bytes(&self) -> Result<RString, Error> {
227
+ let ruby = Ruby::get().map_err(|_| Error::new(vm_error_class(), "Ruby not available"))?;
228
+ Ok(ruby.str_from_slice(&self.input))
229
+ }
230
+ }
231
+
232
+ impl From<Input> for RbInput {
233
+ fn from(i: Input) -> Self {
234
+ RbInput {
235
+ invocation_id: i.invocation_id,
236
+ random_seed: i.random_seed,
237
+ key: i.key,
238
+ headers: i.headers.into_iter().map(Into::into).collect(),
239
+ input: i.input.into(),
240
+ }
241
+ }
242
+ }
243
+
244
+ #[magnus::wrap(class = "Restate::Internal::ExponentialRetryConfig")]
245
+ #[derive(Clone)]
246
+ struct RbExponentialRetryConfig {
247
+ initial_interval: Option<u64>,
248
+ max_attempts: Option<u32>,
249
+ max_duration: Option<u64>,
250
+ max_interval: Option<u64>,
251
+ factor: Option<f64>,
252
+ }
253
+
254
+ impl RbExponentialRetryConfig {
255
+ fn initial_interval(&self) -> Option<u64> {
256
+ self.initial_interval
257
+ }
258
+ fn max_attempts(&self) -> Option<u32> {
259
+ self.max_attempts
260
+ }
261
+ fn max_duration(&self) -> Option<u64> {
262
+ self.max_duration
263
+ }
264
+ fn max_interval(&self) -> Option<u64> {
265
+ self.max_interval
266
+ }
267
+ fn factor(&self) -> Option<f64> {
268
+ self.factor
269
+ }
270
+ }
271
+
272
+ impl From<RbExponentialRetryConfig> for RetryPolicy {
273
+ fn from(value: RbExponentialRetryConfig) -> Self {
274
+ if value.initial_interval.is_some()
275
+ || value.max_attempts.is_some()
276
+ || value.max_duration.is_some()
277
+ || value.max_interval.is_some()
278
+ || value.factor.is_some()
279
+ {
280
+ RetryPolicy::Exponential {
281
+ initial_interval: Duration::from_millis(value.initial_interval.unwrap_or(50)),
282
+ max_attempts: value.max_attempts,
283
+ max_duration: value.max_duration.map(Duration::from_millis),
284
+ factor: value.factor.unwrap_or(2.0) as f32,
285
+ max_interval: value
286
+ .max_interval
287
+ .map(Duration::from_millis)
288
+ .or_else(|| Some(Duration::from_secs(10))),
289
+ }
290
+ } else {
291
+ RetryPolicy::Infinite
292
+ }
293
+ }
294
+ }
295
+
296
+ // ── Progress types ──
297
+
298
+ #[magnus::wrap(class = "Restate::Internal::DoProgressAnyCompleted")]
299
+ struct RbDoProgressAnyCompleted;
300
+
301
+ #[magnus::wrap(class = "Restate::Internal::DoProgressReadFromInput")]
302
+ struct RbDoProgressReadFromInput;
303
+
304
+ #[magnus::wrap(class = "Restate::Internal::DoProgressExecuteRun")]
305
+ struct RbDoProgressExecuteRun {
306
+ handle: u32,
307
+ }
308
+
309
+ impl RbDoProgressExecuteRun {
310
+ fn handle(&self) -> u32 {
311
+ self.handle
312
+ }
313
+ }
314
+
315
+ #[magnus::wrap(class = "Restate::Internal::DoProgressCancelSignalReceived")]
316
+ struct RbDoProgressCancelSignalReceived;
317
+
318
+ #[magnus::wrap(class = "Restate::Internal::DoWaitForPendingRun")]
319
+ struct RbDoWaitForPendingRun;
320
+
321
+ #[magnus::wrap(class = "Restate::Internal::CallHandle")]
322
+ struct RbCallHandle {
323
+ invocation_id_handle: u32,
324
+ result_handle: u32,
325
+ }
326
+
327
+ impl RbCallHandle {
328
+ fn invocation_id_handle(&self) -> u32 {
329
+ self.invocation_id_handle
330
+ }
331
+ fn result_handle(&self) -> u32 {
332
+ self.result_handle
333
+ }
334
+ }
335
+
336
+ impl From<CallHandle> for RbCallHandle {
337
+ fn from(h: CallHandle) -> Self {
338
+ RbCallHandle {
339
+ invocation_id_handle: h.invocation_id_notification_handle.into(),
340
+ result_handle: h.call_notification_handle.into(),
341
+ }
342
+ }
343
+ }
344
+
345
+ // ── VM ──
346
+
347
+ #[magnus::wrap(class = "Restate::Internal::VM")]
348
+ struct RbVM {
349
+ vm: RefCell<CoreVM>,
350
+ }
351
+
352
+ fn core_error_to_magnus(e: CoreError) -> Error {
353
+ Error::new(vm_error_class(), e.to_string())
354
+ }
355
+
356
+ impl RbVM {
357
+ fn new(ruby: &Ruby, headers: RArray) -> Result<Self, Error> {
358
+ let mut hdr_vec: Vec<(String, String)> = Vec::new();
359
+ for item in headers.into_iter() {
360
+ let pair = RArray::try_convert(item)?;
361
+ if pair.len() != 2 {
362
+ return Err(Error::new(
363
+ ruby.exception_arg_error(),
364
+ "Each header must be a [key, value] pair",
365
+ ));
366
+ }
367
+ let k: String = pair.entry(0)?;
368
+ let v: String = pair.entry(1)?;
369
+ hdr_vec.push((k, v));
370
+ }
371
+ let vm =
372
+ CoreVM::new(hdr_vec, VMOptions::default()).map_err(core_error_to_magnus)?;
373
+ Ok(Self {
374
+ vm: RefCell::new(vm),
375
+ })
376
+ }
377
+
378
+ fn get_response_head(&self) -> RbResponseHead {
379
+ self.vm.borrow().get_response_head().into()
380
+ }
381
+
382
+ fn notify_input(&self, buffer: RString) {
383
+ let bytes: Vec<u8> = unsafe { buffer.as_slice().to_vec() };
384
+ self.vm.borrow_mut().notify_input(bytes.into());
385
+ }
386
+
387
+ fn notify_input_closed(&self) {
388
+ self.vm.borrow_mut().notify_input_closed();
389
+ }
390
+
391
+ // notify_error(error_str, stacktrace_or_nil)
392
+ // Both parameters are always passed from Ruby (nil for no stacktrace).
393
+ fn notify_error(&self, error: String, stacktrace: Value) {
394
+ let st: Option<String> = if stacktrace.is_nil() {
395
+ None
396
+ } else {
397
+ Some(String::try_convert(stacktrace).unwrap_or_default())
398
+ };
399
+ let mut err = CoreError::new(restate_sdk_shared_core::error::codes::INTERNAL, error);
400
+ if let Some(s) = st {
401
+ err = err.with_stacktrace(s);
402
+ }
403
+ CoreVM::notify_error(&mut *self.vm.borrow_mut(), err, None);
404
+ }
405
+
406
+ fn take_output(&self) -> Result<Value, Error> {
407
+ let ruby = Ruby::get().map_err(|_| Error::new(vm_error_class(), "Ruby not available"))?;
408
+ Ok(match self.vm.borrow_mut().take_output() {
409
+ TakeOutputResult::Buffer(b) => ruby.str_from_slice(&b).as_value(),
410
+ TakeOutputResult::EOF => ruby.qnil().as_value(),
411
+ })
412
+ }
413
+
414
+ fn is_ready_to_execute(&self) -> Result<bool, Error> {
415
+ self.vm
416
+ .borrow()
417
+ .is_ready_to_execute()
418
+ .map_err(core_error_to_magnus)
419
+ }
420
+
421
+ fn is_completed(&self, handle: u32) -> bool {
422
+ self.vm.borrow().is_completed(handle.into())
423
+ }
424
+
425
+ fn do_progress(&self, handles: RArray) -> Result<Value, Error> {
426
+ let ruby = Ruby::get().map_err(|_| Error::new(vm_error_class(), "Ruby not available"))?;
427
+ let handle_vec: Vec<u32> = handles.to_vec()?;
428
+ let notification_handles: Vec<NotificationHandle> =
429
+ handle_vec.into_iter().map(NotificationHandle::from).collect();
430
+
431
+ let res = self.vm.borrow_mut().do_progress(notification_handles);
432
+
433
+ match res {
434
+ Err(e) if e.is_suspended_error() => Ok(ruby.into_value(RbSuspended)),
435
+ Err(e) => Err(core_error_to_magnus(e)),
436
+ Ok(DoProgressResponse::AnyCompleted) => {
437
+ Ok(ruby.into_value(RbDoProgressAnyCompleted))
438
+ }
439
+ Ok(DoProgressResponse::ReadFromInput) => {
440
+ Ok(ruby.into_value(RbDoProgressReadFromInput))
441
+ }
442
+ Ok(DoProgressResponse::ExecuteRun(handle)) => Ok(ruby.into_value(
443
+ RbDoProgressExecuteRun {
444
+ handle: handle.into(),
445
+ },
446
+ )),
447
+ Ok(DoProgressResponse::CancelSignalReceived) => {
448
+ Ok(ruby.into_value(RbDoProgressCancelSignalReceived))
449
+ }
450
+ Ok(DoProgressResponse::WaitingPendingRun) => {
451
+ Ok(ruby.into_value(RbDoWaitForPendingRun))
452
+ }
453
+ }
454
+ }
455
+
456
+ fn take_notification(&self, handle: u32) -> Result<Value, Error> {
457
+ let ruby = Ruby::get().map_err(|_| Error::new(vm_error_class(), "Ruby not available"))?;
458
+ let res = self
459
+ .vm
460
+ .borrow_mut()
461
+ .take_notification(NotificationHandle::from(handle));
462
+
463
+ match res {
464
+ Err(e) if e.is_suspended_error() => Ok(ruby.into_value(RbSuspended)),
465
+ Err(e) => Err(core_error_to_magnus(e)),
466
+ Ok(None) => Ok(ruby.qnil().as_value()),
467
+ Ok(Some(CoreValue::Void)) => Ok(ruby.into_value(RbVoid)),
468
+ Ok(Some(CoreValue::Success(b))) => Ok(ruby.str_from_slice(&b).as_value()),
469
+ Ok(Some(CoreValue::Failure(f))) => Ok(ruby.into_value(RbFailure::from(f))),
470
+ Ok(Some(CoreValue::StateKeys(keys))) => {
471
+ Ok(ruby.into_value(RbStateKeys { keys }))
472
+ }
473
+ Ok(Some(CoreValue::InvocationId(id))) => Ok(ruby.str_new(&id).as_value()),
474
+ }
475
+ }
476
+
477
+ // Syscalls
478
+
479
+ fn sys_input(&self) -> Result<RbInput, Error> {
480
+ self.vm
481
+ .borrow_mut()
482
+ .sys_input()
483
+ .map(Into::into)
484
+ .map_err(core_error_to_magnus)
485
+ }
486
+
487
+ fn sys_get_state(&self, key: String) -> Result<u32, Error> {
488
+ self.vm
489
+ .borrow_mut()
490
+ .sys_state_get(key, Default::default())
491
+ .map(Into::into)
492
+ .map_err(core_error_to_magnus)
493
+ }
494
+
495
+ fn sys_get_state_keys(&self) -> Result<u32, Error> {
496
+ self.vm
497
+ .borrow_mut()
498
+ .sys_state_get_keys()
499
+ .map(Into::into)
500
+ .map_err(core_error_to_magnus)
501
+ }
502
+
503
+ fn sys_set_state(&self, key: String, buffer: RString) -> Result<(), Error> {
504
+ let bytes: Vec<u8> = unsafe { buffer.as_slice().to_vec() };
505
+ self.vm
506
+ .borrow_mut()
507
+ .sys_state_set(key, bytes.into(), Default::default())
508
+ .map_err(core_error_to_magnus)
509
+ }
510
+
511
+ fn sys_clear_state(&self, key: String) -> Result<(), Error> {
512
+ self.vm
513
+ .borrow_mut()
514
+ .sys_state_clear(key)
515
+ .map_err(core_error_to_magnus)
516
+ }
517
+
518
+ fn sys_clear_all_state(&self) -> Result<(), Error> {
519
+ self.vm
520
+ .borrow_mut()
521
+ .sys_state_clear_all()
522
+ .map_err(core_error_to_magnus)
523
+ }
524
+
525
+ // sys_sleep(millis, name_or_nil)
526
+ fn sys_sleep(&self, millis: u64, name: Value) -> Result<u32, Error> {
527
+ let name_str: Option<String> = if name.is_nil() {
528
+ None
529
+ } else {
530
+ Some(String::try_convert(name).unwrap_or_default())
531
+ };
532
+ let now = SystemTime::now()
533
+ .duration_since(SystemTime::UNIX_EPOCH)
534
+ .expect("Duration since unix epoch cannot fail");
535
+ self.vm
536
+ .borrow_mut()
537
+ .sys_sleep(
538
+ name_str.unwrap_or_default(),
539
+ now + Duration::from_millis(millis),
540
+ Some(now),
541
+ )
542
+ .map(Into::into)
543
+ .map_err(core_error_to_magnus)
544
+ }
545
+
546
+ // sys_call(service, handler, buffer, key_or_nil, idempotency_key_or_nil, headers_or_nil)
547
+ fn sys_call(
548
+ &self,
549
+ service: String,
550
+ handler: String,
551
+ buffer: RString,
552
+ key: Value,
553
+ idempotency_key: Value,
554
+ headers: Value,
555
+ ) -> Result<RbCallHandle, Error> {
556
+ let bytes: Vec<u8> = unsafe { buffer.as_slice().to_vec() };
557
+ let key_opt: Option<String> = if key.is_nil() {
558
+ None
559
+ } else {
560
+ Some(String::try_convert(key)?)
561
+ };
562
+ let idem_opt: Option<String> = if idempotency_key.is_nil() {
563
+ None
564
+ } else {
565
+ Some(String::try_convert(idempotency_key)?)
566
+ };
567
+ let hdr_vec = if headers.is_nil() {
568
+ vec![]
569
+ } else {
570
+ parse_headers_array(RArray::try_convert(headers)?)?
571
+ };
572
+ self.vm
573
+ .borrow_mut()
574
+ .sys_call(
575
+ Target {
576
+ service,
577
+ handler,
578
+ key: key_opt,
579
+ idempotency_key: idem_opt,
580
+ headers: hdr_vec,
581
+ },
582
+ bytes.into(),
583
+ Default::default(),
584
+ )
585
+ .map(Into::into)
586
+ .map_err(core_error_to_magnus)
587
+ }
588
+
589
+ // sys_send(service, handler, buffer, key_or_nil, delay_or_nil, idempotency_key_or_nil, headers_or_nil)
590
+ fn sys_send(
591
+ &self,
592
+ service: String,
593
+ handler: String,
594
+ buffer: RString,
595
+ key: Value,
596
+ delay: Value,
597
+ idempotency_key: Value,
598
+ headers: Value,
599
+ ) -> Result<u32, Error> {
600
+ let bytes: Vec<u8> = unsafe { buffer.as_slice().to_vec() };
601
+ let key_opt: Option<String> = if key.is_nil() {
602
+ None
603
+ } else {
604
+ Some(String::try_convert(key)?)
605
+ };
606
+ let delay_opt: Option<u64> = if delay.is_nil() {
607
+ None
608
+ } else {
609
+ Some(u64::try_convert(delay)?)
610
+ };
611
+ let idem_opt: Option<String> = if idempotency_key.is_nil() {
612
+ None
613
+ } else {
614
+ Some(String::try_convert(idempotency_key)?)
615
+ };
616
+ let hdr_vec = if headers.is_nil() {
617
+ vec![]
618
+ } else {
619
+ parse_headers_array(RArray::try_convert(headers)?)?
620
+ };
621
+ self.vm
622
+ .borrow_mut()
623
+ .sys_send(
624
+ Target {
625
+ service,
626
+ handler,
627
+ key: key_opt,
628
+ idempotency_key: idem_opt,
629
+ headers: hdr_vec,
630
+ },
631
+ bytes.into(),
632
+ delay_opt.map(|millis| {
633
+ SystemTime::now()
634
+ .duration_since(SystemTime::UNIX_EPOCH)
635
+ .expect("Duration since unix epoch cannot fail")
636
+ + Duration::from_millis(millis)
637
+ }),
638
+ Default::default(),
639
+ )
640
+ .map(|s| s.invocation_id_notification_handle.into())
641
+ .map_err(core_error_to_magnus)
642
+ }
643
+
644
+ fn sys_run(&self, name: String) -> Result<u32, Error> {
645
+ self.vm
646
+ .borrow_mut()
647
+ .sys_run(name)
648
+ .map(Into::into)
649
+ .map_err(core_error_to_magnus)
650
+ }
651
+
652
+ fn propose_run_completion_success(&self, handle: u32, buffer: RString) -> Result<(), Error> {
653
+ let bytes: Vec<u8> = unsafe { buffer.as_slice().to_vec() };
654
+ CoreVM::propose_run_completion(
655
+ &mut *self.vm.borrow_mut(),
656
+ handle.into(),
657
+ RunExitResult::Success(bytes.into()),
658
+ RetryPolicy::None,
659
+ )
660
+ .map_err(core_error_to_magnus)
661
+ }
662
+
663
+ fn propose_run_completion_failure(&self, handle: u32, failure: &RbFailure) -> Result<(), Error> {
664
+ self.vm
665
+ .borrow_mut()
666
+ .propose_run_completion(
667
+ handle.into(),
668
+ RunExitResult::TerminalFailure(failure.clone().into()),
669
+ RetryPolicy::None,
670
+ )
671
+ .map_err(core_error_to_magnus)
672
+ }
673
+
674
+ fn propose_run_completion_failure_transient(
675
+ &self,
676
+ handle: u32,
677
+ failure: &RbFailure,
678
+ attempt_duration: u64,
679
+ config: &RbExponentialRetryConfig,
680
+ ) -> Result<(), Error> {
681
+ self.vm
682
+ .borrow_mut()
683
+ .propose_run_completion(
684
+ handle.into(),
685
+ RunExitResult::RetryableFailure {
686
+ attempt_duration: Duration::from_millis(attempt_duration),
687
+ error: failure.clone().into(),
688
+ },
689
+ config.clone().into(),
690
+ )
691
+ .map_err(core_error_to_magnus)
692
+ }
693
+
694
+ fn sys_write_output_success(&self, buffer: RString) -> Result<(), Error> {
695
+ let bytes: Vec<u8> = unsafe { buffer.as_slice().to_vec() };
696
+ self.vm
697
+ .borrow_mut()
698
+ .sys_write_output(NonEmptyValue::Success(bytes.into()), Default::default())
699
+ .map_err(core_error_to_magnus)
700
+ }
701
+
702
+ fn sys_write_output_failure(&self, failure: &RbFailure) -> Result<(), Error> {
703
+ self.vm
704
+ .borrow_mut()
705
+ .sys_write_output(
706
+ NonEmptyValue::Failure(failure.clone().into()),
707
+ Default::default(),
708
+ )
709
+ .map_err(core_error_to_magnus)
710
+ }
711
+
712
+ fn sys_end(&self) -> Result<(), Error> {
713
+ self.vm.borrow_mut().sys_end().map_err(core_error_to_magnus)
714
+ }
715
+
716
+ fn is_replaying(&self) -> bool {
717
+ self.vm.borrow().is_replaying()
718
+ }
719
+
720
+ // ── Awakeables ──
721
+
722
+ fn sys_awakeable(&self) -> Result<Value, Error> {
723
+ let ruby = Ruby::get().map_err(|_| Error::new(vm_error_class(), "Ruby not available"))?;
724
+ let (id, handle) = self
725
+ .vm
726
+ .borrow_mut()
727
+ .sys_awakeable()
728
+ .map_err(core_error_to_magnus)?;
729
+ let ary = ruby.ary_new_capa(2);
730
+ ary.push(ruby.str_new(&id))?;
731
+ ary.push(u32::from(handle))?;
732
+ Ok(ary.as_value())
733
+ }
734
+
735
+ fn sys_complete_awakeable_success(&self, id: String, buffer: RString) -> Result<(), Error> {
736
+ let bytes: Vec<u8> = unsafe { buffer.as_slice().to_vec() };
737
+ self.vm
738
+ .borrow_mut()
739
+ .sys_complete_awakeable(id, NonEmptyValue::Success(bytes.into()), Default::default())
740
+ .map_err(core_error_to_magnus)
741
+ }
742
+
743
+ fn sys_complete_awakeable_failure(&self, id: String, failure: &RbFailure) -> Result<(), Error> {
744
+ self.vm
745
+ .borrow_mut()
746
+ .sys_complete_awakeable(
747
+ id,
748
+ NonEmptyValue::Failure(failure.clone().into()),
749
+ Default::default(),
750
+ )
751
+ .map_err(core_error_to_magnus)
752
+ }
753
+
754
+ // ── Promises ──
755
+
756
+ fn sys_get_promise(&self, key: String) -> Result<u32, Error> {
757
+ self.vm
758
+ .borrow_mut()
759
+ .sys_get_promise(key)
760
+ .map(Into::into)
761
+ .map_err(core_error_to_magnus)
762
+ }
763
+
764
+ fn sys_peek_promise(&self, key: String) -> Result<u32, Error> {
765
+ self.vm
766
+ .borrow_mut()
767
+ .sys_peek_promise(key)
768
+ .map(Into::into)
769
+ .map_err(core_error_to_magnus)
770
+ }
771
+
772
+ fn sys_complete_promise_success(&self, key: String, buffer: RString) -> Result<u32, Error> {
773
+ let bytes: Vec<u8> = unsafe { buffer.as_slice().to_vec() };
774
+ self.vm
775
+ .borrow_mut()
776
+ .sys_complete_promise(key, NonEmptyValue::Success(bytes.into()), Default::default())
777
+ .map(Into::into)
778
+ .map_err(core_error_to_magnus)
779
+ }
780
+
781
+ fn sys_complete_promise_failure(
782
+ &self,
783
+ key: String,
784
+ failure: &RbFailure,
785
+ ) -> Result<u32, Error> {
786
+ self.vm
787
+ .borrow_mut()
788
+ .sys_complete_promise(
789
+ key,
790
+ NonEmptyValue::Failure(failure.clone().into()),
791
+ Default::default(),
792
+ )
793
+ .map(Into::into)
794
+ .map_err(core_error_to_magnus)
795
+ }
796
+
797
+ // ── Cancel invocation ──
798
+
799
+ fn sys_cancel_invocation(&self, target_invocation_id: String) -> Result<(), Error> {
800
+ self.vm
801
+ .borrow_mut()
802
+ .sys_cancel_invocation(target_invocation_id)
803
+ .map_err(core_error_to_magnus)
804
+ }
805
+ }
806
+
807
+ // ── Identity Verifier ──
808
+
809
+ #[magnus::wrap(class = "Restate::Internal::IdentityVerifier")]
810
+ struct RbIdentityVerifier {
811
+ verifier: IdentityVerifier,
812
+ }
813
+
814
+ impl RbIdentityVerifier {
815
+ fn new(keys: RArray) -> Result<Self, Error> {
816
+ let key_strings: Vec<String> = keys.to_vec()?;
817
+ let key_refs: Vec<&str> = key_strings.iter().map(|s| s.as_str()).collect();
818
+ let verifier = IdentityVerifier::new(&key_refs)
819
+ .map_err(|e| Error::new(identity_key_error_class(), e.to_string()))?;
820
+ Ok(Self { verifier })
821
+ }
822
+
823
+ fn verify(&self, headers: RArray, path: String) -> Result<(), Error> {
824
+ let mut hdr_vec: Vec<(String, String)> = Vec::new();
825
+ for item in headers.into_iter() {
826
+ let pair = RArray::try_convert(item)?;
827
+ let k: String = pair.entry(0)?;
828
+ let v: String = pair.entry(1)?;
829
+ hdr_vec.push((k, v));
830
+ }
831
+ self.verifier
832
+ .verify_identity(&hdr_vec, &path)
833
+ .map_err(|e| Error::new(identity_verification_error_class(), e.to_string()))
834
+ }
835
+ }
836
+
837
+ // ── Error formatter ──
838
+
839
+ use restate_sdk_shared_core::fmt::{set_error_formatter, ErrorFormatter};
840
+
841
+ #[derive(Debug)]
842
+ struct RubyErrorFormatter;
843
+
844
+ impl ErrorFormatter for RubyErrorFormatter {
845
+ fn display_closed_error(&self, f: &mut fmt::Formatter<'_>, event: &str) -> fmt::Result {
846
+ write!(f, "Execution is suspended, but the handler is still attempting to make progress (calling '{event}'). This can happen:
847
+
848
+ * If you use a bare rescue that catches all exceptions.
849
+ Don't do:
850
+ begin
851
+ # Code
852
+ rescue => e
853
+ # This catches all exceptions, including internal Restate exceptions!
854
+ # '{event}' <- This operation prints this exception
855
+ end
856
+
857
+ Do instead:
858
+ begin
859
+ # Code
860
+ rescue Restate::TerminalError => e
861
+ # In Restate handlers you typically want to catch TerminalError only
862
+ end
863
+
864
+ Or remove the begin/rescue altogether if you don't need it.
865
+
866
+ * If you use the context after the handler completed, e.g. passing the context to another thread.
867
+ Check https://docs.restate.dev/develop/ruby/error-handling for more details.")
868
+ }
869
+ }
870
+
871
+ // ── Helpers ──
872
+
873
+ fn parse_headers_array(ary: RArray) -> Result<Vec<Header>, Error> {
874
+ let mut result = Vec::new();
875
+ for item in ary.into_iter() {
876
+ let pair = RArray::try_convert(item)?;
877
+ let k: String = pair.entry(0)?;
878
+ let v: String = pair.entry(1)?;
879
+ result.push(Header {
880
+ key: k.into(),
881
+ value: v.into(),
882
+ });
883
+ }
884
+ Ok(result)
885
+ }
886
+
887
+ // Constructor functions (free functions for use with function! macro)
888
+
889
+ fn rb_failure_new(code: u16, message: String, stacktrace: Value) -> RbFailure {
890
+ let st = if stacktrace.is_nil() {
891
+ None
892
+ } else {
893
+ Some(String::try_convert(stacktrace).unwrap_or_default())
894
+ };
895
+ RbFailure {
896
+ code,
897
+ message,
898
+ stacktrace: st,
899
+ }
900
+ }
901
+
902
+ fn rb_exponential_retry_config_new(
903
+ initial_interval: Value,
904
+ max_attempts: Value,
905
+ max_duration: Value,
906
+ max_interval: Value,
907
+ factor: Value,
908
+ ) -> RbExponentialRetryConfig {
909
+ let to_opt_u64 = |v: Value| -> Option<u64> {
910
+ if v.is_nil() { None } else { u64::try_convert(v).ok() }
911
+ };
912
+ let to_opt_u32 = |v: Value| -> Option<u32> {
913
+ if v.is_nil() { None } else { u32::try_convert(v).ok() }
914
+ };
915
+ let to_opt_f64 = |v: Value| -> Option<f64> {
916
+ if v.is_nil() { None } else { f64::try_convert(v).ok() }
917
+ };
918
+ RbExponentialRetryConfig {
919
+ initial_interval: to_opt_u64(initial_interval),
920
+ max_attempts: to_opt_u32(max_attempts),
921
+ max_duration: to_opt_u64(max_duration),
922
+ max_interval: to_opt_u64(max_interval),
923
+ factor: to_opt_f64(factor),
924
+ }
925
+ }
926
+
927
+ // ── Module init ──
928
+
929
+ #[magnus::init(name = "restate_internal")]
930
+ fn init(ruby: &Ruby) -> Result<(), Error> {
931
+ use tracing_subscriber::EnvFilter;
932
+
933
+ let _ = tracing_subscriber::fmt()
934
+ .with_env_filter(EnvFilter::from_env("RESTATE_CORE_LOG"))
935
+ .try_init();
936
+
937
+ let restate = ruby.define_module("Restate")?;
938
+ let internal = restate.define_module("Internal")?;
939
+
940
+ // Initialize exception classes
941
+ let vm_err = internal.define_error("VMError", ruby.exception_runtime_error())?;
942
+ let _ = VM_ERROR_CLASS.set(SyncExceptionClass(vm_err));
943
+
944
+ let ik_err = internal.define_error("IdentityKeyError", ruby.exception_runtime_error())?;
945
+ let _ = IDENTITY_KEY_ERROR_CLASS.set(SyncExceptionClass(ik_err));
946
+
947
+ let iv_err =
948
+ internal.define_error("IdentityVerificationError", ruby.exception_runtime_error())?;
949
+ let _ = IDENTITY_VERIFICATION_ERROR_CLASS.set(SyncExceptionClass(iv_err));
950
+
951
+ // Header
952
+ let header_class = internal.define_class("Header", ruby.class_object())?;
953
+ header_class.define_singleton_method("new", function!(RbHeader::new, 2))?;
954
+ header_class.define_method("key", method!(RbHeader::key, 0))?;
955
+ header_class.define_method("value", method!(RbHeader::value, 0))?;
956
+
957
+ // ResponseHead
958
+ let rh_class = internal.define_class("ResponseHead", ruby.class_object())?;
959
+ rh_class.define_method("status_code", method!(RbResponseHead::status_code, 0))?;
960
+ rh_class.define_method("headers", method!(RbResponseHead::headers_array, 0))?;
961
+
962
+ // Failure - constructor takes (code, message, stacktrace_or_nil)
963
+ let failure_class = internal.define_class("Failure", ruby.class_object())?;
964
+ failure_class.define_singleton_method("new", function!(rb_failure_new, 3))?;
965
+ failure_class.define_method("code", method!(RbFailure::code, 0))?;
966
+ failure_class.define_method("message", method!(RbFailure::message, 0))?;
967
+ failure_class.define_method("stacktrace", method!(RbFailure::stacktrace, 0))?;
968
+
969
+ // Void, Suspended, StateKeys
970
+ internal.define_class("Void", ruby.class_object())?;
971
+ internal.define_class("Suspended", ruby.class_object())?;
972
+ let sk_class = internal.define_class("StateKeys", ruby.class_object())?;
973
+ sk_class.define_method("keys", method!(RbStateKeys::keys_array, 0))?;
974
+
975
+ // Input
976
+ let input_class = internal.define_class("Input", ruby.class_object())?;
977
+ input_class.define_method("invocation_id", method!(RbInput::invocation_id, 0))?;
978
+ input_class.define_method("random_seed", method!(RbInput::random_seed, 0))?;
979
+ input_class.define_method("key", method!(RbInput::key, 0))?;
980
+ input_class.define_method("headers", method!(RbInput::headers_array, 0))?;
981
+ input_class.define_method("input", method!(RbInput::input_bytes, 0))?;
982
+
983
+ // ExponentialRetryConfig - constructor takes all 5 params (nil for unset)
984
+ let erc_class = internal.define_class("ExponentialRetryConfig", ruby.class_object())?;
985
+ erc_class
986
+ .define_singleton_method("new", function!(rb_exponential_retry_config_new, 5))?;
987
+ erc_class
988
+ .define_method("initial_interval", method!(RbExponentialRetryConfig::initial_interval, 0))?;
989
+ erc_class.define_method("max_attempts", method!(RbExponentialRetryConfig::max_attempts, 0))?;
990
+ erc_class.define_method("max_duration", method!(RbExponentialRetryConfig::max_duration, 0))?;
991
+ erc_class.define_method("max_interval", method!(RbExponentialRetryConfig::max_interval, 0))?;
992
+ erc_class.define_method("factor", method!(RbExponentialRetryConfig::factor, 0))?;
993
+
994
+ // Progress types
995
+ internal.define_class("DoProgressAnyCompleted", ruby.class_object())?;
996
+ internal.define_class("DoProgressReadFromInput", ruby.class_object())?;
997
+ let exec_run_class = internal.define_class("DoProgressExecuteRun", ruby.class_object())?;
998
+ exec_run_class.define_method("handle", method!(RbDoProgressExecuteRun::handle, 0))?;
999
+ internal.define_class("DoProgressCancelSignalReceived", ruby.class_object())?;
1000
+ internal.define_class("DoWaitForPendingRun", ruby.class_object())?;
1001
+
1002
+ // CallHandle
1003
+ let ch_class = internal.define_class("CallHandle", ruby.class_object())?;
1004
+ ch_class.define_method(
1005
+ "invocation_id_handle",
1006
+ method!(RbCallHandle::invocation_id_handle, 0),
1007
+ )?;
1008
+ ch_class.define_method("result_handle", method!(RbCallHandle::result_handle, 0))?;
1009
+
1010
+ // VM - all methods use explicit arities
1011
+ let vm_class = internal.define_class("VM", ruby.class_object())?;
1012
+ vm_class.define_singleton_method("new", function!(RbVM::new, 1))?;
1013
+ vm_class.define_method("get_response_head", method!(RbVM::get_response_head, 0))?;
1014
+ vm_class.define_method("notify_input", method!(RbVM::notify_input, 1))?;
1015
+ vm_class.define_method("notify_input_closed", method!(RbVM::notify_input_closed, 0))?;
1016
+ vm_class.define_method("notify_error", method!(RbVM::notify_error, 2))?;
1017
+ vm_class.define_method("take_output", method!(RbVM::take_output, 0))?;
1018
+ vm_class.define_method("is_ready_to_execute", method!(RbVM::is_ready_to_execute, 0))?;
1019
+ vm_class.define_method("is_completed", method!(RbVM::is_completed, 1))?;
1020
+ vm_class.define_method("do_progress", method!(RbVM::do_progress, 1))?;
1021
+ vm_class.define_method("take_notification", method!(RbVM::take_notification, 1))?;
1022
+ vm_class.define_method("sys_input", method!(RbVM::sys_input, 0))?;
1023
+ vm_class.define_method("sys_get_state", method!(RbVM::sys_get_state, 1))?;
1024
+ vm_class.define_method("sys_get_state_keys", method!(RbVM::sys_get_state_keys, 0))?;
1025
+ vm_class.define_method("sys_set_state", method!(RbVM::sys_set_state, 2))?;
1026
+ vm_class.define_method("sys_clear_state", method!(RbVM::sys_clear_state, 1))?;
1027
+ vm_class.define_method("sys_clear_all_state", method!(RbVM::sys_clear_all_state, 0))?;
1028
+ vm_class.define_method("sys_sleep", method!(RbVM::sys_sleep, 2))?;
1029
+ vm_class.define_method("sys_call", method!(RbVM::sys_call, 6))?;
1030
+ vm_class.define_method("sys_send", method!(RbVM::sys_send, 7))?;
1031
+ vm_class.define_method("sys_run", method!(RbVM::sys_run, 1))?;
1032
+ vm_class.define_method(
1033
+ "propose_run_completion_success",
1034
+ method!(RbVM::propose_run_completion_success, 2),
1035
+ )?;
1036
+ vm_class.define_method(
1037
+ "propose_run_completion_failure",
1038
+ method!(RbVM::propose_run_completion_failure, 2),
1039
+ )?;
1040
+ vm_class.define_method(
1041
+ "propose_run_completion_failure_transient",
1042
+ method!(RbVM::propose_run_completion_failure_transient, 4),
1043
+ )?;
1044
+ vm_class.define_method(
1045
+ "sys_write_output_success",
1046
+ method!(RbVM::sys_write_output_success, 1),
1047
+ )?;
1048
+ vm_class.define_method(
1049
+ "sys_write_output_failure",
1050
+ method!(RbVM::sys_write_output_failure, 1),
1051
+ )?;
1052
+ vm_class.define_method("sys_end", method!(RbVM::sys_end, 0))?;
1053
+ vm_class.define_method("is_replaying", method!(RbVM::is_replaying, 0))?;
1054
+ vm_class.define_method("sys_awakeable", method!(RbVM::sys_awakeable, 0))?;
1055
+ vm_class.define_method(
1056
+ "sys_complete_awakeable_success",
1057
+ method!(RbVM::sys_complete_awakeable_success, 2),
1058
+ )?;
1059
+ vm_class.define_method(
1060
+ "sys_complete_awakeable_failure",
1061
+ method!(RbVM::sys_complete_awakeable_failure, 2),
1062
+ )?;
1063
+ vm_class.define_method("sys_get_promise", method!(RbVM::sys_get_promise, 1))?;
1064
+ vm_class.define_method("sys_peek_promise", method!(RbVM::sys_peek_promise, 1))?;
1065
+ vm_class.define_method(
1066
+ "sys_complete_promise_success",
1067
+ method!(RbVM::sys_complete_promise_success, 2),
1068
+ )?;
1069
+ vm_class.define_method(
1070
+ "sys_complete_promise_failure",
1071
+ method!(RbVM::sys_complete_promise_failure, 2),
1072
+ )?;
1073
+ vm_class.define_method(
1074
+ "sys_cancel_invocation",
1075
+ method!(RbVM::sys_cancel_invocation, 1),
1076
+ )?;
1077
+
1078
+ // IdentityVerifier
1079
+ let iv_class = internal.define_class("IdentityVerifier", ruby.class_object())?;
1080
+ iv_class.define_singleton_method("new", function!(RbIdentityVerifier::new, 1))?;
1081
+ iv_class.define_method("verify", method!(RbIdentityVerifier::verify, 2))?;
1082
+
1083
+ // Constants
1084
+ internal.const_set("SDK_VERSION", CURRENT_VERSION)?;
1085
+ internal.const_set(
1086
+ "CANCEL_NOTIFICATION_HANDLE",
1087
+ u32::from(CANCEL_NOTIFICATION_HANDLE),
1088
+ )?;
1089
+
1090
+ // Set customized error formatter
1091
+ set_error_formatter(RubyErrorFormatter);
1092
+
1093
+ Ok(())
1094
+ }