Skip to content

Commit d3d1ee9

Browse files
Port to shared-core 7.0.1 and advertise service protocol v7
Bumps `restate-sdk-shared-core` from 0.10.0 to 7.0.1 and advertises `maxProtocolVersion = 7` in discovery (min stays 5, matching shared-core's `minimum_supported_version()..maximum_supported_version()` = V5..V7). Adapts to the shared-core 7.0.1 API: - `PayloadOptions::stable()` -> `stable_serialization()` - `take_output()` now returns `Bytes` (the `TakeOutputResult` enum is gone), so the EOF/`UnexpectedOutputClosed` path is removed - `do_progress(Vec<NotificationHandle>) -> DoProgressResponse` -> `do_await(UnresolvedFuture) -> AwaitResponse` (`Single` for single awaits, `FirstCompleted` for select) - `sys_run` returns `RunHandle`, `sys_awakeable` returns `AwakeableHandle` - `is_replaying()` -> `state().is_replaying()` - `Target` gains `scope`/`limit_key`; `RetryPolicy` gains `on_max_attempts` Also flush the output buffer when `do_await` returns `WaitingExternalProgress`: on protocol v7 shared-core buffers an `AwaitingOnMessage` describing what the invocation is suspending on, which must reach the runtime before we block on input. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent f52d297 commit d3d1ee9

5 files changed

Lines changed: 101 additions & 85 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pin-project-lite = "0.2"
4040
rand = { version = "0.10", optional = true }
4141
regress = "0.10"
4242
restate-sdk-macros = { version = "0.10", path = "macros" }
43-
restate-sdk-shared-core = { version = "=0.10.0", features = ["request_identity", "sha2_random_seed", "http"] }
43+
restate-sdk-shared-core = { version = "=7.0.1", features = ["request_identity", "sha2_random_seed", "http"] }
4444
schemars = { version = "1.2", optional = true }
4545
serde = "1.0"
4646
serde_json = "1.0"

src/endpoint/context.rs

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ use futures::future::{BoxFuture, Either, Shared};
1515
use futures::{FutureExt, TryFutureExt};
1616
use pin_project_lite::pin_project;
1717
use restate_sdk_shared_core::{
18-
CoreVM, DoProgressResponse, Error as CoreError, Header, NonEmptyValue, NotificationHandle,
19-
PayloadOptions, RetryPolicy, RunExitResult, TakeOutputResult, Target, TerminalFailure, VM,
20-
Value,
18+
AwaitResponse, AwakeableHandle, CoreVM, Error as CoreError, Header, NonEmptyValue,
19+
NotificationHandle, OnMaxAttempts, PayloadOptions, RetryPolicy, RunExitResult, RunHandle,
20+
Target, TerminalFailure, UnresolvedFuture, VM, Value,
2121
};
2222
use std::borrow::Cow;
2323
use std::collections::HashMap;
@@ -67,10 +67,10 @@ impl ContextInternalInner {
6767
}
6868

6969
pub(super) fn maybe_flip_span_replaying_field(&mut self) {
70-
if !self.span_replaying_field_state && self.vm.is_replaying() {
70+
if !self.span_replaying_field_state && self.vm.state().is_replaying() {
7171
tracing::Span::current().record("restate.sdk.is_replaying", true);
7272
self.span_replaying_field_state = true;
73-
} else if self.span_replaying_field_state && !self.vm.is_replaying() {
73+
} else if self.span_replaying_field_state && !self.vm.state().is_replaying() {
7474
tracing::Span::current().record("restate.sdk.is_replaying", false);
7575
self.span_replaying_field_state = false;
7676
}
@@ -131,20 +131,26 @@ impl From<RequestTarget> for Target {
131131
handler,
132132
key: None,
133133
idempotency_key: None,
134+
scope: None,
135+
limit_key: None,
134136
headers: vec![],
135137
},
136138
RequestTarget::Object { name, key, handler } => Target {
137139
service: name,
138140
handler,
139141
key: Some(key),
140142
idempotency_key: None,
143+
scope: None,
144+
limit_key: None,
141145
headers: vec![],
142146
},
143147
RequestTarget::Workflow { name, key, handler } => Target {
144148
service: name,
145149
handler,
146150
key: Some(key),
147151
idempotency_key: None,
152+
scope: None,
153+
limit_key: None,
148154
headers: vec![],
149155
},
150156
}
@@ -236,9 +242,10 @@ impl ContextInternal {
236242
syscall: "input",
237243
err: err.0.clone().into(),
238244
};
239-
let _ = inner_lock
240-
.vm
241-
.sys_write_output(NonEmptyValue::Failure(err.into()), PayloadOptions::stable());
245+
let _ = inner_lock.vm.sys_write_output(
246+
NonEmptyValue::Failure(err.into()),
247+
PayloadOptions::stable_serialization(),
248+
);
242249
let _ = inner_lock.vm.sys_end();
243250
// This causes the trap, plus logs the error
244251
inner_lock.handler_state.mark_error(error_inner.into());
@@ -261,7 +268,7 @@ impl ContextInternal {
261268
inner_lock,
262269
inner_lock
263270
.vm
264-
.sys_state_get(key.to_owned(), PayloadOptions::stable())
271+
.sys_state_get(key.to_owned(), PayloadOptions::stable_serialization())
265272
);
266273
inner_lock.maybe_flip_span_replaying_field();
267274

@@ -307,9 +314,11 @@ impl ContextInternal {
307314
let mut inner_lock = must_lock!(self.inner);
308315
match t.serialize() {
309316
Ok(b) => {
310-
let _ = inner_lock
311-
.vm
312-
.sys_state_set(key.to_owned(), b, PayloadOptions::stable());
317+
let _ = inner_lock.vm.sys_state_set(
318+
key.to_owned(),
319+
b,
320+
PayloadOptions::stable_serialization(),
321+
);
313322
inner_lock.maybe_flip_span_replaying_field();
314323
}
315324
Err(e) => {
@@ -402,7 +411,7 @@ impl ContextInternal {
402411
.and_then(|input| {
403412
inner_lock
404413
.vm
405-
.sys_call(target, input, None, PayloadOptions::stable())
414+
.sys_call(target, input, None, PayloadOptions::stable_serialization())
406415
.map_err(Into::into)
407416
});
408417

@@ -501,7 +510,7 @@ impl ContextInternal {
501510
+ delay
502511
}),
503512
None,
504-
PayloadOptions::stable(),
513+
PayloadOptions::stable_serialization(),
505514
) {
506515
Ok(h) => h,
507516
Err(e) => {
@@ -554,7 +563,7 @@ impl ContextInternal {
554563
inner_lock.maybe_flip_span_replaying_field();
555564

556565
let (awakeable_id, handle) = match maybe_awakeable_id_and_handle {
557-
Ok((s, handle)) => (s, handle),
566+
Ok(AwakeableHandle { id, handle }) => (id, handle),
558567
Err(e) => {
559568
inner_lock.fail(e.into());
560569
return (
@@ -597,7 +606,7 @@ impl ContextInternal {
597606
let _ = inner_lock.vm.sys_complete_awakeable(
598607
id.to_owned(),
599608
NonEmptyValue::Success(b),
600-
PayloadOptions::stable(),
609+
PayloadOptions::stable_serialization(),
601610
);
602611
}
603612
Err(e) => {
@@ -610,7 +619,7 @@ impl ContextInternal {
610619
let _ = must_lock!(self.inner).vm.sys_complete_awakeable(
611620
id.to_owned(),
612621
NonEmptyValue::Failure(failure.into()),
613-
PayloadOptions::stable(),
622+
PayloadOptions::stable_serialization(),
614623
);
615624
}
616625

@@ -679,7 +688,7 @@ impl ContextInternal {
679688
let _ = inner_lock.vm.sys_complete_promise(
680689
name.to_owned(),
681690
NonEmptyValue::Success(b),
682-
PayloadOptions::stable(),
691+
PayloadOptions::stable_serialization(),
683692
);
684693
}
685694
Err(e) => {
@@ -698,7 +707,7 @@ impl ContextInternal {
698707
let _ = must_lock!(self.inner).vm.sys_complete_promise(
699708
id.to_owned(),
700709
NonEmptyValue::Failure(failure.into()),
701-
PayloadOptions::stable(),
710+
PayloadOptions::stable_serialization(),
702711
);
703712
}
704713

@@ -744,7 +753,7 @@ impl ContextInternal {
744753

745754
let _ = inner_lock
746755
.vm
747-
.sys_write_output(res_to_write, PayloadOptions::stable());
756+
.sys_write_output(res_to_write, PayloadOptions::stable_serialization());
748757
inner_lock.maybe_flip_span_replaying_field();
749758
}
750759

@@ -755,11 +764,8 @@ impl ContextInternal {
755764
pub(crate) fn consume_to_end(&self) {
756765
let mut inner_lock = must_lock!(self.inner);
757766

758-
let out = inner_lock.vm.take_output();
759-
if let TakeOutputResult::Buffer(b) = out
760-
&& !b.is_empty()
761-
&& !inner_lock.write.send(b)
762-
{
767+
let b = inner_lock.vm.take_output();
768+
if !b.is_empty() && !inner_lock.write.send(b) {
763769
// Nothing we can do anymore here
764770
}
765771
}
@@ -879,6 +885,7 @@ where
879885
max_interval: retry_policy.max_delay,
880886
max_attempts: retry_policy.max_attempts,
881887
max_duration: retry_policy.max_duration,
888+
on_max_attempts: OnMaxAttempts::FailAsTerminal,
882889
};
883890
self
884891
}
@@ -911,14 +918,14 @@ where
911918
.expect("Future should not be polled after returning Poll::Ready");
912919
let mut inner_ctx = must_lock!(ctx);
913920

914-
let handle = inner_ctx
921+
let RunHandle { handle, .. } = inner_ctx
915922
.vm
916923
.sys_run(this.name.to_owned())
917924
.map_err(ErrorInner::from)?;
918925

919926
// Now we do progress once to check whether this closure should be executed or not.
920-
match inner_ctx.vm.do_progress(vec![handle]) {
921-
Ok(DoProgressResponse::ExecuteRun(handle_to_run)) => {
927+
match inner_ctx.vm.do_await(UnresolvedFuture::Single(handle)) {
928+
Ok(AwaitResponse::ExecuteRun(handle_to_run)) => {
922929
// In case it returns ExecuteRun, it must be the handle we just gave it,
923930
// and it means we need to execute the closure
924931
assert_eq!(handle, handle_to_run);
@@ -931,7 +938,7 @@ where
931938
closure_fut: closure.run(),
932939
});
933940
}
934-
Ok(DoProgressResponse::CancelSignalReceived) => {
941+
Ok(AwaitResponse::CancelSignalReceived) => {
935942
drop(inner_ctx);
936943
// Got cancellation!
937944
this.state.set(RunState::WaitingResultFut {

src/endpoint/futures/async_result_poll.rs

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::endpoint::ErrorInner;
22
use crate::endpoint::context::ContextInternalInner;
33
use restate_sdk_shared_core::{
4-
DoProgressResponse, Error as CoreError, NotificationHandle, TakeOutputResult, TerminalFailure,
5-
VM, Value,
4+
AwaitResponse, Error as CoreError, NotificationHandle, TerminalFailure, UnresolvedFuture, VM,
5+
Value,
66
};
77
use std::future::Future;
88
use std::pin::Pin;
@@ -55,21 +55,14 @@ impl Future for VmAsyncResultPollFuture {
5555
AsyncResultPollState::Init { ctx, handle } => {
5656
let mut inner_lock = must_lock!(ctx);
5757

58-
// Let's consume some output to begin with
59-
let out = inner_lock.vm.take_output();
60-
match out {
61-
TakeOutputResult::Buffer(b) => {
62-
// Skip empty buffers: take_output returns an empty buffer when there's
63-
// nothing to send (e.g. while replaying completed entries). Sending it
64-
// would emit an empty HTTP/2 DATA frame per replayed await, which some
65-
// proxies (e.g. Envoy) reject when consecutive. See sdk-rust#114.
66-
if !b.is_empty() && !inner_lock.write.send(b) {
67-
return Poll::Ready(Err(ErrorInner::Suspended));
68-
}
69-
}
70-
TakeOutputResult::EOF => {
71-
return Poll::Ready(Err(ErrorInner::UnexpectedOutputClosed));
72-
}
58+
// Let's consume some output to begin with.
59+
// Skip empty buffers: take_output returns an empty buffer when there's
60+
// nothing to send (e.g. while replaying completed entries). Sending it
61+
// would emit an empty HTTP/2 DATA frame per replayed await, which some
62+
// proxies (e.g. Envoy) reject when consecutive. See sdk-rust#114.
63+
let b = inner_lock.vm.take_output();
64+
if !b.is_empty() && !inner_lock.write.send(b) {
65+
return Poll::Ready(Err(ErrorInner::Suspended));
7366
}
7467

7568
// We can now start polling
@@ -106,22 +99,32 @@ impl Future for VmAsyncResultPollFuture {
10699
AsyncResultPollState::PollProgress { ctx, handle } => {
107100
let mut inner_lock = must_lock!(ctx);
108101

109-
match inner_lock.vm.do_progress(vec![handle]) {
110-
Ok(DoProgressResponse::AnyCompleted) => {
102+
match inner_lock.vm.do_await(UnresolvedFuture::Single(handle)) {
103+
Ok(AwaitResponse::AnyCompleted) => {
111104
// We're good, we got the response
112105
}
113-
Ok(DoProgressResponse::ReadFromInput) => {
106+
Ok(AwaitResponse::WaitingExternalProgress {
107+
waiting_input: true,
108+
..
109+
}) => {
110+
// do_await buffered an AwaitingOnMessage describing what we're
111+
// suspending on; flush it to the runtime before we block on input,
112+
// otherwise the runtime never learns what we're waiting for.
113+
let b = inner_lock.vm.take_output();
114+
if !b.is_empty() && !inner_lock.write.send(b) {
115+
return Poll::Ready(Err(ErrorInner::Suspended));
116+
}
114117
drop(inner_lock);
115118
self.state = Some(AsyncResultPollState::WaitingInput { ctx, handle });
116119
continue;
117120
}
118-
Ok(DoProgressResponse::ExecuteRun(_)) => {
119-
unimplemented!()
120-
}
121-
Ok(DoProgressResponse::WaitingPendingRun) => {
121+
Ok(AwaitResponse::WaitingExternalProgress { .. })
122+
| Ok(AwaitResponse::ExecuteRun(_)) => {
123+
// Waiting only on a run proposal is not expected on this await path.
124+
// These two are used by the shared-core to implement async run, which is not supported by the rust sdk currently.
122125
unimplemented!()
123126
}
124-
Ok(DoProgressResponse::CancelSignalReceived) => {
127+
Ok(AwaitResponse::CancelSignalReceived) => {
125128
return Poll::Ready(Ok(Value::Failure(TerminalFailure {
126129
code: 409,
127130
message: "cancelled".to_string(),

src/endpoint/futures/select_poll.rs

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ use crate::endpoint::ErrorInner;
22
use crate::endpoint::context::ContextInternalInner;
33
use crate::errors::TerminalError;
44
use restate_sdk_shared_core::{
5-
DoProgressResponse, Error as CoreError, NotificationHandle, TakeOutputResult, TerminalFailure,
6-
VM,
5+
AwaitResponse, Error as CoreError, NotificationHandle, TerminalFailure, UnresolvedFuture, VM,
76
};
87
use std::future::Future;
98
use std::pin::Pin;
@@ -56,21 +55,14 @@ impl Future for VmSelectAsyncResultPollFuture {
5655
VmSelectAsyncResultPollState::Init { ctx, handles } => {
5756
let mut inner_lock = must_lock!(ctx);
5857

59-
// Let's consume some output to begin with
60-
let out = inner_lock.vm.take_output();
61-
match out {
62-
TakeOutputResult::Buffer(b) => {
63-
// Skip empty buffers: take_output returns an empty buffer when there's
64-
// nothing to send (e.g. while replaying completed entries). Sending it
65-
// would emit an empty HTTP/2 DATA frame per replayed await, which some
66-
// proxies (e.g. Envoy) reject when consecutive. See sdk-rust#114.
67-
if !b.is_empty() && !inner_lock.write.send(b) {
68-
return Poll::Ready(Err(ErrorInner::Suspended));
69-
}
70-
}
71-
TakeOutputResult::EOF => {
72-
return Poll::Ready(Err(ErrorInner::UnexpectedOutputClosed));
73-
}
58+
// Let's consume some output to begin with.
59+
// Skip empty buffers: take_output returns an empty buffer when there's
60+
// nothing to send (e.g. while replaying completed entries). Sending it
61+
// would emit an empty HTTP/2 DATA frame per replayed await, which some
62+
// proxies (e.g. Envoy) reject when consecutive. See sdk-rust#114.
63+
let b = inner_lock.vm.take_output();
64+
if !b.is_empty() && !inner_lock.write.send(b) {
65+
return Poll::Ready(Err(ErrorInner::Suspended));
7466
}
7567

7668
// We can now start polling
@@ -108,23 +100,40 @@ impl Future for VmSelectAsyncResultPollFuture {
108100
VmSelectAsyncResultPollState::PollProgress { ctx, handles } => {
109101
let mut inner_lock = must_lock!(ctx);
110102

111-
match inner_lock.vm.do_progress(handles.clone()) {
112-
Ok(DoProgressResponse::AnyCompleted) => {
103+
let unresolved_future = UnresolvedFuture::FirstCompleted(
104+
handles
105+
.iter()
106+
.copied()
107+
.map(UnresolvedFuture::Single)
108+
.collect(),
109+
);
110+
match inner_lock.vm.do_await(unresolved_future) {
111+
Ok(AwaitResponse::AnyCompleted) => {
113112
// We're good, we got the response
114113
}
115-
Ok(DoProgressResponse::ReadFromInput) => {
114+
Ok(AwaitResponse::WaitingExternalProgress {
115+
waiting_input: true,
116+
..
117+
}) => {
118+
// do_await buffered an AwaitingOnMessage describing what we're
119+
// suspending on; flush it to the runtime before we block on input,
120+
// otherwise the runtime never learns what we're waiting for.
121+
let b = inner_lock.vm.take_output();
122+
if !b.is_empty() && !inner_lock.write.send(b) {
123+
return Poll::Ready(Err(ErrorInner::Suspended));
124+
}
116125
drop(inner_lock);
117126
self.state =
118127
Some(VmSelectAsyncResultPollState::WaitingInput { ctx, handles });
119128
continue;
120129
}
121-
Ok(DoProgressResponse::ExecuteRun(_)) => {
122-
unimplemented!()
123-
}
124-
Ok(DoProgressResponse::WaitingPendingRun) => {
130+
Ok(AwaitResponse::WaitingExternalProgress { .. })
131+
| Ok(AwaitResponse::ExecuteRun(_)) => {
132+
// Waiting only on a run proposal is not expected on this await path.
133+
// These two are used by the shared-core to implement async run, which is not supported by the rust sdk currently.
125134
unimplemented!()
126135
}
127-
Ok(DoProgressResponse::CancelSignalReceived) => {
136+
Ok(AwaitResponse::CancelSignalReceived) => {
128137
return Poll::Ready(Ok(Err(TerminalFailure {
129138
code: 409,
130139
message: "cancelled".to_string(),

0 commit comments

Comments
 (0)