1#[cfg(feature = "experimental")]
2mod nexus;
3mod options;
4mod view;
5
6#[cfg(feature = "experimental")]
7pub(crate) use nexus::NexusUnblockData;
8#[cfg(feature = "experimental")]
9pub use nexus::StartedNexusOperation;
10pub use options::{
11 ActivityCancellationType, ActivityOptions, ChildWorkflowCancellationType, ChildWorkflowOptions,
12 ContinueAsNewOptions, LocalActivityOptions, ParentClosePolicy, SignalWorkflowOptions,
13 TimerOptions, VersioningIntent, WaitConditionOptions, WorkflowIdReusePolicy,
14};
15#[cfg(feature = "experimental")]
16pub use options::{
17 ContinueAsNewVersioningBehavior, NexusOperationCancellationType, NexusOperationOptions,
18};
19pub use temporalio_common_wasm::error::StartChildWorkflowExecutionFailedCause;
20pub use view::{NamespacedWorkflowInfo, WorkflowContextView};
21
22use crate::{
23 MemoValue, WorkflowCancellationError, WorkflowCancellationToken,
24 runtime::{
25 SdkWakeGuard,
26 entry::WorkflowImplementation,
27 host::WorkflowHost,
28 mark_intercepted_future_activation,
29 model::{
30 CancelExternalWfResult, CancellableID, SignalExternalWfResult, TimerResult,
31 UnblockEvent, Unblockable, WorkflowTermination,
32 },
33 types::WorkflowInit,
34 },
35 workflow_interceptors::{
36 CancelExternalWorkflowInput, CancelExternalWorkflowResult,
37 CancellableWorkflowOutboundFuture, ChildWorkflowOutboundResult, ContinueAsNewInput,
38 ScheduleActivityInput, ScheduleLocalActivityInput, SignalWorkflowInput,
39 SignalWorkflowResult, SignalWorkflowTarget, StartChildWorkflowInput,
40 StartChildWorkflowResult, StartTimerInput, WorkflowCancellationHandle, WorkflowInterceptor,
41 WorkflowInterceptorConstructor, WorkflowInterceptorContext, WorkflowNext,
42 WorkflowOutboundFuture, WorkflowOutboundValue, call_cancel_external_workflow,
43 call_continue_as_new, call_schedule_activity, call_schedule_local_activity,
44 call_signal_workflow, call_start_child_workflow, call_start_timer,
45 },
46};
47use futures_channel::oneshot;
48use futures_util::{FutureExt, future::FusedFuture, task::Context};
49use rand::SeedableRng;
50use rand_pcg::Pcg64Mcg;
51use siphasher::sip::SipHasher13;
52use std::{
53 any::{Any, TypeId},
54 cell::{Cell, RefCell},
55 collections::{HashMap, HashSet},
56 fmt,
57 future::{self, Future},
58 hash::Hasher,
59 marker::PhantomData,
60 pin::Pin,
61 rc::Rc,
62 sync::{
63 Arc,
64 atomic::{AtomicBool, Ordering},
65 },
66 task::{Poll, Waker},
67 time::SystemTime,
68};
69use temporalio_common_wasm::{
70 ActivityDefinition, Memo, SignalDefinition, WorkflowDefinition,
71 data_converters::{
72 ActivityExecutionDecodeHint, CancelExternalWorkflowDecodeHint,
73 ChildWorkflowExecutionDecodeHint, ChildWorkflowStartDecodeHint, DataConverter,
74 GenericPayloadConverter, PayloadConversionError, PayloadConverter, SerializationContext,
75 SerializationContextData, TemporalDeserializable, WorkflowSerializationContext,
76 WorkflowSignalDecodeHint,
77 },
78 error::{
79 ActivityExecutionError, ChildWorkflowExecutionError, ChildWorkflowStartError,
80 WorkflowSignalError,
81 },
82 protos::{
83 coresdk::{
84 activity_result::{ActivityResolution, Cancellation, activity_resolution},
85 child_workflow::{
86 ChildWorkflowResult,
87 StartChildWorkflowExecutionFailedCause as ProtoStartChildCause,
88 child_workflow_result,
89 },
90 common::NamespacedWorkflowExecution,
91 workflow_activation::{
92 InitializeWorkflow, WorkflowActivation as CoreWorkflowActivation,
93 resolve_child_workflow_execution_start::Status as ChildWorkflowStartStatus,
94 workflow_activation_job::Variant as ActivationVariant,
95 },
96 workflow_commands::{
97 CancelChildWorkflowExecution, CancelSignalWorkflow, CancelTimer,
98 ModifyWorkflowProperties, RequestCancelActivity,
99 RequestCancelExternalWorkflowExecution, RequestCancelLocalActivity,
100 RequestCancelNexusOperation, SetPatchMarker, UpsertWorkflowSearchAttributes,
101 signal_external_workflow_execution, workflow_command,
102 },
103 },
104 temporal::api::{
105 common::v1::{Memo as ProtoMemo, Payload, SearchAttributes as ProtoSearchAttributes},
106 failure::v1::{CanceledFailureInfo, Failure, failure::FailureInfo},
107 },
108 utilities::TryIntoOrNone,
109 },
110 search_attributes::{SearchAttributeUpdate, SearchAttributes},
111 worker::WorkerDeploymentVersion,
112};
113use uuid::Builder;
114
115mod private {
116 use rand::distr::{Distribution, StandardUniform};
117 use rand_pcg::Pcg64Mcg;
118
119 pub trait Sealed: Sized {
120 fn sample(rng: &mut Pcg64Mcg) -> Self;
121 }
122
123 pub(super) fn sample<T>(rng: &mut Pcg64Mcg) -> T
124 where
125 StandardUniform: Distribution<T>,
126 {
127 StandardUniform.sample(rng)
128 }
129}
130
131pub trait WorkflowRandomValue: private::Sealed + Sized {}
136
137macro_rules! impl_random_value {
138 ($($ty:ty),* $(,)?) => {
139 $(
140 impl private::Sealed for $ty {
141 fn sample(rng: &mut Pcg64Mcg) -> Self {
142 private::sample(rng)
143 }
144 }
145
146 impl WorkflowRandomValue for $ty {}
147 )*
148 };
149}
150
151impl_random_value!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64);
152
153#[derive(Clone)]
170pub struct WorkflowRandomStream {
171 source: WorkflowRandomStreamSource,
172 name: String,
173}
174
175#[derive(Clone)]
176enum WorkflowRandomStreamSource {
177 Workflow(Rc<RefCell<WorkflowRandomState>>),
178 System(Rc<RefCell<Pcg64Mcg>>),
179}
180
181impl WorkflowRandomStream {
182 pub fn random<T>(&self) -> T
186 where
187 T: WorkflowRandomValue,
188 {
189 match &self.source {
190 WorkflowRandomStreamSource::Workflow(random) => {
191 random.borrow_mut().named_random(&self.name)
192 }
193 WorkflowRandomStreamSource::System(random) => {
194 <T as private::Sealed>::sample(&mut random.borrow_mut())
195 }
196 }
197 }
198
199 pub fn name(&self) -> &str {
201 &self.name
202 }
203}
204
205fn system_random_stream_source() -> WorkflowRandomStreamSource {
206 #[cfg(not(target_arch = "wasm32"))]
207 let seed = rand::random();
208 #[cfg(target_arch = "wasm32")]
209 let seed = {
210 let mut hasher = std::hash::BuildHasher::build_hasher(&std::hash::RandomState::new());
215 std::hash::Hasher::write(&mut hasher, b"temporal-rust-system-random-stream");
216 std::hash::Hasher::finish(&hasher)
217 };
218
219 WorkflowRandomStreamSource::System(Rc::new(RefCell::new(Pcg64Mcg::seed_from_u64(seed))))
220}
221
222fn named_random_seed(randomness_seed: u64, name: &str) -> u64 {
223 let second_key = randomness_seed ^ u64::from_be_bytes(*b"temporal");
225 let mut hasher = SipHasher13::new_with_keys(randomness_seed, second_key);
226 hasher.write(b"temporal-rust-workflow-random-stream\0");
227 hasher.write(name.as_bytes());
228 hasher.finish()
229}
230
231#[derive(Clone, Debug)]
232pub(super) struct WorkflowRandomState {
233 random: Pcg64Mcg,
234 randomness_seed: u64,
235 named_random: HashMap<String, Pcg64Mcg>,
236}
237
238impl WorkflowRandomState {
239 fn new(randomness_seed: u64) -> Self {
240 Self {
241 random: Pcg64Mcg::seed_from_u64(randomness_seed),
242 randomness_seed,
243 named_random: HashMap::new(),
244 }
245 }
246
247 fn random<T: WorkflowRandomValue>(&mut self) -> T {
248 <T as private::Sealed>::sample(&mut self.random)
249 }
250
251 fn named_random<T: WorkflowRandomValue>(&mut self, name: &str) -> T {
252 let random = self.named_random.entry(name.to_owned()).or_insert_with(|| {
253 Pcg64Mcg::seed_from_u64(named_random_seed(self.randomness_seed, name))
254 });
255 <T as private::Sealed>::sample(random)
256 }
257
258 fn reseed(&mut self, randomness_seed: u64) {
259 self.random = Pcg64Mcg::seed_from_u64(randomness_seed);
260 self.randomness_seed = randomness_seed;
261 self.named_random.clear();
262 }
263}
264
265#[derive(Clone)]
269pub struct BaseWorkflowContext {
270 inner: Rc<WorkflowContextInner>,
271}
272
273pub trait WorkflowContextKey: 'static {
308 type Value: 'static;
310}
311
312type WorkflowContextValues = Rc<HashMap<TypeId, Rc<dyn Any>>>;
313
314#[derive(Clone, Default)]
315pub(super) struct WorkflowContextValueStore {
316 current: Rc<RefCell<WorkflowContextValues>>,
317}
318
319impl fmt::Debug for WorkflowContextValueStore {
320 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321 f.debug_struct("WorkflowContextValueStore")
322 .finish_non_exhaustive()
323 }
324}
325
326impl WorkflowContextValueStore {
327 pub(super) fn context_value<K: WorkflowContextKey>(&self) -> Option<Rc<K::Value>> {
328 self.current
329 .borrow()
330 .get(&TypeId::of::<K>())
331 .cloned()
332 .and_then(|value| value.downcast().ok())
333 }
334}
335
336#[must_use = "futures do nothing unless polled"]
342pub struct WorkflowContextFuture<F> {
343 base: BaseWorkflowContext,
344 values: WorkflowContextValues,
345 inner: Pin<Box<F>>,
346}
347
348impl<F: Future> Future for WorkflowContextFuture<F> {
349 type Output = F::Output;
350
351 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
352 let this = self.get_mut();
353 let _guard = this.base.install_context_values(this.values.clone());
354 this.inner.as_mut().poll(cx)
355 }
356}
357
358struct WorkflowContextRestoreGuard {
359 base: BaseWorkflowContext,
360 previous: Option<WorkflowContextValues>,
361}
362
363impl Drop for WorkflowContextRestoreGuard {
364 fn drop(&mut self) {
365 self.base
366 .inner
367 .context_values
368 .current
369 .replace(self.previous.take().expect("context is restored once"));
370 }
371}
372
373#[derive(Clone, Debug)]
375#[non_exhaustive]
376pub struct PatchActivationInput {
377 pub workflow_info: WorkflowContextView,
379 pub patch_id: String,
381}
382
383pub type PatchActivationCallback =
385 Arc<dyn Fn(PatchActivationInput) -> bool + Send + Sync + 'static>;
386
387#[doc(hidden)]
389pub struct PatchActivationCaller {
390 callback: PatchActivationCallback,
391 workflow_info: WorkflowContextView,
392}
393
394impl PatchActivationCaller {
395 pub fn new(
397 callback: PatchActivationCallback,
398 namespace: String,
399 task_queue: String,
400 run_id: String,
401 init: InitializeWorkflow,
402 payload_converter: PayloadConverter,
403 ) -> Self {
404 Self {
405 callback,
406 workflow_info: WorkflowContextView::new(
407 namespace,
408 task_queue,
409 run_id,
410 init,
411 payload_converter,
412 false,
413 None,
414 ),
415 }
416 }
417
418 pub fn call(&self, patch_id: String) -> bool {
420 (self.callback)(PatchActivationInput {
421 workflow_info: self.workflow_info.clone(),
422 patch_id,
423 })
424 }
425}
426
427pub(crate) struct WorkflowPollWakerGuard<'a> {
428 current_waker: &'a RefCell<Option<Waker>>,
429 previous: Option<Waker>,
430}
431
432impl Drop for WorkflowPollWakerGuard<'_> {
433 fn drop(&mut self) {
434 self.current_waker.replace(self.previous.take());
435 }
436}
437
438fn outbound_type_error(
439 value: &str,
440) -> temporalio_common_wasm::data_converters::PayloadConversionError {
441 temporalio_common_wasm::data_converters::PayloadConversionError::EncodingError(Box::new(
442 std::io::Error::new(
443 std::io::ErrorKind::InvalidData,
444 format!("workflow interceptor returned the wrong concrete {value} type"),
445 ),
446 ))
447}
448
449impl BaseWorkflowContext {
450 pub(crate) fn apply_activation_context(
451 &self,
452 activation: &CoreWorkflowActivation,
453 is_replaying_history_events: bool,
454 ) {
455 let new_seed = {
456 let mut shared = self.inner.shared.borrow_mut();
457 shared.activation = activation.clone();
458 shared.is_replaying_history_events = is_replaying_history_events;
459 activation.jobs.iter().find_map(|job| match &job.variant {
460 Some(ActivationVariant::UpdateRandomSeed(attrs)) => Some(attrs.randomness_seed),
461 _ => None,
462 })
463 };
464 if let Some(seed) = new_seed {
465 self.inner.random.borrow_mut().reseed(seed);
466 }
467 }
468
469 fn random<T>(&self) -> T
470 where
471 T: WorkflowRandomValue,
472 {
473 self.inner.random.borrow_mut().random()
474 }
475
476 pub(crate) fn random_stream(&self, name: impl Into<String>) -> WorkflowRandomStream {
477 WorkflowRandomStream {
478 source: WorkflowRandomStreamSource::Workflow(self.inner.random.clone()),
479 name: name.into(),
480 }
481 }
482
483 fn uuid4(&self) -> String {
484 Builder::from_random_bytes(self.random::<u128>().to_be_bytes())
485 .into_uuid()
486 .hyphenated()
487 .to_string()
488 }
489
490 pub fn data_converter(&self) -> &DataConverter {
492 &self.inner.data_converter
493 }
494
495 pub fn workflow_id(&self) -> &str {
497 &self.inner.initial_information.workflow_id
498 }
499
500 pub fn run_id(&self) -> &str {
502 &self.inner.run_id
503 }
504
505 pub fn namespace(&self) -> &str {
507 &self.inner.namespace
508 }
509
510 pub fn task_queue(&self) -> &str {
512 &self.inner.task_queue
513 }
514
515 pub fn workflow_type(&self) -> &str {
517 &self.inner.initial_information.workflow_type
518 }
519
520 pub(crate) fn initial_headers(&self) -> HashMap<String, Payload> {
521 self.inner.initial_information.headers.clone()
522 }
523
524 pub fn workflow_time(&self) -> Option<SystemTime> {
526 self.inner
527 .shared
528 .borrow()
529 .activation
530 .timestamp
531 .try_into_or_none()
532 }
533
534 pub fn history_length(&self) -> u32 {
536 self.inner.shared.borrow().activation.history_length
537 }
538
539 pub fn search_attributes(&self) -> SearchAttributes {
541 SearchAttributes::from_proto(&self.inner.shared.borrow().search_attributes)
542 }
543
544 pub fn is_replaying(&self) -> bool {
546 self.inner.shared.borrow().activation.is_replaying
547 }
548
549 pub fn is_replaying_history_events(&self) -> bool {
551 self.inner.shared.borrow().is_replaying_history_events
552 }
553
554 fn requires_replay_safety(&self) -> bool {
555 self.inner.requires_replay_safety.get()
556 }
557
558 pub(crate) fn enter_read_only(&self) -> ReadOnlyGuard {
559 let previous = self.inner.requires_replay_safety.replace(false);
560 ReadOnlyGuard {
561 base: self.clone(),
562 previous,
563 }
564 }
565
566 pub fn payload_converter(&self) -> &PayloadConverter {
568 self.inner.data_converter.payload_converter()
569 }
570
571 pub(crate) fn construction_waker(&self) -> Waker {
572 self.inner
573 .current_waker
574 .borrow()
575 .clone()
576 .unwrap_or_else(|| Waker::noop().clone())
577 }
578
579 pub(crate) fn enter_runtime_poll<'a>(&'a self, waker: &Waker) -> WorkflowPollWakerGuard<'a> {
580 WorkflowPollWakerGuard {
581 previous: self.inner.current_waker.replace(Some(waker.clone())),
582 current_waker: &self.inner.current_waker,
583 }
584 }
585
586 pub(crate) fn notify_patch(&self, patch_id: String) {
587 self.inner
588 .shared
589 .borrow_mut()
590 .notified_patches
591 .insert(patch_id);
592 }
593
594 fn prepare_outbound_future<T>(
595 &self,
596 mut future: WorkflowOutboundFuture<T>,
597 ) -> WorkflowOutboundFuture<T> {
598 let waker = self.construction_waker();
599 let mut cx = Context::from_waker(&waker);
600 future.poll_for_construction(&mut cx);
601 future
602 }
603
604 fn prepare_cancellable_outbound_future<T>(
605 &self,
606 mut future: CancellableWorkflowOutboundFuture<T>,
607 ) -> CancellableWorkflowOutboundFuture<T> {
608 let waker = self.construction_waker();
609 let mut cx = Context::from_waker(&waker);
610 future.poll_for_construction(&mut cx);
611 future
612 }
613
614 pub(crate) fn view(&self) -> WorkflowContextView {
616 let shared = self.inner.shared.borrow();
617 let mut initial_information = self.inner.initial_information.clone();
618 if initial_information.memo.is_some() || !shared.memo.fields.is_empty() {
619 initial_information.memo = Some(shared.memo.clone());
620 }
621 WorkflowContextView::new(
622 self.inner.namespace.clone(),
623 self.inner.task_queue.clone(),
624 self.inner.run_id.clone(),
625 initial_information,
626 self.inner.data_converter.payload_converter().clone(),
627 self.requires_replay_safety(),
628 Some(self.inner.random.clone()),
629 )
630 .with_context_values(self.inner.context_values.clone())
631 }
632}
633
634#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
635enum PendingCommandId {
636 Timer(u32),
637 Activity(u32),
638 ChildWorkflowStart(u32),
639 ChildWorkflowComplete(u32),
640 SignalExternal(u32),
641 CancelExternal(u32),
642 NexusOpStart(u32),
643 NexusOpComplete(u32),
644}
645
646impl PendingCommandId {
647 fn from_unblock_event(event: &UnblockEvent) -> Self {
648 match event {
649 UnblockEvent::Timer(seq, _) => Self::Timer(*seq),
650 UnblockEvent::Activity(seq, _) => Self::Activity(*seq),
651 UnblockEvent::WorkflowStart(seq, _) => Self::ChildWorkflowStart(*seq),
652 UnblockEvent::WorkflowComplete(seq, _) => Self::ChildWorkflowComplete(*seq),
653 UnblockEvent::SignalExternal(seq, _) => Self::SignalExternal(*seq),
654 UnblockEvent::CancelExternal(seq, _) => Self::CancelExternal(*seq),
655 UnblockEvent::NexusOperationStart(seq, _) => Self::NexusOpStart(*seq),
656 UnblockEvent::NexusOperationComplete(seq, _) => Self::NexusOpComplete(*seq),
657 }
658 }
659}
660
661struct WorkflowRuntimeState {
662 host: Rc<dyn WorkflowHost>,
663 pending_unblocks: RefCell<HashMap<PendingCommandId, oneshot::Sender<UnblockEvent>>>,
664 forced_wft_failure: RefCell<Option<Box<dyn std::error::Error + Send + Sync>>>,
665 progress_made: Cell<bool>,
666}
667
668impl WorkflowRuntimeState {
669 fn new(host: Rc<dyn WorkflowHost>) -> Self {
670 Self {
671 host,
672 pending_unblocks: RefCell::new(HashMap::new()),
673 forced_wft_failure: RefCell::new(None),
674 progress_made: Cell::new(false),
675 }
676 }
677
678 fn register_unblocker(&self, id: PendingCommandId, unblocker: oneshot::Sender<UnblockEvent>) {
679 self.pending_unblocks.borrow_mut().insert(id, unblocker);
680 }
681
682 fn unblock(&self, event: UnblockEvent) -> Result<(), anyhow::Error> {
683 let id = PendingCommandId::from_unblock_event(&event);
684 let unblocker = self
685 .pending_unblocks
686 .borrow_mut()
687 .remove(&id)
688 .ok_or_else(|| anyhow::anyhow!("Command {id:?} not found to unblock"))?;
689 self.progress_made.set(true);
690 let _guard = SdkWakeGuard::new();
691 let _ = unblocker.send(event);
692 Ok(())
693 }
694
695 fn maybe_unblock(&self, event: UnblockEvent) -> bool {
696 let id = PendingCommandId::from_unblock_event(&event);
697 let Some(unblocker) = self.pending_unblocks.borrow_mut().remove(&id) else {
698 return false;
699 };
700 self.progress_made.set(true);
701 let _guard = SdkWakeGuard::new();
702 let _ = unblocker.send(event);
703 true
704 }
705
706 fn set_forced_wft_failure(&self, err: Box<dyn std::error::Error + Send + Sync>) {
707 *self.forced_wft_failure.borrow_mut() = Some(err);
708 self.progress_made.set(true);
709 }
710
711 fn take_forced_wft_failure(&self) -> Option<Box<dyn std::error::Error + Send + Sync>> {
712 self.forced_wft_failure.borrow_mut().take()
713 }
714
715 fn mark_progress(&self) {
716 self.progress_made.set(true);
717 }
718
719 fn take_progress(&self) -> bool {
720 self.progress_made.replace(false)
721 }
722}
723
724struct WorkflowContextInner {
725 namespace: String,
726 task_queue: String,
727 run_id: String,
728 initial_information: InitializeWorkflow,
729 runtime: WorkflowRuntimeState,
730 cancellation_token: WorkflowCancellationToken,
731 cancelled_operations: RefCell<HashSet<CancellableSeqNum>>,
732 shared: RefCell<WorkflowContextSharedData>,
733 random: Rc<RefCell<WorkflowRandomState>>,
734 seq_nums: RefCell<WfCtxProtectedDat>,
735 data_converter: DataConverter,
736 patch_activation_callback: Option<PatchActivationCallback>,
737 state_mutated: Cell<bool>,
738 active_handlers: Cell<usize>,
739 requires_replay_safety: Cell<bool>,
740 condition_wakers: RefCell<Vec<Waker>>,
741 current_waker: RefCell<Option<Waker>>,
742 context_values: WorkflowContextValueStore,
743 workflow_interceptors: Rc<[Arc<dyn WorkflowInterceptor>]>,
744}
745
746pub(crate) struct HandlerExecutionGuard {
747 base: BaseWorkflowContext,
748}
749
750pub(crate) struct ReadOnlyGuard {
751 base: BaseWorkflowContext,
752 previous: bool,
753}
754
755impl Drop for ReadOnlyGuard {
756 fn drop(&mut self) {
757 self.base.inner.requires_replay_safety.set(self.previous);
758 }
759}
760
761impl Drop for HandlerExecutionGuard {
762 fn drop(&mut self) {
763 let active_handlers = self.base.inner.active_handlers.get();
764 debug_assert!(active_handlers > 0, "handler execution count underflow");
765 self.base
766 .inner
767 .active_handlers
768 .set(active_handlers.saturating_sub(1));
769 if active_handlers <= 1 {
770 self.base.wake_condition_waiters();
771 }
772 }
773}
774
775#[derive(Eq, Hash, PartialEq)]
777enum CancellableSeqNum {
778 Timer(u32),
779 Activity(u32),
780 LocalActivity(u32),
781 ChildWorkflow(u32),
782 SignalExternalWorkflow(u32),
783 NexusOp(u32),
784}
785
786impl From<&CancellableID> for CancellableSeqNum {
787 fn from(value: &CancellableID) -> Self {
788 match value {
789 CancellableID::Timer(seq) => Self::Timer(*seq),
790 CancellableID::Activity(seq) => Self::Activity(*seq),
791 CancellableID::LocalActivity(seq) => Self::LocalActivity(*seq),
792 CancellableID::ChildWorkflow { seqnum, .. } => Self::ChildWorkflow(*seqnum),
793 CancellableID::SignalExternalWorkflow(seq) => Self::SignalExternalWorkflow(*seq),
794 CancellableID::NexusOp(seq) => Self::NexusOp(*seq),
795 }
796 }
797}
798
799pub struct SyncWorkflowContext<W> {
807 base: BaseWorkflowContext,
808 headers: Rc<HashMap<String, Payload>>,
810 _phantom: PhantomData<W>,
811}
812
813impl<W> Clone for SyncWorkflowContext<W> {
814 fn clone(&self) -> Self {
815 Self {
816 base: self.base.clone(),
817 headers: self.headers.clone(),
818 _phantom: PhantomData,
819 }
820 }
821}
822
823pub struct WorkflowContext<W> {
828 sync: SyncWorkflowContext<W>,
829 workflow_state: Rc<RefCell<W>>,
831}
832
833impl<W> Clone for WorkflowContext<W> {
834 fn clone(&self) -> Self {
835 Self {
836 sync: self.sync.clone(),
837 workflow_state: self.workflow_state.clone(),
838 }
839 }
840}
841
842impl BaseWorkflowContext {
843 #[doc(hidden)]
845 pub fn from_raw(
846 init: WorkflowInit,
847 data_converter: DataConverter,
848 host: Rc<dyn WorkflowHost>,
849 patch_activation_callback: Option<PatchActivationCallback>,
850 workflow_interceptor_constructors: Vec<WorkflowInterceptorConstructor>,
851 ) -> Self {
852 let WorkflowInit {
853 namespace,
854 task_queue,
855 run_id,
856 initialize_workflow,
857 } = init;
858 let random = Rc::new(RefCell::new(WorkflowRandomState::new(
859 initialize_workflow.randomness_seed,
860 )));
861 let context_values = WorkflowContextValueStore::default();
862 let view = WorkflowContextView::new(
863 namespace,
864 task_queue,
865 run_id,
866 initialize_workflow,
867 data_converter.payload_converter().clone(),
868 true,
869 Some(random.clone()),
870 )
871 .with_context_values(context_values.clone());
872 let workflow_interceptors = workflow_interceptor_constructors
873 .into_iter()
874 .map(|constructor| constructor.construct(&view))
875 .collect::<Vec<_>>()
876 .into();
877 let (namespace, task_queue, run_id, init_workflow_job) = view.into_parts();
878 Self {
879 inner: Rc::new(WorkflowContextInner {
880 namespace,
881 task_queue,
882 run_id,
883 shared: RefCell::new(WorkflowContextSharedData {
884 memo: init_workflow_job.memo.clone().unwrap_or_default(),
885 search_attributes: init_workflow_job
886 .search_attributes
887 .clone()
888 .unwrap_or_default(),
889 is_replaying_history_events: false,
890 changes: Default::default(),
891 activation: Default::default(),
892 current_details: Default::default(),
893 notified_patches: Default::default(),
894 }),
895 random,
896 initial_information: init_workflow_job,
897 runtime: WorkflowRuntimeState::new(host),
898 cancellation_token: WorkflowCancellationToken::new(),
899 cancelled_operations: Default::default(),
900 seq_nums: RefCell::new(WfCtxProtectedDat {
901 next_timer_sequence_number: 1,
902 next_activity_sequence_number: 1,
903 next_child_workflow_sequence_number: 1,
904 next_cancel_external_wf_sequence_number: 1,
905 next_signal_external_wf_sequence_number: 1,
906 #[cfg(feature = "experimental")]
907 next_nexus_op_sequence_number: 1,
908 }),
909 data_converter,
910 patch_activation_callback,
911 state_mutated: Cell::new(false),
912 active_handlers: Cell::new(0),
913 requires_replay_safety: Cell::new(true),
914 condition_wakers: Default::default(),
915 current_waker: RefCell::new(None),
916 context_values,
917 workflow_interceptors,
918 }),
919 }
920 }
921
922 pub(crate) fn context_value<K: WorkflowContextKey>(&self) -> Option<Rc<K::Value>> {
923 self.inner.context_values.context_value::<K>()
924 }
925
926 fn context_values_with<K: WorkflowContextKey>(&self, value: K::Value) -> WorkflowContextValues {
927 let mut values = self.inner.context_values.current.borrow().as_ref().clone();
928 values.insert(TypeId::of::<K>(), Rc::new(value));
929 Rc::new(values)
930 }
931
932 pub(crate) fn with_context_value<K: WorkflowContextKey, F: Future>(
933 &self,
934 value: K::Value,
935 future: F,
936 ) -> WorkflowContextFuture<F> {
937 WorkflowContextFuture {
938 base: self.clone(),
939 values: self.context_values_with::<K>(value),
940 inner: Box::pin(future),
941 }
942 }
943
944 pub(crate) fn with_context_value_sync<K: WorkflowContextKey, R>(
945 &self,
946 value: K::Value,
947 f: impl FnOnce() -> R,
948 ) -> R {
949 let values = self.context_values_with::<K>(value);
950 let _guard = self.install_context_values(values);
951 f()
952 }
953
954 fn install_context_values(&self, values: WorkflowContextValues) -> WorkflowContextRestoreGuard {
955 let previous = self.inner.context_values.current.replace(values);
956 WorkflowContextRestoreGuard {
957 base: self.clone(),
958 previous: Some(previous),
959 }
960 }
961
962 pub(crate) fn workflow_interceptors(&self) -> Rc<[Arc<dyn WorkflowInterceptor>]> {
963 self.inner.workflow_interceptors.clone()
964 }
965
966 pub(crate) fn take_state_mutated(&self) -> bool {
969 self.inner.state_mutated.replace(false)
970 }
971
972 pub(crate) fn set_state_mutated(&self) {
974 self.inner.state_mutated.set(true);
975 }
976
977 pub(crate) fn all_handlers_finished(&self) -> bool {
978 self.inner.active_handlers.get() == 0
979 }
980
981 pub(crate) fn track_handler(&self) -> HandlerExecutionGuard {
982 self.inner
983 .active_handlers
984 .set(self.inner.active_handlers.get() + 1);
985 HandlerExecutionGuard { base: self.clone() }
986 }
987
988 fn wake_condition_waiters(&self) {
989 let _guard = SdkWakeGuard::new();
990 for waker in self.inner.condition_wakers.borrow_mut().drain(..) {
991 waker.wake();
992 }
993 }
994
995 pub(crate) fn take_runtime_progress(&self) -> bool {
996 self.inner.runtime.take_progress()
997 }
998
999 pub(crate) fn take_forced_wft_failure(
1000 &self,
1001 ) -> Option<Box<dyn std::error::Error + Send + Sync>> {
1002 self.inner.runtime.take_forced_wft_failure()
1003 }
1004
1005 pub(crate) fn notify_cancel(&self, reason: String) {
1006 if reason.is_empty() {
1007 self.inner.cancellation_token.cancel();
1008 } else {
1009 self.inner.cancellation_token.cancel_with_reason(reason);
1010 }
1011 self.inner.runtime.mark_progress();
1012 }
1013
1014 pub fn cancellation_token(&self) -> WorkflowCancellationToken {
1016 self.inner.cancellation_token.clone()
1017 }
1018
1019 pub(crate) fn unblock(&self, event: UnblockEvent) -> Result<(), anyhow::Error> {
1020 self.inner.runtime.unblock(event)
1021 }
1022
1023 fn cancel(&self, cancellable_id: CancellableID) {
1025 if !self
1026 .inner
1027 .cancelled_operations
1028 .borrow_mut()
1029 .insert((&cancellable_id).into())
1030 {
1031 return;
1032 }
1033 match cancellable_id {
1034 CancellableID::Timer(seq) => {
1035 if self
1036 .inner
1037 .runtime
1038 .maybe_unblock(UnblockEvent::Timer(seq, TimerResult::Cancelled))
1039 {
1040 self.inner.runtime.host.push_command(
1041 workflow_command::Variant::CancelTimer(CancelTimer { seq }).into(),
1042 );
1043 }
1044 }
1045 CancellableID::Activity(seq) => {
1046 self.inner.runtime.host.push_command(
1047 workflow_command::Variant::RequestCancelActivity(RequestCancelActivity { seq })
1048 .into(),
1049 );
1050 }
1051 CancellableID::LocalActivity(seq) => {
1052 self.inner.runtime.host.push_command(
1053 workflow_command::Variant::RequestCancelLocalActivity(
1054 RequestCancelLocalActivity { seq },
1055 )
1056 .into(),
1057 );
1058 }
1059 CancellableID::ChildWorkflow { seqnum, reason } => {
1060 self.inner.runtime.host.push_command(
1061 workflow_command::Variant::CancelChildWorkflowExecution(
1062 CancelChildWorkflowExecution {
1063 child_workflow_seq: seqnum,
1064 reason,
1065 },
1066 )
1067 .into(),
1068 );
1069 }
1070 CancellableID::SignalExternalWorkflow(seq) => {
1071 self.inner.runtime.host.push_command(
1072 workflow_command::Variant::CancelSignalWorkflow(CancelSignalWorkflow { seq })
1073 .into(),
1074 );
1075 }
1076 CancellableID::NexusOp(seq) => {
1077 self.inner.runtime.host.push_command(
1078 workflow_command::Variant::RequestCancelNexusOperation(
1079 RequestCancelNexusOperation { seq },
1080 )
1081 .into(),
1082 );
1083 }
1084 }
1085 }
1086
1087 fn cancellation_handle(&self, cancellable_id: CancellableID) -> WorkflowCancellationHandle {
1088 let base_ctx = self.clone();
1089 WorkflowCancellationHandle::new(move |reason| {
1090 let id = reason.map_or_else(
1091 || cancellable_id.clone(),
1092 |reason| cancellable_id.clone().with_reason(reason),
1093 );
1094 base_ctx.cancel(id);
1095 })
1096 }
1097
1098 pub fn current_details(&self) -> String {
1100 self.inner.shared.borrow().current_details.clone()
1101 }
1102
1103 pub fn timer<T: Into<TimerOptions>>(
1105 &self,
1106 opts: T,
1107 ) -> impl CancellableFuture<Output = TimerResult> + use<T> {
1108 let input = StartTimerInput::new(opts.into());
1109 let base_ctx = self.clone();
1110 let next = WorkflowNext::new(move |input: StartTimerInput| {
1111 let mut opts = input.into_options();
1112 let cancellation_token = opts
1113 .cancellation_token
1114 .take()
1115 .unwrap_or_else(|| base_ctx.cancellation_token());
1116 let seq = base_ctx.inner.seq_nums.borrow_mut().next_timer_seq();
1117 let (cmd, unblocker) =
1118 CancellableWFCommandFut::new(CancellableID::Timer(seq), base_ctx.clone());
1119 base_ctx
1120 .inner
1121 .runtime
1122 .register_unblocker(PendingCommandId::Timer(seq), unblocker);
1123 base_ctx
1124 .inner
1125 .runtime
1126 .host
1127 .push_command(opts.into_command(seq));
1128 CancellableWorkflowOutboundFuture::new(
1129 cmd,
1130 base_ctx.cancellation_handle(CancellableID::Timer(seq)),
1131 )
1132 .with_cancellation_token(cancellation_token)
1133 });
1134 let interceptors = self.inner.workflow_interceptors.clone();
1135 let future = call_start_timer(
1136 interceptors,
1137 WorkflowInterceptorContext::new(self.clone()),
1138 input,
1139 next,
1140 );
1141 self.prepare_cancellable_outbound_future(future)
1142 }
1143
1144 #[allow(clippy::result_large_err)]
1146 pub fn execute_activity<AD: ActivityDefinition>(
1147 &self,
1148 activity: AD,
1149 input: impl Into<AD::Input>,
1150 opts: ActivityOptions,
1151 ) -> impl CancellableFuture<Output = Result<AD::Output, ActivityExecutionError>>
1152 where
1153 AD::Output: TemporalDeserializable,
1154 {
1155 let input =
1156 ScheduleActivityInput::new(activity.name().to_string(), Box::new(input.into()), opts);
1157 let base_ctx = self.clone();
1158 let next = WorkflowNext::new(move |input: ScheduleActivityInput| {
1159 let (activity_type, input, headers, mut opts) = input.into_parts();
1160 let input = match input.downcast::<AD::Input>() {
1161 Ok(input) => *input,
1162 Err(_) => {
1163 return CancellableWorkflowOutboundFuture::new(
1164 async {
1165 Err(ActivityExecutionError::Serialization(outbound_type_error(
1166 "activity input",
1167 )))
1168 },
1169 WorkflowCancellationHandle::noop(),
1170 );
1171 }
1172 };
1173 let payload_converter = base_ctx.inner.data_converter.payload_converter();
1174 let context_data =
1175 SerializationContextData::Workflow(WorkflowSerializationContext::new());
1176 let ctx = SerializationContext::new(&context_data, payload_converter);
1177 match payload_converter.to_payloads(&ctx, &input) {
1178 Ok(payloads) => {
1179 let cancellation_token = opts
1180 .cancellation_token
1181 .take()
1182 .unwrap_or_else(|| base_ctx.cancellation_token());
1183 let seq = base_ctx.inner.seq_nums.borrow_mut().next_activity_seq();
1184 let (cmd, unblocker) = CancellableWFCommandFut::new(
1185 CancellableID::Activity(seq),
1186 base_ctx.clone(),
1187 );
1188 base_ctx
1189 .inner
1190 .runtime
1191 .register_unblocker(PendingCommandId::Activity(seq), unblocker);
1192 if opts.task_queue.is_none() {
1193 opts.task_queue = Some(base_ctx.inner.task_queue.clone());
1194 }
1195 base_ctx.inner.runtime.host.push_command(opts.into_command(
1196 seq,
1197 activity_type,
1198 payloads,
1199 headers,
1200 ));
1201 CancellableWorkflowOutboundFuture::new(
1202 ActivityFut::running(cmd, base_ctx.inner.data_converter.clone()),
1203 base_ctx.cancellation_handle(CancellableID::Activity(seq)),
1204 )
1205 .with_cancellation_token(cancellation_token)
1206 }
1207 Err(err) => CancellableWorkflowOutboundFuture::new(
1208 ActivityFut::<future::Ready<ActivityResolution>, AD::Output>::eager(err.into()),
1209 WorkflowCancellationHandle::noop(),
1210 ),
1211 }
1212 .map(|result| result.map(|output| Box::new(output) as Box<dyn WorkflowOutboundValue>))
1213 });
1214 let interceptors = self.inner.workflow_interceptors.clone();
1215 let future = call_schedule_activity(
1216 interceptors,
1217 WorkflowInterceptorContext::new(self.clone()),
1218 input,
1219 next,
1220 )
1221 .map(|result| {
1222 result.and_then(|output| {
1223 output
1224 .downcast::<AD::Output>()
1225 .map(|output| *output)
1226 .map_err(|_| {
1227 ActivityExecutionError::Serialization(outbound_type_error(
1228 "activity output",
1229 ))
1230 })
1231 })
1232 });
1233 self.prepare_cancellable_outbound_future(future)
1234 }
1235
1236 #[allow(clippy::result_large_err)]
1238 pub fn execute_local_activity<AD: ActivityDefinition>(
1239 &self,
1240 activity: AD,
1241 input: impl Into<AD::Input>,
1242 opts: LocalActivityOptions,
1243 ) -> impl CancellableFuture<Output = Result<AD::Output, ActivityExecutionError>>
1244 where
1245 AD::Output: TemporalDeserializable,
1246 {
1247 let input = ScheduleLocalActivityInput::new(
1248 activity.name().to_string(),
1249 Box::new(input.into()),
1250 opts,
1251 );
1252 let base_ctx = self.clone();
1253 let next = WorkflowNext::new(move |input: ScheduleLocalActivityInput| {
1254 let (activity_type, input, headers, mut opts) = input.into_parts();
1255 let input = match input.downcast::<AD::Input>() {
1256 Ok(input) => *input,
1257 Err(_) => {
1258 return CancellableWorkflowOutboundFuture::new(
1259 async {
1260 Err(ActivityExecutionError::Serialization(outbound_type_error(
1261 "local activity input",
1262 )))
1263 },
1264 WorkflowCancellationHandle::noop(),
1265 );
1266 }
1267 };
1268 let payload_converter = base_ctx.inner.data_converter.payload_converter();
1269 let context_data =
1270 SerializationContextData::Workflow(WorkflowSerializationContext::new());
1271 let ctx = SerializationContext::new(&context_data, payload_converter);
1272 match payload_converter.to_payloads(&ctx, &input) {
1273 Ok(payloads) => {
1274 let cancellation_token = opts
1275 .cancellation_token
1276 .take()
1277 .unwrap_or_else(|| base_ctx.cancellation_token());
1278 let future = LATimerBackoffFut::new(
1279 activity_type,
1280 payloads,
1281 headers,
1282 opts,
1283 cancellation_token.clone(),
1284 base_ctx.clone(),
1285 );
1286 cancellable_outbound(ActivityFut::running(
1287 future,
1288 base_ctx.inner.data_converter.clone(),
1289 ))
1290 .with_cancellation_token(cancellation_token)
1291 }
1292 Err(err) => CancellableWorkflowOutboundFuture::new(
1293 ActivityFut::<future::Ready<ActivityResolution>, AD::Output>::eager(err.into()),
1294 WorkflowCancellationHandle::noop(),
1295 ),
1296 }
1297 .map(|result| result.map(|output| Box::new(output) as Box<dyn WorkflowOutboundValue>))
1298 });
1299 let interceptors = self.inner.workflow_interceptors.clone();
1300 let future = call_schedule_local_activity(
1301 interceptors,
1302 WorkflowInterceptorContext::new(self.clone()),
1303 input,
1304 next,
1305 )
1306 .map(|result| {
1307 result.and_then(|output| {
1308 output
1309 .downcast::<AD::Output>()
1310 .map(|output| *output)
1311 .map_err(|_| {
1312 ActivityExecutionError::Serialization(outbound_type_error(
1313 "local activity output",
1314 ))
1315 })
1316 })
1317 });
1318 self.prepare_cancellable_outbound_future(future)
1319 }
1320
1321 pub(crate) fn start_child_workflow<WD: WorkflowDefinition + 'static>(
1323 &self,
1324 workflow: WD,
1325 input: impl Into<WD::Input>,
1326 opts: ChildWorkflowOptions,
1327 ) -> impl CancellableFutureWithReason<
1328 Output = Result<StartedChildWorkflow<WD>, ChildWorkflowStartError>,
1329 >
1330 where
1331 WD::Output: TemporalDeserializable,
1332 {
1333 let input =
1334 StartChildWorkflowInput::new(workflow.name().to_string(), Box::new(input.into()), opts);
1335 let base_ctx = self.clone();
1336 let next = WorkflowNext::new(move |input: StartChildWorkflowInput| {
1337 let (workflow_type, input, headers, mut opts) = input.into_parts();
1338 let input = match input.downcast::<WD::Input>() {
1339 Ok(input) => *input,
1340 Err(_) => {
1341 return CancellableWorkflowOutboundFuture::new(
1342 async {
1343 Err(ChildWorkflowStartError::Serialization(outbound_type_error(
1344 "child workflow input",
1345 )))
1346 },
1347 WorkflowCancellationHandle::noop(),
1348 );
1349 }
1350 };
1351 let payload_converter = base_ctx.inner.data_converter.payload_converter();
1352 let context_data =
1353 SerializationContextData::Workflow(WorkflowSerializationContext::new());
1354 let ctx = SerializationContext::new(&context_data, payload_converter);
1355 let payloads = match payload_converter.to_payloads(&ctx, &input) {
1356 Ok(payloads) => payloads,
1357 Err(err) => {
1358 return CancellableWorkflowOutboundFuture::new(
1359 ChildWorkflowStartFut::<future::Ready<PendingChildWorkflow<WD>>, WD>::eager(
1360 err.into(),
1361 ),
1362 WorkflowCancellationHandle::noop(),
1363 );
1364 }
1365 };
1366 let workflow_id = opts
1367 .workflow_id
1368 .take()
1369 .filter(|id| !id.is_empty())
1370 .unwrap_or_else(|| base_ctx.uuid4());
1371 let cancellation_token = opts
1372 .cancellation_token
1373 .take()
1374 .unwrap_or_else(|| base_ctx.cancellation_token());
1375
1376 let child_seq = base_ctx
1377 .inner
1378 .seq_nums
1379 .borrow_mut()
1380 .next_child_workflow_seq();
1381 let (result_cmd, unblocker) = CancellableWFCommandFut::new(
1385 CancellableID::ChildWorkflow {
1386 seqnum: child_seq,
1387 reason: String::new(),
1388 },
1389 base_ctx.clone(),
1390 );
1391 base_ctx.inner.runtime.register_unblocker(
1392 PendingCommandId::ChildWorkflowComplete(child_seq),
1393 unblocker,
1394 );
1395 base_ctx.inner.runtime.host.push_command(opts.into_command(
1396 child_seq,
1397 workflow_type,
1398 payloads,
1399 headers,
1400 workflow_id.clone(),
1401 ));
1402
1403 let result_future =
1404 cancellable_outbound_with_reason(ChildWorkflowFut::<_, WD::Output>::Running {
1405 inner: result_cmd,
1406 data_converter: base_ctx.inner.data_converter.clone(),
1407 _phantom: PhantomData,
1408 })
1409 .map(|result| {
1410 result.map(|output| Box::new(output) as Box<dyn WorkflowOutboundValue>)
1411 })
1412 .with_cancellation_token(cancellation_token);
1413
1414 let common = ChildWfCommon {
1415 workflow_id: workflow_id.clone(),
1416 child_seq,
1417 result_future,
1418 base_ctx: base_ctx.clone(),
1419 };
1420
1421 let (cmd, unblocker) =
1422 CancellableWFCommandFut::<PendingChildWorkflow<WD>, ChildWfCommon>::new_with_dat(
1423 CancellableID::ChildWorkflow {
1424 seqnum: child_seq,
1425 reason: String::new(),
1426 },
1427 common,
1428 base_ctx.clone(),
1429 );
1430 base_ctx
1431 .inner
1432 .runtime
1433 .register_unblocker(PendingCommandId::ChildWorkflowStart(child_seq), unblocker);
1434
1435 cancellable_outbound_with_reason(ChildWorkflowStartFut::Running(cmd))
1436 });
1437 let interceptors = self.inner.workflow_interceptors.clone();
1438 let future = call_start_child_workflow(
1439 interceptors,
1440 WorkflowInterceptorContext::new(self.clone()),
1441 input,
1442 next,
1443 )
1444 .map(|result| result.map(StartChildWorkflowOutput::into_started));
1445 self.prepare_cancellable_outbound_future(future)
1446 }
1447
1448 fn local_activity_no_timer_retry(
1450 self,
1451 activity_type: String,
1452 arguments: Vec<Payload>,
1453 headers: HashMap<String, Payload>,
1454 opts: LocalActivityOptions,
1455 ) -> impl CancellableFuture<Output = ActivityResolution> {
1456 let seq = self.inner.seq_nums.borrow_mut().next_activity_seq();
1457 let (cmd, unblocker) =
1458 CancellableWFCommandFut::new(CancellableID::LocalActivity(seq), self.clone());
1459 self.inner
1460 .runtime
1461 .register_unblocker(PendingCommandId::Activity(seq), unblocker);
1462 self.inner.runtime.host.push_command(opts.into_command(
1463 seq,
1464 activity_type,
1465 arguments,
1466 headers,
1467 ));
1468 cmd
1469 }
1470
1471 fn signal_workflow<S: SignalDefinition + 'static>(
1472 &self,
1473 target: SignalWorkflowTarget,
1474 signal: S,
1475 input: S::Input,
1476 options: SignalWorkflowOptions,
1477 ) -> CancellableWorkflowOutboundFuture<SignalWorkflowResult> {
1478 let input = SignalWorkflowInput::new(
1479 S::name(&signal).to_string(),
1480 target,
1481 Box::new(input),
1482 options,
1483 );
1484 let base_ctx = self.clone();
1485 let next = WorkflowNext::new(move |input: SignalWorkflowInput| {
1486 let (signal_name, target, input, headers, mut options) = input.into_parts();
1487 let cancellation_token = options
1488 .cancellation_token
1489 .take()
1490 .unwrap_or_else(|| base_ctx.cancellation_token());
1491 let input = match input.downcast::<S::Input>() {
1492 Ok(input) => *input,
1493 Err(_) => {
1494 return CancellableWorkflowOutboundFuture::new(
1495 async {
1496 Err(WorkflowSignalError::Serialization(outbound_type_error(
1497 "signal input",
1498 )))
1499 },
1500 WorkflowCancellationHandle::noop(),
1501 );
1502 }
1503 };
1504 let payload_converter = base_ctx.data_converter().payload_converter();
1505 let context_data =
1506 SerializationContextData::Workflow(WorkflowSerializationContext::new());
1507 let ctx = SerializationContext::new(&context_data, payload_converter);
1508 let payloads = match payload_converter.to_payloads(&ctx, &input) {
1509 Ok(payloads) => payloads,
1510 Err(err) => {
1511 return CancellableWorkflowOutboundFuture::new(
1512 async move { Err(err.into()) },
1513 WorkflowCancellationHandle::noop(),
1514 );
1515 }
1516 };
1517 let target = match target {
1518 SignalWorkflowTarget::Child { workflow_id } => {
1519 signal_external_workflow_execution::Target::ChildWorkflowId(workflow_id)
1520 }
1521 SignalWorkflowTarget::External {
1522 namespace,
1523 workflow_id,
1524 run_id,
1525 } => signal_external_workflow_execution::Target::WorkflowExecution(
1526 NamespacedWorkflowExecution {
1527 namespace,
1528 workflow_id,
1529 run_id: run_id.unwrap_or_default(),
1530 },
1531 ),
1532 };
1533 let seq = base_ctx
1534 .inner
1535 .seq_nums
1536 .borrow_mut()
1537 .next_signal_external_wf_seq();
1538 let (cmd, unblocker) = CancellableWFCommandFut::new(
1539 CancellableID::SignalExternalWorkflow(seq),
1540 base_ctx.clone(),
1541 );
1542 base_ctx
1543 .inner
1544 .runtime
1545 .register_unblocker(PendingCommandId::SignalExternal(seq), unblocker);
1546 base_ctx
1547 .inner
1548 .runtime
1549 .host
1550 .push_command(options.into_command(seq, signal_name, payloads, headers, target));
1551 cancellable_outbound(SignalChildFut::Running {
1552 inner: cmd,
1553 data_converter: base_ctx.data_converter().clone(),
1554 })
1555 .with_cancellation_token(cancellation_token)
1556 });
1557 let interceptors = self.inner.workflow_interceptors.clone();
1558 let future = call_signal_workflow(
1559 interceptors,
1560 WorkflowInterceptorContext::new(self.clone()),
1561 input,
1562 next,
1563 );
1564 self.prepare_cancellable_outbound_future(future)
1565 }
1566
1567 pub(crate) fn external_workflow(
1568 &self,
1569 workflow_id: impl Into<String>,
1570 run_id: Option<String>,
1571 ) -> ExternalWorkflowHandle {
1572 ExternalWorkflowHandle {
1573 workflow_id: workflow_id.into(),
1574 run_id,
1575 namespace: self.inner.namespace.clone(),
1576 base_ctx: self.clone(),
1577 }
1578 }
1579
1580 fn cancel_external_workflow(
1581 &self,
1582 input: CancelExternalWorkflowInput,
1583 ) -> WorkflowOutboundFuture<CancelExternalWorkflowResult> {
1584 let base_ctx = self.clone();
1585 let next = WorkflowNext::new(move |input: CancelExternalWorkflowInput| {
1586 let seq = base_ctx
1587 .inner
1588 .seq_nums
1589 .borrow_mut()
1590 .next_cancel_external_wf_seq();
1591 let (cmd, unblocker) = WFCommandFut::<CancelExternalWfResult, ()>::new();
1592 base_ctx
1593 .inner
1594 .runtime
1595 .register_unblocker(PendingCommandId::CancelExternal(seq), unblocker);
1596 base_ctx.inner.runtime.host.push_command(
1597 workflow_command::Variant::RequestCancelExternalWorkflowExecution(
1598 RequestCancelExternalWorkflowExecution {
1599 seq,
1600 workflow_execution: Some(NamespacedWorkflowExecution {
1601 namespace: base_ctx.inner.namespace.clone(),
1602 workflow_id: input.workflow_id,
1603 run_id: input.run_id.unwrap_or_default(),
1604 }),
1605 reason: input.reason.unwrap_or_default(),
1606 },
1607 )
1608 .into(),
1609 );
1610 let data_converter = base_ctx.data_converter().clone();
1611 WorkflowOutboundFuture::new(async move {
1612 match cmd.await {
1613 Ok(_) => Ok(()),
1614 Err(error) => {
1615 let context =
1616 SerializationContextData::Workflow(WorkflowSerializationContext::new());
1617 Err(data_converter.to_error(
1618 &context,
1619 error.failure,
1620 CancelExternalWorkflowDecodeHint::new(error.cause),
1621 )?)
1622 }
1623 }
1624 })
1625 });
1626 let interceptors = self.inner.workflow_interceptors.clone();
1627 let future = call_cancel_external_workflow(
1628 interceptors,
1629 WorkflowInterceptorContext::new(self.clone()),
1630 input,
1631 next,
1632 );
1633 self.prepare_outbound_future(future)
1634 }
1635}
1636
1637impl<W> SyncWorkflowContext<W> {
1638 pub fn context_value<K: WorkflowContextKey>(&self) -> Option<Rc<K::Value>> {
1643 self.base.context_value::<K>()
1644 }
1645
1646 pub fn with_context_value_sync<K: WorkflowContextKey, R>(
1651 &self,
1652 value: K::Value,
1653 f: impl FnOnce() -> R,
1654 ) -> R {
1655 self.base.with_context_value_sync::<K, R>(value, f)
1656 }
1657
1658 pub fn workflow_id(&self) -> &str {
1660 &self.base.inner.initial_information.workflow_id
1661 }
1662
1663 pub fn run_id(&self) -> &str {
1665 &self.base.inner.run_id
1666 }
1667
1668 pub fn namespace(&self) -> &str {
1670 &self.base.inner.namespace
1671 }
1672
1673 pub fn task_queue(&self) -> &str {
1675 &self.base.inner.task_queue
1676 }
1677
1678 pub fn workflow_time(&self) -> Option<SystemTime> {
1680 self.base
1681 .inner
1682 .shared
1683 .borrow()
1684 .activation
1685 .timestamp
1686 .try_into_or_none()
1687 }
1688
1689 pub fn history_length(&self) -> u32 {
1691 self.base.inner.shared.borrow().activation.history_length
1692 }
1693
1694 pub fn current_deployment_version(&self) -> Option<WorkerDeploymentVersion> {
1698 self.base
1699 .inner
1700 .shared
1701 .borrow()
1702 .activation
1703 .clone()
1704 .deployment_version_for_current_task
1705 .map(Into::into)
1706 }
1707
1708 pub fn search_attributes(&self) -> SearchAttributes {
1710 SearchAttributes::from_proto(&self.base.inner.shared.borrow().search_attributes)
1711 }
1712
1713 pub fn memo(&self) -> Memo {
1715 Memo::from_raw(
1716 Some(self.base.inner.shared.borrow().memo.clone()),
1717 self.payload_converter().clone(),
1718 SerializationContextData::Workflow(WorkflowSerializationContext::new()),
1719 )
1720 }
1721
1722 pub fn random<T>(&self) -> T
1727 where
1728 T: WorkflowRandomValue,
1729 {
1730 self.base.random()
1731 }
1732
1733 pub fn uuid4(&self) -> String {
1737 self.base.uuid4()
1738 }
1739
1740 pub fn random_stream(&self, name: impl Into<String>) -> WorkflowRandomStream {
1756 self.base.random_stream(name)
1757 }
1758
1759 pub fn is_replaying(&self) -> bool {
1761 self.base.inner.shared.borrow().activation.is_replaying
1762 }
1763
1764 pub fn is_replaying_history_events(&self) -> bool {
1766 self.base.inner.shared.borrow().is_replaying_history_events
1767 }
1768
1769 pub fn all_handlers_finished(&self) -> bool {
1773 self.base.all_handlers_finished()
1774 }
1775
1776 pub fn continue_as_new_suggested(&self) -> bool {
1778 self.base
1779 .inner
1780 .shared
1781 .borrow()
1782 .activation
1783 .continue_as_new_suggested
1784 }
1785
1786 #[cfg(feature = "experimental")]
1790 pub fn target_worker_deployment_version_changed(&self) -> bool {
1791 self.base
1792 .inner
1793 .shared
1794 .borrow()
1795 .activation
1796 .target_worker_deployment_version_changed
1797 }
1798
1799 pub fn headers(&self) -> &HashMap<String, Payload> {
1804 &self.headers
1805 }
1806
1807 pub fn payload_converter(&self) -> &PayloadConverter {
1809 self.base.inner.data_converter.payload_converter()
1810 }
1811
1812 pub fn info(&self) -> WorkflowContextView {
1814 self.view()
1815 }
1816
1817 pub fn cancellation_token(&self) -> WorkflowCancellationToken {
1819 self.base.cancellation_token()
1820 }
1821
1822 pub fn cancelled(&self) -> impl FusedFuture<Output = Option<String>> + '_ {
1824 let token = self.cancellation_token();
1825 async move {
1826 token.cancelled().await;
1827 token.reason()
1828 }
1829 .fuse()
1830 }
1831
1832 pub fn continue_as_new(
1837 &self,
1838 input: <W::Run as WorkflowDefinition>::Input,
1839 opts: ContinueAsNewOptions,
1840 ) -> Result<std::convert::Infallible, WorkflowTermination>
1841 where
1842 W: WorkflowImplementation,
1843 {
1844 let input = ContinueAsNewInput::new(Box::new(input), opts);
1845 let base_ctx = self.base.clone();
1846 let workflow_type = base_ctx.workflow_type().to_string();
1847 let next = WorkflowNext::new(move |input: ContinueAsNewInput| {
1848 let (input, headers, opts) = input.into_parts();
1849 let input = match input.downcast::<<W::Run as WorkflowDefinition>::Input>() {
1850 Ok(input) => input,
1851 Err(_) => return Err(outbound_type_error("continue-as-new input").into()),
1852 };
1853 let pc = base_ctx.data_converter().payload_converter();
1854 let context_data =
1855 SerializationContextData::Workflow(WorkflowSerializationContext::new());
1856 let ctx = SerializationContext::new(&context_data, pc);
1857 let arguments = pc
1858 .to_payloads(&ctx, &*input)
1859 .map_err(WorkflowTermination::from)?;
1860 let request = opts.into_request(workflow_type, arguments, headers, pc)?;
1861 Err(WorkflowTermination::continue_as_new(request))
1862 });
1863 let interceptors = self.base.inner.workflow_interceptors.clone();
1864 call_continue_as_new(
1865 interceptors,
1866 crate::workflow_interceptors::SyncWorkflowInterceptorContext::new(self.base.clone()),
1867 input,
1868 next,
1869 )
1870 }
1871
1872 pub fn timer<T: Into<TimerOptions>>(
1874 &self,
1875 opts: T,
1876 ) -> impl CancellableFuture<Output = TimerResult> {
1877 self.base.timer(opts)
1878 }
1879
1880 pub fn execute_activity<AD: ActivityDefinition>(
1882 &self,
1883 activity: AD,
1884 input: impl Into<AD::Input>,
1885 opts: ActivityOptions,
1886 ) -> impl CancellableFuture<Output = Result<AD::Output, ActivityExecutionError>>
1887 where
1888 AD::Output: TemporalDeserializable,
1889 {
1890 self.base.execute_activity(activity, input, opts)
1891 }
1892
1893 #[deprecated(note = "use `execute_activity` instead")]
1897 pub fn start_activity<AD: ActivityDefinition>(
1898 &self,
1899 activity: AD,
1900 input: impl Into<AD::Input>,
1901 opts: ActivityOptions,
1902 ) -> impl CancellableFuture<Output = Result<AD::Output, ActivityExecutionError>>
1903 where
1904 AD::Output: TemporalDeserializable,
1905 {
1906 self.execute_activity(activity, input, opts)
1907 }
1908
1909 pub fn execute_local_activity<AD: ActivityDefinition>(
1911 &self,
1912 activity: AD,
1913 input: impl Into<AD::Input>,
1914 opts: LocalActivityOptions,
1915 ) -> impl CancellableFuture<Output = Result<AD::Output, ActivityExecutionError>>
1916 where
1917 AD::Output: TemporalDeserializable,
1918 {
1919 self.base.execute_local_activity(activity, input, opts)
1920 }
1921
1922 #[deprecated(note = "use `execute_local_activity` instead")]
1926 pub fn start_local_activity<AD: ActivityDefinition>(
1927 &self,
1928 activity: AD,
1929 input: impl Into<AD::Input>,
1930 opts: LocalActivityOptions,
1931 ) -> impl CancellableFuture<Output = Result<AD::Output, ActivityExecutionError>>
1932 where
1933 AD::Output: TemporalDeserializable,
1934 {
1935 self.execute_local_activity(activity, input, opts)
1936 }
1937
1938 pub fn start_child_workflow<WD: WorkflowDefinition + 'static>(
1941 &self,
1942 workflow: WD,
1943 input: impl Into<WD::Input>,
1944 opts: ChildWorkflowOptions,
1945 ) -> impl CancellableFutureWithReason<
1946 Output = Result<StartedChildWorkflow<WD>, ChildWorkflowStartError>,
1947 >
1948 where
1949 WD::Output: TemporalDeserializable,
1950 {
1951 self.base.start_child_workflow(workflow, input, opts)
1952 }
1953
1954 #[deprecated(note = "use `start_child_workflow` instead")]
1956 pub fn child_workflow<WD: WorkflowDefinition + 'static>(
1957 &self,
1958 workflow: WD,
1959 input: impl Into<WD::Input>,
1960 opts: ChildWorkflowOptions,
1961 ) -> impl CancellableFutureWithReason<
1962 Output = Result<StartedChildWorkflow<WD>, ChildWorkflowStartError>,
1963 >
1964 where
1965 WD::Output: TemporalDeserializable,
1966 {
1967 self.start_child_workflow(workflow, input, opts)
1968 }
1969
1970 pub fn patched(&self, patch_id: &str) -> bool {
1976 self.patch_impl(patch_id, false)
1977 }
1978
1979 pub fn deprecate_patch(&self, patch_id: &str) -> bool {
1982 self.patch_impl(patch_id, true)
1983 }
1984
1985 fn patch_impl(&self, patch_id: &str, deprecated: bool) -> bool {
1986 if let Some(present) = self.base.inner.shared.borrow().changes.get(patch_id) {
1987 return *present;
1988 }
1989
1990 let shared = self.base.inner.shared.borrow();
1991 let replaying = shared.activation.is_replaying;
1992 let notified = shared.notified_patches.contains(patch_id);
1993 drop(shared);
1994
1995 let res = if deprecated || replaying || notified {
1997 !replaying || notified
1998 } else if let Some(callback) = &self.base.inner.patch_activation_callback {
1999 let _read_only = self.base.enter_read_only();
2000 callback(PatchActivationInput {
2001 workflow_info: self.base.view(),
2002 patch_id: patch_id.to_string(),
2003 })
2004 } else {
2005 true
2006 };
2007
2008 if res {
2009 self.base.inner.runtime.host.push_command(
2010 workflow_command::Variant::SetPatchMarker(SetPatchMarker {
2011 patch_id: patch_id.to_string(),
2012 deprecated,
2013 })
2014 .into(),
2015 );
2016 }
2017
2018 self.base
2019 .inner
2020 .shared
2021 .borrow_mut()
2022 .changes
2023 .insert(patch_id.to_string(), res);
2024
2025 res
2026 }
2027
2028 pub fn external_workflow(
2030 &self,
2031 workflow_id: impl Into<String>,
2032 run_id: Option<String>,
2033 ) -> ExternalWorkflowHandle {
2034 self.base.external_workflow(workflow_id, run_id)
2035 }
2036
2037 pub fn upsert_search_attributes(
2043 &self,
2044 updates: impl IntoIterator<Item = SearchAttributeUpdate>,
2045 ) {
2046 let updates: Vec<SearchAttributeUpdate> = updates.into_iter().collect();
2049
2050 {
2053 let mut shared = self.base.inner.shared.borrow_mut();
2054 let mut attrs = SearchAttributes::from_proto(&shared.search_attributes);
2055 for update in updates.iter().cloned() {
2056 attrs.apply(update);
2057 }
2058 shared.search_attributes = attrs.into_proto();
2059 }
2060
2061 let proto = SearchAttributes::updates_to_proto(updates);
2062 self.base.inner.runtime.host.push_command(
2063 workflow_command::Variant::UpsertWorkflowSearchAttributes(
2064 UpsertWorkflowSearchAttributes {
2065 search_attributes: Some(proto),
2066 },
2067 )
2068 .into(),
2069 );
2070 }
2071
2072 pub fn upsert_memo<K>(
2074 &self,
2075 updates: impl IntoIterator<Item = (K, Option<MemoValue>)>,
2076 ) -> Result<(), PayloadConversionError>
2077 where
2078 K: Into<String>,
2079 {
2080 let payload_converter = self.payload_converter();
2081 let context_data = SerializationContextData::Workflow(WorkflowSerializationContext::new());
2082 let context = SerializationContext::new(&context_data, payload_converter);
2083 let mut fields = HashMap::new();
2084 let mut local_updates = Vec::new();
2085 for (key, value) in updates {
2086 let key = key.into();
2087 let (command_payload, local_payload) = match value {
2088 Some(value) => {
2089 let payload = payload_converter.to_payload(&context, &value)?;
2090 (payload.clone(), Some(payload))
2091 }
2092 None => (
2093 payload_converter.to_payload(&context, &MemoValue::new(()))?,
2094 None,
2095 ),
2096 };
2097 fields.insert(key.clone(), command_payload);
2098 local_updates.push((key, local_payload));
2099 }
2100 {
2101 let mut shared = self.base.inner.shared.borrow_mut();
2102 for (key, payload) in local_updates {
2103 match payload {
2104 Some(payload) => {
2105 shared.memo.fields.insert(key, payload);
2106 }
2107 None => {
2108 shared.memo.fields.remove(&key);
2109 }
2110 }
2111 }
2112 }
2113 self.base.inner.runtime.host.push_command(
2114 workflow_command::Variant::ModifyWorkflowProperties(ModifyWorkflowProperties {
2115 upserted_memo: Some(ProtoMemo { fields }),
2116 })
2117 .into(),
2118 );
2119 Ok(())
2120 }
2121
2122 pub fn set_current_details(&self, details: impl Into<String>) {
2127 let details = details.into();
2128 self.base.inner.shared.borrow_mut().current_details = details.clone();
2129 self.base.inner.runtime.host.set_current_details(details);
2130 }
2131
2132 pub fn force_task_fail(&self, with: impl Into<Box<dyn std::error::Error + Send + Sync>>) {
2134 self.base.inner.runtime.set_forced_wft_failure(with.into());
2135 }
2136
2137 pub(crate) fn view(&self) -> WorkflowContextView {
2139 self.base.view()
2140 }
2141}
2142
2143impl<W> WorkflowContext<W> {
2144 pub(crate) fn from_base(base: BaseWorkflowContext, workflow_state: Rc<RefCell<W>>) -> Self {
2146 Self {
2147 sync: SyncWorkflowContext {
2148 base,
2149 headers: Rc::new(HashMap::new()),
2150 _phantom: PhantomData,
2151 },
2152 workflow_state,
2153 }
2154 }
2155
2156 pub(crate) fn with_headers(&self, headers: HashMap<String, Payload>) -> Self {
2158 Self {
2159 sync: SyncWorkflowContext {
2160 base: self.sync.base.clone(),
2161 headers: Rc::new(headers),
2162 _phantom: PhantomData,
2163 },
2164 workflow_state: self.workflow_state.clone(),
2165 }
2166 }
2167
2168 pub(crate) fn sync_context(&self) -> SyncWorkflowContext<W> {
2170 self.sync.clone()
2171 }
2172
2173 pub(crate) fn view(&self) -> WorkflowContextView {
2175 self.sync.view()
2176 }
2177
2178 pub fn context_value<K: WorkflowContextKey>(&self) -> Option<Rc<K::Value>> {
2182 self.sync.context_value::<K>()
2183 }
2184
2185 pub fn with_context_value<K: WorkflowContextKey, F: Future>(
2195 &self,
2196 value: K::Value,
2197 future: F,
2198 ) -> WorkflowContextFuture<F> {
2199 self.sync.base.with_context_value::<K, F>(value, future)
2200 }
2201
2202 pub fn with_context_value_sync<K: WorkflowContextKey, R>(
2204 &self,
2205 value: K::Value,
2206 f: impl FnOnce() -> R,
2207 ) -> R {
2208 self.sync.with_context_value_sync::<K, R>(value, f)
2209 }
2210
2211 pub fn workflow_id(&self) -> &str {
2213 self.sync.workflow_id()
2214 }
2215
2216 pub fn run_id(&self) -> &str {
2218 self.sync.run_id()
2219 }
2220
2221 pub fn namespace(&self) -> &str {
2223 self.sync.namespace()
2224 }
2225
2226 pub fn task_queue(&self) -> &str {
2228 self.sync.task_queue()
2229 }
2230
2231 pub fn workflow_time(&self) -> Option<SystemTime> {
2233 self.sync.workflow_time()
2234 }
2235
2236 pub fn history_length(&self) -> u32 {
2238 self.sync.history_length()
2239 }
2240
2241 pub fn current_deployment_version(&self) -> Option<WorkerDeploymentVersion> {
2245 self.sync.current_deployment_version()
2246 }
2247
2248 pub fn search_attributes(&self) -> SearchAttributes {
2250 self.sync.search_attributes()
2251 }
2252
2253 pub fn memo(&self) -> Memo {
2255 self.sync.memo()
2256 }
2257
2258 pub fn random<T>(&self) -> T
2262 where
2263 T: WorkflowRandomValue,
2264 {
2265 self.sync.random()
2266 }
2267
2268 pub fn uuid4(&self) -> String {
2272 self.sync.uuid4()
2273 }
2274
2275 pub fn random_stream(&self, name: impl Into<String>) -> WorkflowRandomStream {
2279 self.sync.random_stream(name)
2280 }
2281
2282 pub fn is_replaying(&self) -> bool {
2284 self.sync.is_replaying()
2285 }
2286
2287 pub fn is_replaying_history_events(&self) -> bool {
2289 self.sync.is_replaying_history_events()
2290 }
2291
2292 pub fn all_handlers_finished(&self) -> bool {
2311 self.sync.all_handlers_finished()
2312 }
2313
2314 pub fn continue_as_new_suggested(&self) -> bool {
2316 self.sync.continue_as_new_suggested()
2317 }
2318
2319 #[cfg(feature = "experimental")]
2323 pub fn target_worker_deployment_version_changed(&self) -> bool {
2324 self.sync.target_worker_deployment_version_changed()
2325 }
2326
2327 pub fn headers(&self) -> &HashMap<String, Payload> {
2329 self.sync.headers()
2330 }
2331
2332 pub fn payload_converter(&self) -> &PayloadConverter {
2334 self.sync.payload_converter()
2335 }
2336
2337 pub fn info(&self) -> WorkflowContextView {
2339 self.sync.info()
2340 }
2341
2342 pub fn cancellation_token(&self) -> WorkflowCancellationToken {
2344 self.sync.cancellation_token()
2345 }
2346
2347 pub fn cancelled(&self) -> impl FusedFuture<Output = Option<String>> + '_ {
2349 self.sync.cancelled()
2350 }
2351
2352 pub fn timer<T: Into<TimerOptions>>(
2354 &self,
2355 opts: T,
2356 ) -> impl CancellableFuture<Output = TimerResult> {
2357 self.sync.timer(opts)
2358 }
2359
2360 pub fn execute_activity<AD: ActivityDefinition>(
2362 &self,
2363 activity: AD,
2364 input: impl Into<AD::Input>,
2365 opts: ActivityOptions,
2366 ) -> impl CancellableFuture<Output = Result<AD::Output, ActivityExecutionError>>
2367 where
2368 AD::Output: TemporalDeserializable,
2369 {
2370 self.sync.execute_activity(activity, input, opts)
2371 }
2372
2373 #[deprecated(note = "use `execute_activity` instead")]
2377 pub fn start_activity<AD: ActivityDefinition>(
2378 &self,
2379 activity: AD,
2380 input: impl Into<AD::Input>,
2381 opts: ActivityOptions,
2382 ) -> impl CancellableFuture<Output = Result<AD::Output, ActivityExecutionError>>
2383 where
2384 AD::Output: TemporalDeserializable,
2385 {
2386 self.execute_activity(activity, input, opts)
2387 }
2388
2389 pub fn execute_local_activity<AD: ActivityDefinition>(
2391 &self,
2392 activity: AD,
2393 input: impl Into<AD::Input>,
2394 opts: LocalActivityOptions,
2395 ) -> impl CancellableFuture<Output = Result<AD::Output, ActivityExecutionError>>
2396 where
2397 AD::Output: TemporalDeserializable,
2398 {
2399 self.sync.execute_local_activity(activity, input, opts)
2400 }
2401
2402 #[deprecated(note = "use `execute_local_activity` instead")]
2406 pub fn start_local_activity<AD: ActivityDefinition>(
2407 &self,
2408 activity: AD,
2409 input: impl Into<AD::Input>,
2410 opts: LocalActivityOptions,
2411 ) -> impl CancellableFuture<Output = Result<AD::Output, ActivityExecutionError>>
2412 where
2413 AD::Output: TemporalDeserializable,
2414 {
2415 self.execute_local_activity(activity, input, opts)
2416 }
2417
2418 pub fn start_child_workflow<WD: WorkflowDefinition + 'static>(
2420 &self,
2421 workflow: WD,
2422 input: impl Into<WD::Input>,
2423 opts: ChildWorkflowOptions,
2424 ) -> impl CancellableFutureWithReason<
2425 Output = Result<StartedChildWorkflow<WD>, ChildWorkflowStartError>,
2426 >
2427 where
2428 WD::Output: TemporalDeserializable,
2429 {
2430 self.sync.start_child_workflow(workflow, input, opts)
2431 }
2432
2433 #[deprecated(note = "use `start_child_workflow` instead")]
2435 pub fn child_workflow<WD: WorkflowDefinition + 'static>(
2436 &self,
2437 workflow: WD,
2438 input: impl Into<WD::Input>,
2439 opts: ChildWorkflowOptions,
2440 ) -> impl CancellableFutureWithReason<
2441 Output = Result<StartedChildWorkflow<WD>, ChildWorkflowStartError>,
2442 >
2443 where
2444 WD::Output: TemporalDeserializable,
2445 {
2446 self.start_child_workflow(workflow, input, opts)
2447 }
2448
2449 pub fn patched(&self, patch_id: &str) -> bool {
2451 self.sync.patched(patch_id)
2452 }
2453
2454 pub fn deprecate_patch(&self, patch_id: &str) -> bool {
2457 self.sync.deprecate_patch(patch_id)
2458 }
2459
2460 pub fn external_workflow(
2462 &self,
2463 workflow_id: impl Into<String>,
2464 run_id: Option<String>,
2465 ) -> ExternalWorkflowHandle {
2466 self.sync.external_workflow(workflow_id, run_id)
2467 }
2468
2469 pub fn upsert_search_attributes(
2471 &self,
2472 updates: impl IntoIterator<Item = SearchAttributeUpdate>,
2473 ) {
2474 self.sync.upsert_search_attributes(updates)
2475 }
2476
2477 pub fn upsert_memo<K>(
2479 &self,
2480 updates: impl IntoIterator<Item = (K, Option<MemoValue>)>,
2481 ) -> Result<(), PayloadConversionError>
2482 where
2483 K: Into<String>,
2484 {
2485 self.sync.upsert_memo(updates)
2486 }
2487
2488 pub fn set_current_details(&self, details: impl Into<String>) {
2492 self.sync.set_current_details(details)
2493 }
2494
2495 pub fn force_task_fail(&self, with: impl Into<Box<dyn std::error::Error + Send + Sync>>) {
2497 self.sync.force_task_fail(with)
2498 }
2499
2500 pub fn state<R>(&self, f: impl FnOnce(&W) -> R) -> R {
2505 f(&*self.workflow_state.borrow())
2506 }
2507
2508 pub fn state_mut<R>(&self, f: impl FnOnce(&mut W) -> R) -> R {
2517 let result = f(&mut *self.workflow_state.borrow_mut());
2518 self.sync.base.wake_condition_waiters();
2519 self.sync.base.set_state_mutated();
2520 result
2521 }
2522
2523 pub fn continue_as_new(
2528 &self,
2529 input: <W::Run as WorkflowDefinition>::Input,
2530 opts: ContinueAsNewOptions,
2531 ) -> Result<std::convert::Infallible, WorkflowTermination>
2532 where
2533 W: WorkflowImplementation,
2534 {
2535 self.sync.continue_as_new(input, opts)
2536 }
2537
2538 pub fn wait_condition<'a>(
2544 &'a self,
2545 condition: impl FnMut(&W) -> bool + 'a,
2546 ) -> impl FusedFuture<Output = Result<(), WorkflowCancellationError>> + 'a {
2547 self.wait_condition_with_options(condition, Default::default())
2548 }
2549
2550 pub fn wait_condition_with_options<'a>(
2552 &'a self,
2553 mut condition: impl FnMut(&W) -> bool + 'a,
2554 options: WaitConditionOptions,
2555 ) -> impl FusedFuture<Output = Result<(), WorkflowCancellationError>> + 'a {
2556 let token = options
2557 .cancellation_token
2558 .unwrap_or_else(|| self.cancellation_token());
2559 let wait_token = token.clone();
2560 let mut cancelled = Box::pin(async move {
2561 wait_token.cancelled().await;
2562 });
2563 future::poll_fn(move |cx: &mut Context<'_>| {
2564 if condition(&*self.workflow_state.borrow()) {
2565 Poll::Ready(Ok(()))
2566 } else if cancelled.as_mut().poll(cx).is_ready() {
2567 Poll::Ready(Err(WorkflowCancellationError::new(token.reason())))
2568 } else {
2569 self.sync
2570 .base
2571 .inner
2572 .condition_wakers
2573 .borrow_mut()
2574 .push(cx.waker().clone());
2575 Poll::Pending
2576 }
2577 })
2578 .fuse()
2579 }
2580}
2581
2582struct WfCtxProtectedDat {
2583 next_timer_sequence_number: u32,
2584 next_activity_sequence_number: u32,
2585 next_child_workflow_sequence_number: u32,
2586 next_cancel_external_wf_sequence_number: u32,
2587 next_signal_external_wf_sequence_number: u32,
2588 #[cfg(feature = "experimental")]
2589 next_nexus_op_sequence_number: u32,
2590}
2591
2592impl WfCtxProtectedDat {
2593 fn next_timer_seq(&mut self) -> u32 {
2594 let seq = self.next_timer_sequence_number;
2595 self.next_timer_sequence_number += 1;
2596 seq
2597 }
2598 fn next_activity_seq(&mut self) -> u32 {
2599 let seq = self.next_activity_sequence_number;
2600 self.next_activity_sequence_number += 1;
2601 seq
2602 }
2603 fn next_child_workflow_seq(&mut self) -> u32 {
2604 let seq = self.next_child_workflow_sequence_number;
2605 self.next_child_workflow_sequence_number += 1;
2606 seq
2607 }
2608 fn next_cancel_external_wf_seq(&mut self) -> u32 {
2609 let seq = self.next_cancel_external_wf_sequence_number;
2610 self.next_cancel_external_wf_sequence_number += 1;
2611 seq
2612 }
2613 fn next_signal_external_wf_seq(&mut self) -> u32 {
2614 let seq = self.next_signal_external_wf_sequence_number;
2615 self.next_signal_external_wf_sequence_number += 1;
2616 seq
2617 }
2618}
2619
2620#[derive(Clone, Debug)]
2621struct WorkflowContextSharedData {
2622 changes: HashMap<String, bool>,
2624 notified_patches: HashSet<String>,
2626 activation: CoreWorkflowActivation,
2627 memo: ProtoMemo,
2628 is_replaying_history_events: bool,
2629 search_attributes: ProtoSearchAttributes,
2630 current_details: String,
2632}
2633
2634pub trait CancellableFuture: FusedFuture {
2637 fn cancel(&self);
2639}
2640
2641pub trait CancellableFutureWithReason: CancellableFuture {
2643 fn cancel_with_reason(&self, reason: String);
2645}
2646
2647fn cancellable_outbound<T: 'static>(
2648 future: impl CancellableFuture<Output = T> + 'static,
2649) -> CancellableWorkflowOutboundFuture<T> {
2650 let future = Rc::new(RefCell::new(Box::pin(future)));
2651 let polled = future.clone();
2652 let cancellation = WorkflowCancellationHandle::new(move |_| {
2653 future.borrow().as_ref().get_ref().cancel();
2654 });
2655 CancellableWorkflowOutboundFuture::new(
2656 future::poll_fn(move |cx| polled.borrow_mut().as_mut().poll(cx)),
2657 cancellation,
2658 )
2659}
2660
2661fn cancellable_outbound_with_reason<T: 'static>(
2662 future: impl CancellableFutureWithReason<Output = T> + 'static,
2663) -> CancellableWorkflowOutboundFuture<T> {
2664 let future = Rc::new(RefCell::new(Box::pin(future)));
2665 let polled = future.clone();
2666 let cancellation = WorkflowCancellationHandle::new(move |reason| {
2667 let future = future.borrow();
2668 let future = future.as_ref().get_ref();
2669 if let Some(reason) = reason {
2670 future.cancel_with_reason(reason);
2671 } else {
2672 future.cancel();
2673 }
2674 });
2675 CancellableWorkflowOutboundFuture::new(
2676 future::poll_fn(move |cx| polled.borrow_mut().as_mut().poll(cx)),
2677 cancellation,
2678 )
2679}
2680
2681pub(crate) struct WFCommandFut<T, D> {
2682 _unused: PhantomData<T>,
2683 result_rx: oneshot::Receiver<UnblockEvent>,
2684 other_dat: Option<D>,
2685}
2686impl<T> WFCommandFut<T, ()> {
2687 fn new() -> (Self, oneshot::Sender<UnblockEvent>) {
2688 Self::new_with_dat(())
2689 }
2690}
2691
2692impl<T, D> WFCommandFut<T, D> {
2693 fn new_with_dat(other_dat: D) -> (Self, oneshot::Sender<UnblockEvent>) {
2694 let (tx, rx) = oneshot::channel();
2695 (
2696 Self {
2697 _unused: PhantomData,
2698 result_rx: rx,
2699 other_dat: Some(other_dat),
2700 },
2701 tx,
2702 )
2703 }
2704}
2705
2706impl<T, D> Unpin for WFCommandFut<T, D> where T: Unblockable<OtherDat = D> {}
2707impl<T, D> Future for WFCommandFut<T, D>
2708where
2709 T: Unblockable<OtherDat = D>,
2710{
2711 type Output = T;
2712
2713 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2714 let poll = self.result_rx.poll_unpin(cx).map(|x| {
2715 let od = self
2716 .other_dat
2717 .take()
2718 .expect("Other data must exist when resolving command future");
2719 Unblockable::unblock(x.unwrap(), od)
2720 });
2721 if poll.is_pending() {
2722 mark_intercepted_future_activation();
2723 }
2724 poll
2725 }
2726}
2727impl<T, D> FusedFuture for WFCommandFut<T, D>
2728where
2729 T: Unblockable<OtherDat = D>,
2730{
2731 fn is_terminated(&self) -> bool {
2732 self.other_dat.is_none()
2733 }
2734}
2735
2736struct CancellableWFCommandFut<T, D> {
2737 cmd_fut: WFCommandFut<T, D>,
2738 cancellable_id: CancellableID,
2739 base_ctx: BaseWorkflowContext,
2740}
2741impl<T> CancellableWFCommandFut<T, ()> {
2742 fn new(
2743 cancellable_id: CancellableID,
2744 base_ctx: BaseWorkflowContext,
2745 ) -> (Self, oneshot::Sender<UnblockEvent>) {
2746 Self::new_with_dat(cancellable_id, (), base_ctx)
2747 }
2748}
2749impl<T, D> CancellableWFCommandFut<T, D> {
2750 fn new_with_dat(
2751 cancellable_id: CancellableID,
2752 other_dat: D,
2753 base_ctx: BaseWorkflowContext,
2754 ) -> (Self, oneshot::Sender<UnblockEvent>) {
2755 let (cmd_fut, sender) = WFCommandFut::new_with_dat(other_dat);
2756 (
2757 Self {
2758 cmd_fut,
2759 cancellable_id,
2760 base_ctx,
2761 },
2762 sender,
2763 )
2764 }
2765}
2766impl<T, D> Unpin for CancellableWFCommandFut<T, D> where T: Unblockable<OtherDat = D> {}
2767impl<T, D> Future for CancellableWFCommandFut<T, D>
2768where
2769 T: Unblockable<OtherDat = D>,
2770{
2771 type Output = T;
2772
2773 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2774 self.cmd_fut.poll_unpin(cx)
2775 }
2776}
2777impl<T, D> FusedFuture for CancellableWFCommandFut<T, D>
2778where
2779 T: Unblockable<OtherDat = D>,
2780{
2781 fn is_terminated(&self) -> bool {
2782 self.cmd_fut.is_terminated()
2783 }
2784}
2785
2786impl<T, D> CancellableFuture for CancellableWFCommandFut<T, D>
2787where
2788 T: Unblockable<OtherDat = D>,
2789{
2790 fn cancel(&self) {
2791 self.base_ctx.cancel(self.cancellable_id.clone());
2792 }
2793}
2794impl<T, D> CancellableFutureWithReason for CancellableWFCommandFut<T, D>
2795where
2796 T: Unblockable<OtherDat = D>,
2797{
2798 fn cancel_with_reason(&self, reason: String) {
2799 self.base_ctx
2800 .cancel(self.cancellable_id.clone().with_reason(reason));
2801 }
2802}
2803
2804struct LATimerBackoffFut {
2805 la_opts: LocalActivityOptions,
2806 activity_type: String,
2807 arguments: Vec<Payload>,
2808 headers: HashMap<String, Payload>,
2809 current_fut: Pin<Box<dyn CancellableFuture<Output = ActivityResolution> + Unpin>>,
2810 timer_fut: Option<Pin<Box<dyn CancellableFuture<Output = TimerResult> + Unpin>>>,
2811 cancellation_token: WorkflowCancellationToken,
2812 base_ctx: BaseWorkflowContext,
2813 next_attempt: u32,
2814 next_sched_time: Option<prost_types::Timestamp>,
2815 did_cancel: AtomicBool,
2816 terminated: bool,
2817}
2818impl LATimerBackoffFut {
2819 fn new(
2820 activity_type: String,
2821 arguments: Vec<Payload>,
2822 headers: HashMap<String, Payload>,
2823 opts: LocalActivityOptions,
2824 cancellation_token: WorkflowCancellationToken,
2825 base_ctx: BaseWorkflowContext,
2826 ) -> Self {
2827 let current_fut = Box::pin(base_ctx.clone().local_activity_no_timer_retry(
2828 activity_type.clone(),
2829 arguments.clone(),
2830 headers.clone(),
2831 opts.clone(),
2832 ));
2833 Self {
2834 la_opts: opts,
2835 activity_type,
2836 arguments,
2837 headers,
2838 current_fut,
2839 timer_fut: None,
2840 cancellation_token,
2841 base_ctx,
2842 next_attempt: 1,
2843 next_sched_time: None,
2844 did_cancel: AtomicBool::new(false),
2845 terminated: false,
2846 }
2847 }
2848}
2849impl Unpin for LATimerBackoffFut {}
2850impl Future for LATimerBackoffFut {
2851 type Output = ActivityResolution;
2852
2853 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2854 if let Some(tf) = self.timer_fut.as_mut() {
2856 return match tf.poll_unpin(cx) {
2857 Poll::Ready(tr) => {
2858 self.timer_fut = None;
2859 if let TimerResult::Fired = tr {
2861 let mut opts = self.la_opts.clone();
2862 opts.attempt = Some(self.next_attempt);
2863 opts.original_schedule_time
2864 .clone_from(&self.next_sched_time);
2865 self.current_fut =
2866 Box::pin(self.base_ctx.clone().local_activity_no_timer_retry(
2867 self.activity_type.clone(),
2868 self.arguments.clone(),
2869 self.headers.clone(),
2870 opts,
2871 ));
2872 Poll::Pending
2873 } else {
2874 self.terminated = true;
2875 Poll::Ready(ActivityResolution {
2876 status: Some(activity_resolution::Status::Cancelled(Cancellation {
2877 failure: Some(Failure {
2878 message: "Activity cancelled".to_owned(),
2879 failure_info: Some(FailureInfo::CanceledFailureInfo(
2880 CanceledFailureInfo::default(),
2881 )),
2882 ..Default::default()
2883 }),
2884 })),
2885 })
2886 }
2887 }
2888 Poll::Pending => Poll::Pending,
2889 };
2890 }
2891 let poll_res = self.current_fut.poll_unpin(cx);
2892 if let Poll::Ready(ref r) = poll_res
2893 && let Some(activity_resolution::Status::Backoff(b)) = r.status.as_ref()
2894 {
2895 if self.did_cancel.load(Ordering::Acquire) {
2899 self.terminated = true;
2900 return Poll::Ready(ActivityResolution {
2901 status: Some(activity_resolution::Status::Cancelled(Cancellation {
2902 failure: Some(Failure {
2903 message: "Activity cancelled".to_owned(),
2904 failure_info: Some(FailureInfo::CanceledFailureInfo(
2905 CanceledFailureInfo::default(),
2906 )),
2907 ..Default::default()
2908 }),
2909 })),
2910 });
2911 }
2912
2913 let timer_f = self.base_ctx.timer(TimerOptions {
2914 duration: b
2915 .backoff_duration
2916 .expect("Duration is set")
2917 .try_into()
2918 .expect("duration converts ok"),
2919 cancellation_token: Some(self.cancellation_token.clone()),
2920 summary: None,
2921 #[cfg(feature = "experimental")]
2922 event_group_markers: self.la_opts.event_group_markers.clone(),
2923 });
2924 self.timer_fut = Some(Box::pin(timer_f));
2925 self.next_attempt = b.attempt;
2926 self.next_sched_time.clone_from(&b.original_schedule_time);
2927 return Poll::Pending;
2928 }
2929 if poll_res.is_ready() {
2930 self.terminated = true;
2931 }
2932 poll_res
2933 }
2934}
2935impl FusedFuture for LATimerBackoffFut {
2936 fn is_terminated(&self) -> bool {
2937 self.terminated
2938 }
2939}
2940impl CancellableFuture for LATimerBackoffFut {
2941 fn cancel(&self) {
2942 self.did_cancel.store(true, Ordering::Release);
2943 if let Some(tf) = self.timer_fut.as_ref() {
2944 tf.cancel();
2945 }
2946 self.current_fut.cancel();
2947 }
2948}
2949
2950enum ActivityFut<F, Output> {
2952 Errored {
2954 error: Option<Box<ActivityExecutionError>>,
2955 _phantom: PhantomData<Output>,
2956 },
2957 Running {
2959 inner: F,
2960 data_converter: DataConverter,
2961 _phantom: PhantomData<Output>,
2962 },
2963 Terminated,
2964}
2965
2966impl<F, Output> ActivityFut<F, Output> {
2967 fn eager(err: ActivityExecutionError) -> Self {
2968 Self::Errored {
2969 error: Some(Box::new(err)),
2970 _phantom: PhantomData,
2971 }
2972 }
2973
2974 fn running(inner: F, data_converter: DataConverter) -> Self {
2975 Self::Running {
2976 inner,
2977 data_converter,
2978 _phantom: PhantomData,
2979 }
2980 }
2981}
2982
2983impl<F, Output> Unpin for ActivityFut<F, Output> where F: Unpin {}
2984
2985impl<F, Output> Future for ActivityFut<F, Output>
2986where
2987 F: Future<Output = ActivityResolution> + Unpin,
2988 Output: TemporalDeserializable + 'static,
2989{
2990 type Output = Result<Output, ActivityExecutionError>;
2991
2992 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2993 let this = self.get_mut();
2994 let poll =
2995 match this {
2996 ActivityFut::Errored { error, .. } => {
2997 Poll::Ready(Err(*error.take().expect("polled after completion")))
2998 }
2999 ActivityFut::Running {
3000 inner,
3001 data_converter,
3002 ..
3003 } => match Pin::new(inner).poll(cx) {
3004 Poll::Pending => Poll::Pending,
3005 Poll::Ready(resolution) => Poll::Ready({
3006 let status = resolution.status.ok_or_else(|| {
3007 data_converter
3008 .to_error(
3009 &SerializationContextData::Workflow(
3010 WorkflowSerializationContext::new(),
3011 ),
3012 Failure {
3013 message: "Activity completed without a status".to_string(),
3014 ..Default::default()
3015 },
3016 ActivityExecutionDecodeHint::new(false),
3017 )
3018 .expect("synthetic activity failure should decode")
3019 })?;
3020
3021 match status {
3022 activity_resolution::Status::Completed(success) => {
3023 let payload = success.result.unwrap_or_default();
3024 let context_data = SerializationContextData::Workflow(
3025 WorkflowSerializationContext::new(),
3026 );
3027 let ctx = SerializationContext::new(
3028 &context_data,
3029 data_converter.payload_converter(),
3030 );
3031 data_converter
3032 .payload_converter()
3033 .from_payload::<Output>(&ctx, payload)
3034 .map_err(ActivityExecutionError::Serialization)
3035 }
3036 activity_resolution::Status::Failed(f) => Err(data_converter
3037 .to_error(
3038 &SerializationContextData::Workflow(
3039 WorkflowSerializationContext::new(),
3040 ),
3041 f.failure.unwrap_or_default(),
3042 ActivityExecutionDecodeHint::new(false),
3043 )?),
3044 activity_resolution::Status::Cancelled(c) => Err(data_converter
3045 .to_error(
3046 &SerializationContextData::Workflow(
3047 WorkflowSerializationContext::new(),
3048 ),
3049 c.failure.unwrap_or_default(),
3050 ActivityExecutionDecodeHint::new(true),
3051 )?),
3052 activity_resolution::Status::Backoff(_) => {
3053 panic!("DoBackoff should be handled by LATimerBackoffFut")
3054 }
3055 }
3056 }),
3057 },
3058 ActivityFut::Terminated => panic!("polled after termination"),
3059 };
3060 if poll.is_ready() {
3061 *this = ActivityFut::Terminated;
3062 }
3063 poll
3064 }
3065}
3066
3067impl<F, Output> FusedFuture for ActivityFut<F, Output>
3068where
3069 F: Future<Output = ActivityResolution> + Unpin,
3070 Output: TemporalDeserializable + 'static,
3071{
3072 fn is_terminated(&self) -> bool {
3073 matches!(self, ActivityFut::Terminated)
3074 }
3075}
3076
3077impl<F, Output> CancellableFuture for ActivityFut<F, Output>
3078where
3079 F: CancellableFuture<Output = ActivityResolution> + Unpin,
3080 Output: TemporalDeserializable + 'static,
3081{
3082 fn cancel(&self) {
3083 if let ActivityFut::Running { inner, .. } = self {
3084 inner.cancel()
3085 }
3086 }
3087}
3088
3089pub(crate) struct ChildWfCommon {
3090 workflow_id: String,
3091 child_seq: u32,
3092 result_future: CancellableWorkflowOutboundFuture<ChildWorkflowOutboundResult>,
3093 base_ctx: BaseWorkflowContext,
3094}
3095
3096#[derive(derive_more::Debug)]
3100pub(crate) struct PendingChildWorkflow<WD: WorkflowDefinition> {
3101 pub(crate) status: ChildWorkflowStartStatus,
3102 #[debug(skip)]
3103 pub(crate) common: ChildWfCommon,
3104 pub(crate) _phantom: PhantomData<WD>,
3105}
3106
3107#[derive(derive_more::Debug)]
3109pub struct StartChildWorkflowOutput {
3110 pub run_id: String,
3112 #[debug(skip)]
3113 result_future: CancellableWorkflowOutboundFuture<ChildWorkflowOutboundResult>,
3114 workflow_id: String,
3115 child_seq: u32,
3116 #[debug(skip)]
3117 base_ctx: BaseWorkflowContext,
3118}
3119
3120impl StartChildWorkflowOutput {
3121 pub fn map_result(
3123 mut self,
3124 map: impl FnOnce(
3125 CancellableWorkflowOutboundFuture<ChildWorkflowOutboundResult>,
3126 ) -> CancellableWorkflowOutboundFuture<ChildWorkflowOutboundResult>,
3127 ) -> Self {
3128 self.result_future = map(self.result_future);
3129 self
3130 }
3131
3132 fn into_started<WD: WorkflowDefinition>(self) -> StartedChildWorkflow<WD> {
3133 StartedChildWorkflow {
3134 run_id: self.run_id,
3135 result_future: self.result_future,
3136 workflow_id: self.workflow_id,
3137 child_seq: self.child_seq,
3138 base_ctx: self.base_ctx,
3139 _phantom: PhantomData,
3140 }
3141 }
3142}
3143
3144#[derive(derive_more::Debug)]
3146pub struct StartedChildWorkflow<WD: WorkflowDefinition> {
3147 pub run_id: String,
3149 #[debug(skip)]
3150 result_future: CancellableWorkflowOutboundFuture<ChildWorkflowOutboundResult>,
3151 workflow_id: String,
3152 child_seq: u32,
3153 #[debug(skip)]
3154 base_ctx: BaseWorkflowContext,
3155 _phantom: PhantomData<WD>,
3156}
3157
3158enum ChildWorkflowFut<F, Output> {
3161 Running {
3162 inner: F,
3163 data_converter: DataConverter,
3164 _phantom: PhantomData<Output>,
3165 },
3166 Terminated,
3167}
3168
3169impl<F, Output> Unpin for ChildWorkflowFut<F, Output> where F: Unpin {}
3170
3171impl<F, Output> Future for ChildWorkflowFut<F, Output>
3172where
3173 F: Future<Output = ChildWorkflowResult> + Unpin,
3174 Output: TemporalDeserializable + 'static,
3175{
3176 type Output = Result<Output, ChildWorkflowExecutionError>;
3177
3178 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
3179 let this = self.get_mut();
3180 let poll = match this {
3181 ChildWorkflowFut::Running {
3182 inner,
3183 data_converter,
3184 ..
3185 } => match Pin::new(inner).poll(cx) {
3186 Poll::Pending => Poll::Pending,
3187 Poll::Ready(result) => Poll::Ready({
3188 let status = result.status.ok_or_else(|| {
3189 data_converter
3190 .to_error(
3191 &SerializationContextData::Workflow(
3192 WorkflowSerializationContext::new(),
3193 ),
3194 Failure {
3195 message: "Child workflow completed without a status"
3196 .to_string(),
3197 ..Default::default()
3198 },
3199 ChildWorkflowExecutionDecodeHint::default(),
3200 )
3201 .expect("synthetic child workflow failure should decode")
3202 })?;
3203 match status {
3204 child_workflow_result::Status::Completed(success) => {
3205 let payloads = success.result.into_iter().collect();
3206 let context_data = SerializationContextData::Workflow(
3207 WorkflowSerializationContext::new(),
3208 );
3209 let ctx = SerializationContext::new(
3210 &context_data,
3211 data_converter.payload_converter(),
3212 );
3213 data_converter
3214 .payload_converter()
3215 .from_payloads::<Output>(&ctx, payloads)
3216 .map_err(ChildWorkflowExecutionError::Serialization)
3217 }
3218 child_workflow_result::Status::Failed(f) => {
3219 Err(data_converter.to_error(
3220 &SerializationContextData::Workflow(
3221 WorkflowSerializationContext::new(),
3222 ),
3223 f.failure.unwrap_or_default(),
3224 ChildWorkflowExecutionDecodeHint::default(),
3225 )?)
3226 }
3227 child_workflow_result::Status::Cancelled(c) => Err(data_converter
3228 .to_error(
3229 &SerializationContextData::Workflow(
3230 WorkflowSerializationContext::new(),
3231 ),
3232 c.failure.unwrap_or_default(),
3233 ChildWorkflowExecutionDecodeHint::default(),
3234 )?),
3235 }
3236 }),
3237 },
3238 ChildWorkflowFut::Terminated => panic!("polled after termination"),
3239 };
3240 if poll.is_ready() {
3241 *this = ChildWorkflowFut::Terminated;
3242 }
3243 poll
3244 }
3245}
3246
3247impl<F, Output> FusedFuture for ChildWorkflowFut<F, Output>
3248where
3249 F: Future<Output = ChildWorkflowResult> + Unpin,
3250 Output: TemporalDeserializable + 'static,
3251{
3252 fn is_terminated(&self) -> bool {
3253 matches!(self, ChildWorkflowFut::Terminated)
3254 }
3255}
3256
3257impl<F, Output> CancellableFutureWithReason for ChildWorkflowFut<F, Output>
3258where
3259 F: CancellableFutureWithReason<Output = ChildWorkflowResult> + Unpin,
3260 Output: TemporalDeserializable + 'static,
3261{
3262 fn cancel_with_reason(&self, reason: String) {
3263 if let ChildWorkflowFut::Running { inner, .. } = self {
3264 inner.cancel_with_reason(reason)
3265 }
3266 }
3267}
3268
3269impl<F, Output> CancellableFuture for ChildWorkflowFut<F, Output>
3270where
3271 F: CancellableFutureWithReason<Output = ChildWorkflowResult> + Unpin,
3272 Output: TemporalDeserializable + 'static,
3273{
3274 fn cancel(&self) {
3275 if let ChildWorkflowFut::Running { inner, .. } = self {
3276 inner.cancel()
3277 }
3278 }
3279}
3280
3281enum ChildWorkflowStartFut<F, WD: WorkflowDefinition> {
3284 Errored {
3286 error: Option<Box<ChildWorkflowStartError>>,
3287 _phantom: PhantomData<WD>,
3288 },
3289 Running(F),
3290 Terminated,
3291}
3292
3293impl<F, WD: WorkflowDefinition> ChildWorkflowStartFut<F, WD> {
3294 fn eager(err: ChildWorkflowStartError) -> Self {
3295 Self::Errored {
3296 error: Some(Box::new(err)),
3297 _phantom: PhantomData,
3298 }
3299 }
3300}
3301
3302impl<F, WD: WorkflowDefinition> Unpin for ChildWorkflowStartFut<F, WD> where F: Unpin {}
3303
3304impl<F, WD> Future for ChildWorkflowStartFut<F, WD>
3305where
3306 F: Future<Output = PendingChildWorkflow<WD>> + Unpin,
3307 WD: WorkflowDefinition,
3308{
3309 type Output = StartChildWorkflowResult;
3310
3311 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
3312 let this = self.get_mut();
3313 let poll = match this {
3314 ChildWorkflowStartFut::Errored { error, .. } => {
3315 Poll::Ready(Err(*error.take().expect("polled after completion")))
3316 }
3317 ChildWorkflowStartFut::Running(inner) => {
3318 match Pin::new(inner).poll(cx) {
3319 Poll::Pending => Poll::Pending,
3320 Poll::Ready(pending) => Poll::Ready(match pending.status {
3321 ChildWorkflowStartStatus::Succeeded(s) => {
3322 let ChildWfCommon {
3323 workflow_id,
3324 child_seq,
3325 result_future,
3326 base_ctx,
3327 } = pending.common;
3328 Ok(StartChildWorkflowOutput {
3329 run_id: s.run_id,
3330 result_future,
3331 workflow_id,
3332 child_seq,
3333 base_ctx,
3334 })
3335 }
3336 ChildWorkflowStartStatus::Failed(f) => {
3337 let mut result_future = pending.common.result_future;
3338 result_future.unregister_cancellation();
3339 Err(ChildWorkflowStartError::StartFailed {
3340 workflow_id: f.workflow_id,
3341 workflow_type: f.workflow_type,
3342 cause: match f.cause {
3343 cause if cause == ProtoStartChildCause::Unspecified as i32 => {
3344 StartChildWorkflowExecutionFailedCause::Unspecified
3345 }
3346 cause
3347 if cause
3348 == ProtoStartChildCause::WorkflowAlreadyExists as i32 =>
3349 {
3350 StartChildWorkflowExecutionFailedCause::WorkflowAlreadyExists
3351 }
3352 _ => StartChildWorkflowExecutionFailedCause::Unknown,
3353 },
3354 })
3355 }
3356 ChildWorkflowStartStatus::Cancelled(c) => {
3357 let ChildWfCommon {
3358 mut result_future,
3359 base_ctx,
3360 ..
3361 } = pending.common;
3362 result_future.unregister_cancellation();
3363 Err(base_ctx.data_converter().to_error(
3364 &SerializationContextData::Workflow(
3365 WorkflowSerializationContext::new(),
3366 ),
3367 c.failure.unwrap_or_default(),
3368 ChildWorkflowStartDecodeHint::default(),
3369 )?)
3370 }
3371 }),
3372 }
3373 }
3374 ChildWorkflowStartFut::Terminated => panic!("polled after termination"),
3375 };
3376 if poll.is_ready() {
3377 *this = ChildWorkflowStartFut::Terminated;
3378 }
3379 poll
3380 }
3381}
3382
3383impl<F, WD> FusedFuture for ChildWorkflowStartFut<F, WD>
3384where
3385 F: Future<Output = PendingChildWorkflow<WD>> + Unpin,
3386 WD: WorkflowDefinition,
3387{
3388 fn is_terminated(&self) -> bool {
3389 matches!(self, ChildWorkflowStartFut::Terminated)
3390 }
3391}
3392
3393impl<F, WD> CancellableFuture for ChildWorkflowStartFut<F, WD>
3394where
3395 F: CancellableFutureWithReason<Output = PendingChildWorkflow<WD>> + Unpin,
3396 WD: WorkflowDefinition,
3397{
3398 fn cancel(&self) {
3399 if let ChildWorkflowStartFut::Running(inner) = self {
3400 inner.cancel()
3401 }
3402 }
3403}
3404
3405impl<F, WD> CancellableFutureWithReason for ChildWorkflowStartFut<F, WD>
3406where
3407 F: CancellableFutureWithReason<Output = PendingChildWorkflow<WD>> + Unpin,
3408 WD: WorkflowDefinition,
3409{
3410 fn cancel_with_reason(&self, reason: String) {
3411 if let ChildWorkflowStartFut::Running(inner) = self {
3412 inner.cancel_with_reason(reason)
3413 }
3414 }
3415}
3416
3417enum SignalChildFut<F> {
3419 Running {
3420 inner: F,
3421 data_converter: DataConverter,
3422 },
3423 Terminated,
3424}
3425
3426impl<F> Unpin for SignalChildFut<F> where F: Unpin {}
3427
3428impl<F> Future for SignalChildFut<F>
3429where
3430 F: Future<Output = SignalExternalWfResult> + Unpin,
3431{
3432 type Output = Result<(), WorkflowSignalError>;
3433
3434 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
3435 let this = self.get_mut();
3436 let poll = match this {
3437 SignalChildFut::Running {
3438 inner,
3439 data_converter,
3440 } => match Pin::new(inner).poll(cx) {
3441 Poll::Pending => Poll::Pending,
3442 Poll::Ready(Ok(_)) => Poll::Ready(Ok(())),
3443 Poll::Ready(Err(error)) => Poll::Ready(Err(data_converter.to_error(
3444 &SerializationContextData::Workflow(WorkflowSerializationContext::new()),
3445 error.failure,
3446 WorkflowSignalDecodeHint::new(error.cause),
3447 )?)),
3448 },
3449 SignalChildFut::Terminated => panic!("polled after termination"),
3450 };
3451 if poll.is_ready() {
3452 *this = SignalChildFut::Terminated;
3453 }
3454 poll
3455 }
3456}
3457
3458impl<F> FusedFuture for SignalChildFut<F>
3459where
3460 F: Future<Output = SignalExternalWfResult> + Unpin,
3461{
3462 fn is_terminated(&self) -> bool {
3463 matches!(self, SignalChildFut::Terminated)
3464 }
3465}
3466
3467impl<F> CancellableFuture for SignalChildFut<F>
3468where
3469 F: CancellableFuture<Output = SignalExternalWfResult> + Unpin,
3470{
3471 fn cancel(&self) {
3472 if let SignalChildFut::Running { inner, .. } = self {
3473 inner.cancel()
3474 }
3475 }
3476}
3477
3478impl<WD: WorkflowDefinition> StartedChildWorkflow<WD>
3479where
3480 WD::Output: TemporalDeserializable + 'static,
3481{
3482 pub fn result(
3485 self,
3486 ) -> impl CancellableFutureWithReason<Output = Result<WD::Output, ChildWorkflowExecutionError>>
3487 {
3488 self.result_future.map(|result| {
3489 result.and_then(|output| {
3490 output
3491 .downcast::<WD::Output>()
3492 .map(|output| *output)
3493 .map_err(|_| {
3494 ChildWorkflowExecutionError::Serialization(outbound_type_error(
3495 "child workflow output",
3496 ))
3497 })
3498 })
3499 })
3500 }
3501
3502 pub fn cancel(&self, reason: String) {
3504 self.base_ctx.cancel(CancellableID::ChildWorkflow {
3505 seqnum: self.child_seq,
3506 reason,
3507 });
3508 }
3509
3510 pub fn signal<S: SignalDefinition<Workflow = WD> + 'static>(
3514 &self,
3515 signal: S,
3516 input: S::Input,
3517 options: SignalWorkflowOptions,
3518 ) -> impl CancellableFuture<Output = Result<(), WorkflowSignalError>> + 'static {
3519 self.base_ctx.signal_workflow(
3520 SignalWorkflowTarget::Child {
3521 workflow_id: self.workflow_id.clone(),
3522 },
3523 signal,
3524 input,
3525 options,
3526 )
3527 }
3528}
3529
3530#[derive(derive_more::Debug)]
3536pub struct ExternalWorkflowHandle {
3537 workflow_id: String,
3538 run_id: Option<String>,
3539 namespace: String,
3540 #[debug(skip)]
3541 base_ctx: BaseWorkflowContext,
3542}
3543
3544impl ExternalWorkflowHandle {
3545 pub fn workflow_id(&self) -> &str {
3547 &self.workflow_id
3548 }
3549
3550 pub fn run_id(&self) -> Option<&str> {
3552 self.run_id.as_deref()
3553 }
3554
3555 pub fn signal<S: SignalDefinition + 'static>(
3559 &self,
3560 signal: S,
3561 input: S::Input,
3562 options: SignalWorkflowOptions,
3563 ) -> impl CancellableFuture<Output = Result<(), WorkflowSignalError>> + 'static {
3564 self.base_ctx.signal_workflow(
3565 SignalWorkflowTarget::External {
3566 namespace: self.namespace.clone(),
3567 workflow_id: self.workflow_id.clone(),
3568 run_id: self.run_id.clone(),
3569 },
3570 signal,
3571 input,
3572 options,
3573 )
3574 }
3575
3576 pub fn cancel(
3578 &self,
3579 reason: Option<String>,
3580 ) -> impl FusedFuture<Output = CancelExternalWorkflowResult> {
3581 self.base_ctx
3582 .cancel_external_workflow(CancelExternalWorkflowInput {
3583 workflow_id: self.workflow_id.clone(),
3584 run_id: self.run_id.clone(),
3585 reason,
3586 })
3587 }
3588}
3589
3590#[cfg(test)]
3591mod tests {
3592 use super::*;
3593 use crate::MemoValues;
3594 use std::{
3595 collections::HashMap,
3596 sync::{
3597 Mutex,
3598 atomic::{AtomicUsize, Ordering as AtomicOrdering},
3599 },
3600 task::Wake,
3601 time::Duration,
3602 };
3603 use temporalio_common_wasm::{
3604 data_converters::{TemporalDeserializable, TemporalSerializable},
3605 error::OutgoingWorkflowError,
3606 protos::{
3607 coresdk::{
3608 AsJsonPayloadExt, FromJsonPayloadExt,
3609 common::VersioningIntent as ProtoVersioningIntent,
3610 workflow_activation::{UpdateRandomSeed, WorkflowActivationJob},
3611 workflow_commands::WorkflowCommand,
3612 },
3613 temporal::api::{
3614 common::v1::Payload,
3615 enums::v1::ContinueAsNewVersioningBehavior as ProtoContinueAsNewVersioningBehavior,
3616 },
3617 },
3618 };
3619 use temporalio_macros::{workflow, workflow_methods};
3620
3621 #[derive(Default)]
3622 struct NoopHost;
3623
3624 struct CountingWake(Arc<AtomicUsize>);
3625
3626 impl Wake for CountingWake {
3627 fn wake(self: Arc<Self>) {
3628 self.wake_by_ref();
3629 }
3630
3631 fn wake_by_ref(self: &Arc<Self>) {
3632 self.0.fetch_add(1, AtomicOrdering::Relaxed);
3633 }
3634 }
3635
3636 impl WorkflowHost for NoopHost {
3637 fn set_current_details(&self, _details: String) {}
3638 fn push_command(&self, _command: WorkflowCommand) {}
3639 }
3640
3641 #[derive(Default)]
3642 struct RecordingHost {
3643 commands: Rc<RefCell<Vec<WorkflowCommand>>>,
3644 }
3645
3646 impl WorkflowHost for RecordingHost {
3647 fn set_current_details(&self, _details: String) {}
3648
3649 fn push_command(&self, command: WorkflowCommand) {
3650 self.commands.borrow_mut().push(command);
3651 }
3652 }
3653
3654 #[derive(Debug)]
3655 struct FailingMemoValue;
3656
3657 impl TemporalSerializable for FailingMemoValue {
3658 fn to_payload(
3659 &self,
3660 _ctx: &temporalio_common_wasm::data_converters::SerializationContext<'_>,
3661 ) -> Result<Payload, temporalio_common_wasm::data_converters::PayloadConversionError>
3662 {
3663 Err(
3664 temporalio_common_wasm::data_converters::PayloadConversionError::EncodingError(
3665 std::io::Error::other("memo serialization failure").into(),
3666 ),
3667 )
3668 }
3669 }
3670
3671 #[workflow]
3672 #[derive(Default)]
3673 struct TestWorkflow;
3674
3675 #[workflow_methods]
3676 impl TestWorkflow {
3677 #[run]
3678 async fn run(_ctx: &mut WorkflowContext<Self>, _input: u8) -> crate::WorkflowResult<()> {
3679 unreachable!("test workflow run should not be polled")
3680 }
3681
3682 #[signal]
3683 fn test_signal(&mut self, _ctx: &mut SyncWorkflowContext<Self>, _input: String) {
3684 unreachable!("test workflow signal should not be dispatched")
3685 }
3686 }
3687
3688 fn test_context() -> WorkflowContext<TestWorkflow> {
3689 test_context_with_seed(0)
3690 }
3691
3692 fn test_context_with_seed(randomness_seed: u64) -> WorkflowContext<TestWorkflow> {
3693 let init = InitializeWorkflow {
3694 workflow_type: TestWorkflow.name().to_string(),
3695 randomness_seed,
3696 ..Default::default()
3697 };
3698 let init = WorkflowInit {
3699 namespace: "default".to_string(),
3700 task_queue: "orig-task-queue".to_string(),
3701 run_id: "run-id".to_string(),
3702 initialize_workflow: init,
3703 };
3704 let base = BaseWorkflowContext::from_raw(
3705 init,
3706 DataConverter::default(),
3707 Rc::new(NoopHost),
3708 None,
3709 Vec::new(),
3710 );
3711 WorkflowContext::from_base(base, Rc::new(RefCell::new(TestWorkflow)))
3712 }
3713
3714 fn patch_test_context(
3715 callback: Option<PatchActivationCallback>,
3716 ) -> (
3717 BaseWorkflowContext,
3718 WorkflowContext<TestWorkflow>,
3719 Rc<RefCell<Vec<WorkflowCommand>>>,
3720 ) {
3721 let init = InitializeWorkflow {
3722 workflow_id: "workflow-id".to_string(),
3723 workflow_type: TestWorkflow.name().to_string(),
3724 ..Default::default()
3725 };
3726 let host = Rc::new(RecordingHost::default());
3727 let commands = host.commands.clone();
3728 let init = WorkflowInit {
3729 namespace: "default".to_string(),
3730 task_queue: "task-queue".to_string(),
3731 run_id: "run-id".to_string(),
3732 initialize_workflow: init,
3733 };
3734 let base = BaseWorkflowContext::from_raw(
3735 init,
3736 DataConverter::default(),
3737 host,
3738 callback,
3739 Vec::new(),
3740 );
3741
3742 let ctx = WorkflowContext::from_base(base.clone(), Rc::new(RefCell::new(TestWorkflow)));
3743 (base, ctx, commands)
3744 }
3745
3746 struct ShortCircuitFirstTimer {
3747 calls: AtomicUsize,
3748 }
3749
3750 impl WorkflowInterceptor for ShortCircuitFirstTimer {
3751 fn start_timer(
3752 &self,
3753 _ctx: WorkflowInterceptorContext,
3754 input: StartTimerInput,
3755 next: WorkflowNext<
3756 'static,
3757 StartTimerInput,
3758 CancellableWorkflowOutboundFuture<TimerResult>,
3759 >,
3760 ) -> CancellableWorkflowOutboundFuture<TimerResult> {
3761 if self.calls.fetch_add(1, Ordering::Relaxed) == 0 {
3762 CancellableWorkflowOutboundFuture::new(
3763 async { TimerResult::Cancelled },
3764 WorkflowCancellationHandle::new(|_| {}),
3765 )
3766 } else {
3767 next.run(input)
3768 }
3769 }
3770 }
3771
3772 #[test]
3773 fn short_circuited_outbound_call_does_not_consume_sequence_number() {
3774 let host = Rc::new(RecordingHost::default());
3775 let init = InitializeWorkflow {
3776 workflow_type: TestWorkflow.name().to_string(),
3777 ..Default::default()
3778 };
3779 let init = WorkflowInit {
3780 namespace: "default".to_string(),
3781 task_queue: "task-queue".to_string(),
3782 run_id: "run-id".to_string(),
3783 initialize_workflow: init,
3784 };
3785 let base = BaseWorkflowContext::from_raw(
3786 init,
3787 DataConverter::default(),
3788 host.clone(),
3789 None,
3790 vec![WorkflowInterceptorConstructor::new(|_| {
3791 ShortCircuitFirstTimer {
3792 calls: AtomicUsize::new(0),
3793 }
3794 })],
3795 );
3796
3797 let first = base.timer(Duration::from_secs(1));
3798 assert_eq!(first.now_or_never(), Some(TimerResult::Cancelled));
3799 let _second = base.timer(Duration::from_secs(1));
3800
3801 let commands = host.commands.borrow();
3802 assert_eq!(commands.len(), 1);
3803 let Some(workflow_command::Variant::StartTimer(timer)) = &commands[0].variant else {
3804 panic!("expected start timer command");
3805 };
3806 assert_eq!(timer.seq, 1);
3807 }
3808
3809 #[cfg(feature = "experimental")]
3810 mod experimental_operation_tests {
3811 use super::*;
3812 use temporalio_common_wasm::protos::{
3813 coresdk::workflow_activation::{
3814 ResolveChildWorkflowExecutionStartSuccess, resolve_nexus_operation_start,
3815 },
3816 temporal::api::sdk::v1::{EventGroupMarker, event_group_marker},
3817 };
3818
3819 struct TestActivity;
3820
3821 impl ActivityDefinition for TestActivity {
3822 type Input = ();
3823 type Output = ();
3824
3825 fn name(&self) -> &str {
3826 "test_activity"
3827 }
3828 }
3829
3830 #[test]
3831 fn custom_token_cancels_command_backed_operations() {
3832 let host = Rc::new(RecordingHost::default());
3833 let init = WorkflowInit {
3834 namespace: "default".to_string(),
3835 task_queue: "task-queue".to_string(),
3836 run_id: "run-id".to_string(),
3837 initialize_workflow: InitializeWorkflow {
3838 workflow_type: TestWorkflow.name().to_string(),
3839 ..Default::default()
3840 },
3841 };
3842 let base = BaseWorkflowContext::from_raw(
3843 init,
3844 DataConverter::default(),
3845 host.clone(),
3846 None,
3847 Vec::new(),
3848 );
3849 let token = WorkflowCancellationToken::new();
3850
3851 let timer = base.timer(TimerOptions {
3852 duration: Duration::from_secs(1),
3853 cancellation_token: Some(token.clone()),
3854 summary: None,
3855 event_group_markers: vec![],
3856 });
3857
3858 let mut activity_options =
3859 ActivityOptions::start_to_close_timeout(Duration::from_secs(1));
3860 activity_options.cancellation_token = Some(token.clone());
3861 let activity = base.execute_activity(TestActivity, (), activity_options);
3862
3863 let mut local_activity_options = LocalActivityOptions {
3864 schedule_to_close_timeout: Some(Duration::from_secs(1)),
3865 ..Default::default()
3866 };
3867 local_activity_options.cancellation_token = Some(token.clone());
3868 let local_activity =
3869 base.execute_local_activity(TestActivity, (), local_activity_options);
3870
3871 let child_options = ChildWorkflowOptions {
3872 cancellation_token: Some(token.clone()),
3873 ..Default::default()
3874 };
3875 let child = base.start_child_workflow(TestWorkflow::run, 1, child_options);
3876
3877 let signal = base.external_workflow("external", None).signal(
3878 TestWorkflow::test_signal,
3879 "input".to_string(),
3880 SignalWorkflowOptions::builder()
3881 .cancellation_token(token.clone())
3882 .build(),
3883 );
3884
3885 let nexus_options = NexusOperationOptions::builder()
3886 .endpoint("endpoint")
3887 .service("service")
3888 .operation("operation")
3889 .cancellation_token(token.clone())
3890 .build();
3891 let nexus = base.start_nexus_operation(nexus_options);
3892
3893 token.cancel_with_reason("group cancelled");
3894 timer.cancel();
3895 activity.cancel();
3896 local_activity.cancel();
3897 child.cancel_with_reason("explicit cancellation".to_string());
3898 signal.cancel();
3899 nexus.cancel();
3900
3901 let commands = host.commands.borrow();
3902 assert_eq!(
3903 commands
3904 .iter()
3905 .filter(|command| matches!(
3906 &command.variant,
3907 Some(workflow_command::Variant::CancelTimer(_))
3908 ))
3909 .count(),
3910 1
3911 );
3912 assert_eq!(
3913 commands
3914 .iter()
3915 .filter(|command| matches!(
3916 &command.variant,
3917 Some(workflow_command::Variant::RequestCancelActivity(_))
3918 ))
3919 .count(),
3920 1
3921 );
3922 assert_eq!(
3923 commands
3924 .iter()
3925 .filter(|command| matches!(
3926 &command.variant,
3927 Some(workflow_command::Variant::RequestCancelLocalActivity(_))
3928 ))
3929 .count(),
3930 1
3931 );
3932 let child_cancellations = commands
3933 .iter()
3934 .filter_map(|command| match &command.variant {
3935 Some(workflow_command::Variant::CancelChildWorkflowExecution(cancel)) => {
3936 Some(cancel)
3937 }
3938 _ => None,
3939 })
3940 .collect::<Vec<_>>();
3941 assert_eq!(child_cancellations.len(), 1);
3942 assert_eq!(child_cancellations[0].reason, "group cancelled");
3943 assert_eq!(
3944 commands
3945 .iter()
3946 .filter(|command| matches!(
3947 &command.variant,
3948 Some(workflow_command::Variant::CancelSignalWorkflow(_))
3949 ))
3950 .count(),
3951 1
3952 );
3953 assert_eq!(
3954 commands
3955 .iter()
3956 .filter(|command| matches!(
3957 &command.variant,
3958 Some(workflow_command::Variant::RequestCancelNexusOperation(_))
3959 ))
3960 .count(),
3961 1
3962 );
3963 }
3964
3965 #[test]
3966 fn child_and_nexus_tokens_remain_active_after_start() {
3967 let host = Rc::new(RecordingHost::default());
3968 let init = WorkflowInit {
3969 namespace: "default".to_string(),
3970 task_queue: "task-queue".to_string(),
3971 run_id: "run-id".to_string(),
3972 initialize_workflow: InitializeWorkflow {
3973 workflow_type: TestWorkflow.name().to_string(),
3974 ..Default::default()
3975 },
3976 };
3977 let base = BaseWorkflowContext::from_raw(
3978 init,
3979 DataConverter::default(),
3980 host.clone(),
3981 None,
3982 Vec::new(),
3983 );
3984
3985 let child_token = WorkflowCancellationToken::new();
3986 let child_options = ChildWorkflowOptions {
3987 cancellation_token: Some(child_token.clone()),
3988 ..Default::default()
3989 };
3990 let child = base.start_child_workflow(TestWorkflow::run, 1, child_options);
3991 base.unblock(UnblockEvent::WorkflowStart(
3992 1,
3993 Box::new(ChildWorkflowStartStatus::Succeeded(
3994 ResolveChildWorkflowExecutionStartSuccess {
3995 run_id: "child-run".to_string(),
3996 },
3997 )),
3998 ))
3999 .unwrap();
4000 let started_child = child
4001 .now_or_never()
4002 .expect("child start should resolve")
4003 .unwrap();
4004 child_token.cancel();
4005 started_child.cancel("explicit cancellation".to_string());
4006
4007 let nexus_token = WorkflowCancellationToken::new();
4008 let nexus_options = NexusOperationOptions::builder()
4009 .endpoint("endpoint")
4010 .service("service")
4011 .operation("operation")
4012 .cancellation_token(nexus_token.clone())
4013 .build();
4014 let nexus = base.start_nexus_operation(nexus_options);
4015 base.unblock(UnblockEvent::NexusOperationStart(
4016 1,
4017 Box::new(resolve_nexus_operation_start::Status::OperationToken(
4018 "operation-token".to_string(),
4019 )),
4020 ))
4021 .unwrap();
4022 let started_nexus = nexus
4023 .now_or_never()
4024 .expect("Nexus start should resolve")
4025 .unwrap();
4026 nexus_token.cancel();
4027 started_nexus.cancel();
4028
4029 let commands = host.commands.borrow();
4030 assert_eq!(
4031 commands
4032 .iter()
4033 .filter(|command| matches!(
4034 &command.variant,
4035 Some(workflow_command::Variant::CancelChildWorkflowExecution(_))
4036 ))
4037 .count(),
4038 1
4039 );
4040 assert_eq!(
4041 commands
4042 .iter()
4043 .filter(|command| matches!(
4044 &command.variant,
4045 Some(workflow_command::Variant::RequestCancelNexusOperation(_))
4046 ))
4047 .count(),
4048 1
4049 );
4050 }
4051
4052 #[test]
4053 fn local_activity_token_cancels_retry_backoff_timer() {
4054 let host = Rc::new(RecordingHost::default());
4055 let init = WorkflowInit {
4056 namespace: "default".to_string(),
4057 task_queue: "task-queue".to_string(),
4058 run_id: "run-id".to_string(),
4059 initialize_workflow: InitializeWorkflow {
4060 workflow_type: TestWorkflow.name().to_string(),
4061 ..Default::default()
4062 },
4063 };
4064 let base = BaseWorkflowContext::from_raw(
4065 init,
4066 DataConverter::default(),
4067 host.clone(),
4068 None,
4069 Vec::new(),
4070 );
4071 let token = WorkflowCancellationToken::new();
4072 let marker = EventGroupMarker {
4073 variant: Some(event_group_marker::Variant::Label(
4074 event_group_marker::Label {
4075 id: "la-group".to_string(),
4076 label: Some("la-group".as_json_payload().unwrap()),
4077 },
4078 )),
4079 };
4080 let mut options = LocalActivityOptions {
4081 schedule_to_close_timeout: Some(Duration::from_secs(10)),
4082 event_group_markers: vec![marker.clone()],
4083 ..Default::default()
4084 };
4085 options.cancellation_token = Some(token.clone());
4086 let activity = base.execute_local_activity(TestActivity, (), options);
4087 futures_util::pin_mut!(activity);
4088 base.unblock(UnblockEvent::Activity(
4089 1,
4090 Box::new(ActivityResolution {
4091 status: Some(activity_resolution::Status::Backoff(
4092 temporalio_common_wasm::protos::coresdk::activity_result::DoBackoff {
4093 attempt: 2,
4094 backoff_duration: Some(Duration::from_secs(5).try_into().unwrap()),
4095 original_schedule_time: None,
4096 },
4097 )),
4098 }),
4099 ))
4100 .unwrap();
4101
4102 assert!(activity.as_mut().now_or_never().is_none());
4103 token.cancel();
4104
4105 let commands = host.commands.borrow();
4106 assert!(commands.iter().any(|command| matches!(
4107 &command.variant,
4108 Some(workflow_command::Variant::CancelTimer(_))
4109 )));
4110
4111 let start_timer = commands
4112 .iter()
4113 .find(|command| {
4114 matches!(
4115 &command.variant,
4116 Some(workflow_command::Variant::StartTimer(_))
4117 )
4118 })
4119 .expect("backoff StartTimer is issued");
4120 assert_eq!(start_timer.event_group_markers, [marker]);
4121 }
4122 }
4123
4124 #[test]
4125 fn patch_activation_callback_activates_and_memoizes() {
4126 let calls = Arc::new(AtomicUsize::new(0));
4127 let input = Arc::new(Mutex::new(None));
4128 let callback_calls = calls.clone();
4129 let callback_input = input.clone();
4130 let callback: PatchActivationCallback = Arc::new(move |value| {
4131 assert!(matches!(
4132 value.workflow_info.random_stream("plugin").source,
4133 WorkflowRandomStreamSource::System(_)
4134 ));
4135 callback_calls.fetch_add(1, AtomicOrdering::Relaxed);
4136 *callback_input.lock().unwrap() = Some((
4137 value.workflow_info.workflow_id().to_string(),
4138 value.workflow_info.run_id().to_string(),
4139 value.patch_id,
4140 ));
4141 true
4142 });
4143 let (_, ctx, commands) = patch_test_context(Some(callback));
4144
4145 assert!(ctx.patched("my-patch"));
4146 assert!(ctx.patched("my-patch"));
4147 assert_eq!(calls.load(AtomicOrdering::Relaxed), 1);
4148 assert_eq!(commands.borrow().len(), 1);
4149 let input = input.lock().unwrap();
4150 let input = input.as_ref().unwrap();
4151 assert_eq!(input.0, "workflow-id");
4152 assert_eq!(input.1, "run-id");
4153 assert_eq!(input.2, "my-patch");
4154 }
4155
4156 #[test]
4157 fn patch_activation_callback_can_decline_and_memoizes() {
4158 let calls = Arc::new(AtomicUsize::new(0));
4159 let callback_calls = calls.clone();
4160 let callback: PatchActivationCallback = Arc::new(move |_| {
4161 callback_calls.fetch_add(1, AtomicOrdering::Relaxed);
4162 false
4163 });
4164 let (_, ctx, commands) = patch_test_context(Some(callback));
4165
4166 assert!(!ctx.patched("my-patch"));
4167 assert!(!ctx.patched("my-patch"));
4168 assert_eq!(calls.load(AtomicOrdering::Relaxed), 1);
4169 assert!(commands.borrow().is_empty());
4170 }
4171
4172 #[test]
4173 fn patch_activation_callback_bypasses_history_and_deprecation() {
4174 let callback: PatchActivationCallback = Arc::new(|_| panic!("callback must not run"));
4175
4176 let (base, ctx, commands) = patch_test_context(Some(callback.clone()));
4177 base.apply_activation_context(
4178 &CoreWorkflowActivation {
4179 is_replaying: true,
4180 ..Default::default()
4181 },
4182 true,
4183 );
4184 assert!(!ctx.patched("replay-patch"));
4185 assert!(commands.borrow().is_empty());
4186
4187 let (base, ctx, commands) = patch_test_context(Some(callback.clone()));
4188 base.apply_activation_context(
4189 &CoreWorkflowActivation {
4190 is_replaying: true,
4191 ..Default::default()
4192 },
4193 true,
4194 );
4195 base.notify_patch("existing-patch".to_string());
4196 assert!(ctx.patched("existing-patch"));
4197 assert_eq!(commands.borrow().len(), 1);
4198
4199 let (_, ctx, commands) = patch_test_context(Some(callback));
4200 assert!(ctx.deprecate_patch("deprecated-patch"));
4201 assert_eq!(commands.borrow().len(), 1);
4202 }
4203
4204 #[test]
4205 fn patch_activation_defaults_to_active() {
4206 let (_, ctx, commands) = patch_test_context(None);
4207
4208 assert!(ctx.patched("my-patch"));
4209 assert_eq!(commands.borrow().len(), 1);
4210 }
4211
4212 #[test]
4213 fn random_is_deterministic_for_supported_numeric_types() {
4214 let first = test_context_with_seed(42);
4215 let second = test_context_with_seed(42);
4216
4217 assert_eq!(first.random::<u8>(), second.random::<u8>());
4218 assert_eq!(first.random::<i64>(), second.random::<i64>());
4219 assert_eq!(first.random::<u128>(), second.random::<u128>());
4220 assert_eq!(first.random::<f32>(), second.random::<f32>());
4221 assert_eq!(first.random::<f64>(), second.random::<f64>());
4222 assert_eq!(first.uuid4(), second.uuid4());
4223 }
4224
4225 #[test]
4226 fn random_is_reseeded_by_activation() {
4227 let ctx = test_context_with_seed(123);
4228 let expected = ctx.random::<u64>();
4229 let activation = CoreWorkflowActivation {
4230 jobs: vec![WorkflowActivationJob {
4231 variant: Some(ActivationVariant::UpdateRandomSeed(UpdateRandomSeed {
4232 randomness_seed: 123,
4233 })),
4234 }],
4235 ..Default::default()
4236 };
4237
4238 ctx.sync.base.apply_activation_context(&activation, false);
4239
4240 assert_eq!(ctx.random::<u64>(), expected);
4241 }
4242
4243 #[test]
4244 fn named_random_lookup_continues_the_same_stream() {
4245 let ctx = test_context_with_seed(42);
4246 let first_lookup = ctx.random_stream("orders");
4247 let first = first_lookup.random::<u64>();
4248 let second = ctx.random_stream("orders").random::<u64>();
4249
4250 let expected = test_context_with_seed(42).random_stream("orders");
4251 assert_eq!(first, expected.random::<u64>());
4252 assert_eq!(second, expected.random::<u64>());
4253 }
4254
4255 #[test]
4256 fn named_random_sequence_is_stable() {
4257 let stream = test_context_with_seed(42).random_stream("example.com/orders");
4258
4259 assert_eq!(stream.random::<u64>(), 18_054_372_068_998_079_507);
4261 }
4262
4263 #[test]
4264 fn named_random_streams_are_isolated() {
4265 let ctx = test_context_with_seed(42);
4266 let alpha = ctx.random_stream("alpha");
4267 let first_alpha = alpha.random::<u64>();
4268 let _ = ctx.random_stream("beta").random::<u64>();
4269 let second_alpha = alpha.random::<u64>();
4270
4271 let expected_ctx = test_context_with_seed(42);
4272 let expected_alpha = expected_ctx.random_stream("alpha");
4273 assert_eq!(first_alpha, expected_alpha.random::<u64>());
4274 assert_eq!(second_alpha, expected_alpha.random::<u64>());
4275 assert_ne!(
4276 test_context_with_seed(42)
4277 .random_stream("alpha")
4278 .random::<u64>(),
4279 test_context_with_seed(42)
4280 .random_stream("beta")
4281 .random::<u64>()
4282 );
4283 }
4284
4285 #[test]
4286 fn named_random_does_not_advance_default_randomness() {
4287 let ctx = test_context_with_seed(42);
4288 let first = ctx.random::<u64>();
4289 let _ = ctx.random_stream("plugin").random::<u64>();
4290 let second = ctx.random::<u64>();
4291
4292 let expected = test_context_with_seed(42);
4293 assert_eq!(first, expected.random::<u64>());
4294 assert_eq!(second, expected.random::<u64>());
4295 }
4296
4297 #[test]
4298 fn interceptor_context_shares_named_random_stream_state() {
4299 let ctx = test_context_with_seed(42);
4300 let first = ctx.random_stream("plugin").random::<u64>();
4301 let interceptor_ctx =
4302 crate::workflow_interceptors::WorkflowInterceptorContext::new(ctx.sync.base.clone());
4303 let second = interceptor_ctx.random_stream("plugin").random::<u64>();
4304
4305 let expected = test_context_with_seed(42).random_stream("plugin");
4306 assert_eq!(first, expected.random::<u64>());
4307 assert_eq!(second, expected.random::<u64>());
4308 }
4309
4310 #[test]
4311 fn replay_safe_context_view_shares_workflow_randomness() {
4312 let ctx = test_context_with_seed(42);
4313 let first = ctx.sync.base.view().random_stream("plugin").random::<u64>();
4314 let second = ctx.random_stream("plugin").random::<u64>();
4315
4316 let expected = test_context_with_seed(42).random_stream("plugin");
4317 assert_eq!(first, expected.random::<u64>());
4318 assert_eq!(second, expected.random::<u64>());
4319 }
4320
4321 #[test]
4322 fn read_only_context_view_does_not_advance_workflow_randomness() {
4323 let ctx = test_context_with_seed(42);
4324 let expected = test_context_with_seed(42)
4325 .random_stream("plugin")
4326 .random::<u64>();
4327
4328 {
4329 let _read_only = ctx.sync.base.enter_read_only();
4330 let _ = ctx.sync.base.view().random_stream("plugin").random::<u64>();
4331 }
4332
4333 assert_eq!(ctx.random_stream("plugin").random::<u64>(), expected);
4334 }
4335
4336 #[test]
4337 fn nested_read_only_scopes_restore_replay_safety() {
4338 let ctx = test_context_with_seed(42);
4339 assert!(ctx.sync.base.requires_replay_safety());
4340
4341 {
4342 let _outer = ctx.sync.base.enter_read_only();
4343 assert!(!ctx.sync.base.requires_replay_safety());
4344 {
4345 let _inner = ctx.sync.base.enter_read_only();
4346 assert!(!ctx.sync.base.requires_replay_safety());
4347 }
4348 assert!(!ctx.sync.base.requires_replay_safety());
4349 }
4350
4351 assert!(ctx.sync.base.requires_replay_safety());
4352 }
4353
4354 #[test]
4355 fn named_random_streams_are_reseeded_by_activation() {
4356 let ctx = test_context_with_seed(123);
4357 let stream = ctx.random_stream("orders");
4358 let _ = stream.random::<u64>();
4359 let activation = CoreWorkflowActivation {
4360 jobs: vec![WorkflowActivationJob {
4361 variant: Some(ActivationVariant::UpdateRandomSeed(UpdateRandomSeed {
4362 randomness_seed: 456,
4363 })),
4364 }],
4365 ..Default::default()
4366 };
4367
4368 ctx.sync.base.apply_activation_context(&activation, false);
4369
4370 let expected = test_context_with_seed(456).random_stream("orders");
4371 assert_eq!(stream.random::<u64>(), expected.random::<u64>());
4372 }
4373
4374 #[cfg(feature = "experimental")]
4375 mod experimental_interceptor_tests {
4376 use super::*;
4377 use crate::workflow_interceptors::StartNexusOperationInput;
4378
4379 struct MutatingRemainingOutboundInterceptor;
4380
4381 impl WorkflowInterceptor for MutatingRemainingOutboundInterceptor {
4382 fn signal_workflow(
4383 &self,
4384 _ctx: WorkflowInterceptorContext,
4385 mut input: SignalWorkflowInput,
4386 next: WorkflowNext<
4387 'static,
4388 SignalWorkflowInput,
4389 CancellableWorkflowOutboundFuture<SignalWorkflowResult>,
4390 >,
4391 ) -> CancellableWorkflowOutboundFuture<SignalWorkflowResult> {
4392 *input.signal_name_mut() = "mutated-signal".to_string();
4393 *input.input_mut::<String>().unwrap() = "mutated-input".to_string();
4394 *input.target_mut() = SignalWorkflowTarget::External {
4395 namespace: "mutated-namespace".to_string(),
4396 workflow_id: "mutated-workflow".to_string(),
4397 run_id: Some("mutated-run".to_string()),
4398 };
4399 input
4400 .headers_mut()
4401 .insert("signal-header".to_string(), Payload::default());
4402 next.run(input)
4403 }
4404
4405 fn cancel_external_workflow(
4406 &self,
4407 _ctx: WorkflowInterceptorContext,
4408 mut input: CancelExternalWorkflowInput,
4409 next: WorkflowNext<
4410 'static,
4411 CancelExternalWorkflowInput,
4412 WorkflowOutboundFuture<CancelExternalWorkflowResult>,
4413 >,
4414 ) -> WorkflowOutboundFuture<CancelExternalWorkflowResult> {
4415 input.workflow_id = "mutated-cancel-workflow".to_string();
4416 input.run_id = Some("mutated-cancel-run".to_string());
4417 input.reason = Some("mutated-reason".to_string());
4418 next.run(input)
4419 }
4420
4421 fn continue_as_new(
4422 &self,
4423 _ctx: crate::workflow_interceptors::SyncWorkflowInterceptorContext,
4424 mut input: ContinueAsNewInput,
4425 next: WorkflowNext<
4426 'static,
4427 ContinueAsNewInput,
4428 crate::workflow_interceptors::ContinueAsNewResult,
4429 >,
4430 ) -> crate::workflow_interceptors::ContinueAsNewResult {
4431 *input.input_mut::<u8>().unwrap() = 42;
4432 input.options_mut().workflow_type = Some("mutated-workflow-type".to_string());
4433 input.headers_mut().insert(
4434 "continue-header".to_string(),
4435 Payload::from(b"continue-header-value".as_slice()),
4436 );
4437 next.run(input)
4438 }
4439
4440 fn start_nexus_operation(
4441 &self,
4442 _ctx: WorkflowInterceptorContext,
4443 mut input: StartNexusOperationInput,
4444 next: WorkflowNext<
4445 'static,
4446 StartNexusOperationInput,
4447 CancellableWorkflowOutboundFuture<
4448 crate::workflow_interceptors::StartNexusOperationResult,
4449 >,
4450 >,
4451 ) -> CancellableWorkflowOutboundFuture<
4452 crate::workflow_interceptors::StartNexusOperationResult,
4453 > {
4454 input.options_mut().endpoint = "mutated-endpoint".to_string();
4455 input.options_mut().service = "mutated-service".to_string();
4456 input.options_mut().operation = "mutated-operation".to_string();
4457 next.run(input)
4458 }
4459 }
4460
4461 #[test]
4462 fn outbound_interceptors_mutate_signal_cancel_continue_as_new_and_nexus() {
4463 let host = Rc::new(RecordingHost::default());
4464 let init = InitializeWorkflow {
4465 workflow_type: TestWorkflow.name().to_string(),
4466 ..Default::default()
4467 };
4468 let init = WorkflowInit {
4469 namespace: "default".to_string(),
4470 task_queue: "task-queue".to_string(),
4471 run_id: "run-id".to_string(),
4472 initialize_workflow: init,
4473 };
4474 let base = BaseWorkflowContext::from_raw(
4475 init,
4476 DataConverter::default(),
4477 host.clone(),
4478 None,
4479 vec![WorkflowInterceptorConstructor::new(|_| {
4480 MutatingRemainingOutboundInterceptor
4481 })],
4482 );
4483 let ctx = WorkflowContext::from_base(base, Rc::new(RefCell::new(TestWorkflow)));
4484
4485 let signal = ctx
4486 .external_workflow("original-workflow", Some("original-run".to_string()))
4487 .signal(
4488 TestWorkflow::test_signal,
4489 "original-input".to_string(),
4490 Default::default(),
4491 );
4492 let cancel_target =
4493 ctx.external_workflow("cancel-workflow", Some("cancel-run".to_string()));
4494 let cancel = cancel_target.cancel(Some("original-reason".to_string()));
4495 let termination = ctx
4496 .continue_as_new(7, ContinueAsNewOptions::default())
4497 .expect_err("continue_as_new should terminate the workflow");
4498 let sync_ctx = ctx.sync_context();
4499 let nexus = sync_ctx.start_nexus_operation(
4500 NexusOperationOptions::builder()
4501 .endpoint("original-endpoint")
4502 .service("original-service")
4503 .operation("original-operation")
4504 .build(),
4505 );
4506 drop((signal, cancel, nexus));
4507
4508 let WorkflowTermination::ContinueAsNew(continue_as_new) = termination else {
4509 panic!("expected continue-as-new termination")
4510 };
4511 assert_eq!(continue_as_new.workflow_type, "mutated-workflow-type");
4512 assert_eq!(
4513 continue_as_new.arguments,
4514 vec![42u8.as_json_payload().unwrap()]
4515 );
4516 assert!(continue_as_new.headers.contains_key("continue-header"));
4517
4518 let commands = host.commands.borrow();
4519 assert_eq!(commands.len(), 3);
4520 let Some(workflow_command::Variant::SignalExternalWorkflowExecution(signal)) =
4521 &commands[0].variant
4522 else {
4523 panic!("expected signal command")
4524 };
4525 assert_eq!(signal.signal_name, "mutated-signal");
4526 assert_eq!(
4527 signal.args,
4528 vec!["mutated-input".to_string().as_json_payload().unwrap()]
4529 );
4530 assert!(signal.headers.contains_key("signal-header"));
4531 let Some(signal_external_workflow_execution::Target::WorkflowExecution(target)) =
4532 &signal.target
4533 else {
4534 panic!("expected external workflow signal target")
4535 };
4536 assert_eq!(target.namespace, "mutated-namespace");
4537 assert_eq!(target.workflow_id, "mutated-workflow");
4538 assert_eq!(target.run_id, "mutated-run");
4539
4540 let Some(workflow_command::Variant::RequestCancelExternalWorkflowExecution(cancel)) =
4541 &commands[1].variant
4542 else {
4543 panic!("expected external cancellation command")
4544 };
4545 let target = cancel.workflow_execution.as_ref().unwrap();
4546 assert_eq!(target.workflow_id, "mutated-cancel-workflow");
4547 assert_eq!(target.run_id, "mutated-cancel-run");
4548 assert_eq!(cancel.reason, "mutated-reason");
4549
4550 let Some(workflow_command::Variant::ScheduleNexusOperation(nexus)) =
4551 &commands[2].variant
4552 else {
4553 panic!("expected Nexus operation command")
4554 };
4555 assert_eq!(nexus.endpoint, "mutated-endpoint");
4556 assert_eq!(nexus.service, "mutated-service");
4557 assert_eq!(nexus.operation, "mutated-operation");
4558 }
4559 }
4560
4561 struct HeaderAddingContinueAsNewInterceptor;
4562
4563 impl WorkflowInterceptor for HeaderAddingContinueAsNewInterceptor {
4564 fn continue_as_new(
4565 &self,
4566 _ctx: crate::workflow_interceptors::SyncWorkflowInterceptorContext,
4567 mut input: ContinueAsNewInput,
4568 next: WorkflowNext<
4569 'static,
4570 ContinueAsNewInput,
4571 crate::workflow_interceptors::ContinueAsNewResult,
4572 >,
4573 ) -> crate::workflow_interceptors::ContinueAsNewResult {
4574 input.headers_mut().insert(
4575 "continue-header".to_string(),
4576 Payload::from(b"continue-header-value".as_slice()),
4577 );
4578 next.run(input)
4579 }
4580 }
4581
4582 #[test]
4583 fn continue_as_new_interceptor_header_reaches_proto_command() {
4584 let init = InitializeWorkflow {
4585 workflow_type: TestWorkflow.name().to_string(),
4586 ..Default::default()
4587 };
4588 let init = WorkflowInit {
4589 namespace: "default".to_string(),
4590 task_queue: "task-queue".to_string(),
4591 run_id: "run-id".to_string(),
4592 initialize_workflow: init,
4593 };
4594 let base = BaseWorkflowContext::from_raw(
4595 init,
4596 DataConverter::default(),
4597 Rc::new(NoopHost),
4598 None,
4599 vec![WorkflowInterceptorConstructor::new(|_| {
4600 HeaderAddingContinueAsNewInterceptor
4601 })],
4602 );
4603 let ctx = WorkflowContext::from_base(base, Rc::new(RefCell::new(TestWorkflow)));
4604
4605 let termination = ctx
4606 .continue_as_new(7, ContinueAsNewOptions::default())
4607 .expect_err("continue_as_new should terminate the workflow");
4608 let WorkflowTermination::ContinueAsNew(proto_command) = termination else {
4609 panic!("expected continue-as-new termination")
4610 };
4611
4612 assert_eq!(
4613 proto_command.headers,
4614 HashMap::from([(
4615 "continue-header".to_string(),
4616 Payload::from(b"continue-header-value".as_slice()),
4617 )])
4618 );
4619 }
4620
4621 #[test]
4622 fn construction_waker_uses_runtime_poll_waker() {
4623 let base = test_context().sync.base;
4624 let wakes = Arc::new(AtomicUsize::new(0));
4625 let waker = Waker::from(Arc::new(CountingWake(wakes.clone())));
4626 let _guard = base.enter_runtime_poll(&waker);
4627 base.construction_waker().wake_by_ref();
4628 assert_eq!(wakes.load(AtomicOrdering::Relaxed), 1);
4629 }
4630
4631 #[test]
4632 fn workflow_context_continue_as_new_serializes_input_and_defaults() {
4633 let ctx = test_context();
4634
4635 let termination = ctx
4636 .continue_as_new(7, ContinueAsNewOptions::default())
4637 .expect_err("continue_as_new should terminate the workflow");
4638 assert!(
4639 matches!(termination, WorkflowTermination::ContinueAsNew(_)),
4640 "expected continue-as-new termination, got {termination:?}"
4641 );
4642 let WorkflowTermination::ContinueAsNew(cmd) = termination else {
4643 unreachable!()
4644 };
4645
4646 assert_eq!(
4647 *cmd,
4648 crate::runtime::types::ContinueAsNewRequest {
4649 workflow_type: TestWorkflow.name().to_string(),
4650 task_queue: String::new(),
4651 arguments: vec![7u8.as_json_payload().unwrap()],
4652 workflow_run_timeout: None,
4653 workflow_task_timeout: None,
4654 backoff_start_interval: None,
4655 memo: HashMap::new(),
4656 headers: HashMap::new(),
4657 search_attributes: None,
4658 retry_policy: None,
4659 versioning_intent: ProtoVersioningIntent::Unspecified.into(),
4660 initial_versioning_behavior: ProtoContinueAsNewVersioningBehavior::Unspecified
4661 .into(),
4662 }
4663 );
4664 }
4665
4666 #[cfg(feature = "experimental")]
4667 mod experimental_continue_as_new_tests {
4668 use super::*;
4669 use temporalio_common_wasm::{
4670 RetryPolicy, protos::temporal::api::common::v1::RetryPolicy as ProtoRetryPolicy,
4671 };
4672
4673 #[test]
4674 fn sync_workflow_context_continue_as_new_applies_options() {
4675 let ctx = test_context();
4676 let sync = ctx.sync_context();
4677 let mut memo = MemoValues::new();
4678 memo.insert("memo-key", "memo-value".to_string());
4679 let mut proto_search_attributes = ProtoSearchAttributes::default();
4680 proto_search_attributes.indexed_fields.insert(
4681 "CustomKeywordField".to_string(),
4682 Payload::from(b"value".as_slice()),
4683 );
4684 let search_attributes = SearchAttributes::from_proto(&proto_search_attributes);
4685
4686 let termination = sync
4687 .continue_as_new(
4688 11,
4689 ContinueAsNewOptions {
4690 workflow_type: Some("next-workflow".to_string()),
4691 task_queue: Some("next-task-queue".to_string()),
4692 run_timeout: Some(Duration::from_secs(10)),
4693 task_timeout: Some(Duration::from_secs(3)),
4694 backoff_start_interval: Some(Duration::from_secs(4)),
4695 memo: Some(memo.clone()),
4696 search_attributes: Some(search_attributes.clone()),
4697 retry_policy: Some(RetryPolicy::builder().maximum_attempts(5).build()),
4698 versioning_intent: Some(ProtoVersioningIntent::Compatible.into()),
4699 initial_versioning_behavior: Some(
4700 ContinueAsNewVersioningBehavior::UseRampingVersion,
4701 ),
4702 },
4703 )
4704 .expect_err("continue_as_new should terminate the workflow");
4705 assert!(
4706 matches!(termination, WorkflowTermination::ContinueAsNew(_)),
4707 "expected continue-as-new termination, got {termination:?}"
4708 );
4709 let WorkflowTermination::ContinueAsNew(cmd) = termination else {
4710 unreachable!()
4711 };
4712
4713 assert_eq!(
4714 *cmd,
4715 crate::runtime::types::ContinueAsNewRequest {
4716 workflow_type: "next-workflow".to_string(),
4717 task_queue: "next-task-queue".to_string(),
4718 arguments: vec![11u8.as_json_payload().unwrap()],
4719 workflow_run_timeout: Some(Duration::from_secs(10).try_into().unwrap()),
4720 workflow_task_timeout: Some(Duration::from_secs(3).try_into().unwrap()),
4721 backoff_start_interval: Some(Duration::from_secs(4).try_into().unwrap()),
4722 memo: HashMap::from([(
4723 "memo-key".to_string(),
4724 "memo-value".as_json_payload().unwrap(),
4725 )]),
4726 headers: HashMap::new(),
4727 search_attributes: Some(proto_search_attributes),
4728 retry_policy: Some(ProtoRetryPolicy {
4729 initial_interval: Some(Duration::from_secs(1).try_into().unwrap()),
4730 backoff_coefficient: 2.0,
4731 maximum_attempts: 5,
4732 ..Default::default()
4733 }),
4734 versioning_intent: ProtoVersioningIntent::Compatible.into(),
4735 initial_versioning_behavior:
4736 ProtoContinueAsNewVersioningBehavior::UseRampingVersion as i32,
4737 }
4738 );
4739 }
4740
4741 #[test]
4742 fn workflow_context_continue_as_new_applies_auto_upgrade_versioning_behavior() {
4743 let ctx = test_context();
4744
4745 let termination = ctx
4746 .continue_as_new(
4747 13,
4748 ContinueAsNewOptions {
4749 initial_versioning_behavior: Some(
4750 ContinueAsNewVersioningBehavior::AutoUpgrade,
4751 ),
4752 ..Default::default()
4753 },
4754 )
4755 .expect_err("continue_as_new should terminate the workflow");
4756 let WorkflowTermination::ContinueAsNew(cmd) = termination else {
4757 unreachable!()
4758 };
4759
4760 assert_eq!(
4761 cmd.initial_versioning_behavior,
4762 ProtoContinueAsNewVersioningBehavior::AutoUpgrade as i32
4763 );
4764 }
4765 }
4766
4767 #[test]
4768 fn continue_as_new_preserves_explicit_empty_search_attributes() {
4769 let ctx = test_context();
4770 let sync = ctx.sync_context();
4771
4772 let termination = sync
4773 .continue_as_new(
4774 11,
4775 ContinueAsNewOptions {
4776 search_attributes: Some(SearchAttributes::default()),
4777 ..Default::default()
4778 },
4779 )
4780 .expect_err("continue_as_new should terminate the workflow");
4781 let WorkflowTermination::ContinueAsNew(cmd) = termination else {
4782 unreachable!()
4783 };
4784
4785 assert_eq!(
4786 cmd.search_attributes,
4787 Some(ProtoSearchAttributes::default())
4788 );
4789 }
4790
4791 #[test]
4792 fn continue_as_new_preserves_input_serialization_errors() {
4793 #[derive(Debug)]
4794 struct FailingInput;
4795
4796 impl TemporalSerializable for FailingInput {
4797 fn to_payload(
4798 &self,
4799 _ctx: &temporalio_common_wasm::data_converters::SerializationContext<'_>,
4800 ) -> Result<Payload, temporalio_common_wasm::data_converters::PayloadConversionError>
4801 {
4802 Err(
4803 temporalio_common_wasm::data_converters::PayloadConversionError::EncodingError(
4804 std::io::Error::other("serialization failure").into(),
4805 ),
4806 )
4807 }
4808 }
4809
4810 impl TemporalDeserializable for FailingInput {
4811 fn from_payload(
4812 _ctx: &temporalio_common_wasm::data_converters::SerializationContext<'_>,
4813 _payload: Payload,
4814 ) -> Result<Self, temporalio_common_wasm::data_converters::PayloadConversionError>
4815 {
4816 unreachable!("test input is only serialized")
4817 }
4818 }
4819
4820 #[workflow]
4821 #[derive(Default)]
4822 struct FailingWorkflow;
4823
4824 #[workflow_methods]
4825 impl FailingWorkflow {
4826 #[run]
4827 async fn run(
4828 _ctx: &mut WorkflowContext<Self>,
4829 _input: FailingInput,
4830 ) -> crate::WorkflowResult<()> {
4831 unreachable!("test workflow run should not be polled")
4832 }
4833 }
4834
4835 let init = InitializeWorkflow {
4836 workflow_type: "failing-workflow".to_string(),
4837 ..Default::default()
4838 };
4839 let init = WorkflowInit {
4840 namespace: "default".to_string(),
4841 task_queue: "orig-task-queue".to_string(),
4842 run_id: "run-id".to_string(),
4843 initialize_workflow: init,
4844 };
4845 let base = BaseWorkflowContext::from_raw(
4846 init,
4847 DataConverter::default(),
4848 Rc::new(NoopHost),
4849 None,
4850 Vec::new(),
4851 );
4852 let ctx = WorkflowContext::from_base(base, Rc::new(RefCell::new(FailingWorkflow)));
4853
4854 let termination = ctx
4855 .continue_as_new(FailingInput, ContinueAsNewOptions::default())
4856 .expect_err("input serialization should fail");
4857 let WorkflowTermination::Failed(OutgoingWorkflowError::PayloadConversion(err)) =
4858 termination
4859 else {
4860 panic!("expected a payload conversion failure");
4861 };
4862 assert_eq!(err.to_string(), "Encoding error: serialization failure");
4863 }
4864
4865 #[test]
4866 fn continue_as_new_preserves_memo_serialization_errors() {
4867 let ctx = test_context();
4868 let mut memo = MemoValues::new();
4869 memo.insert("invalid", FailingMemoValue);
4870
4871 let termination = ctx
4872 .continue_as_new(
4873 7,
4874 ContinueAsNewOptions {
4875 memo: Some(memo),
4876 ..Default::default()
4877 },
4878 )
4879 .expect_err("memo serialization should fail");
4880 let WorkflowTermination::Failed(OutgoingWorkflowError::PayloadConversion(err)) =
4881 termination
4882 else {
4883 panic!("expected a payload conversion failure");
4884 };
4885 assert_eq!(
4886 err.to_string(),
4887 "Encoding error: memo serialization failure"
4888 );
4889 }
4890
4891 #[test]
4892 fn upsert_search_attributes_updates_local_state() {
4893 use temporalio_common_wasm::search_attributes::SearchAttributeKey;
4894
4895 const K: SearchAttributeKey<i64> = SearchAttributeKey::int("my_int");
4896
4897 let ctx = test_context();
4898 assert!(ctx.search_attributes().is_empty());
4899
4900 ctx.upsert_search_attributes([K.value_set(42)]);
4901 let attrs = ctx.search_attributes();
4902 assert_eq!(attrs.get(&K), Some(42));
4903 }
4904
4905 #[test]
4906 fn upsert_memo_updates_local_state_and_encodes_removals() {
4907 let init = InitializeWorkflow {
4908 workflow_type: TestWorkflow.name().to_string(),
4909 memo: Some(ProtoMemo {
4910 fields: HashMap::from([("old".to_string(), "before".as_json_payload().unwrap())]),
4911 }),
4912 ..Default::default()
4913 };
4914 let host = Rc::new(RecordingHost::default());
4915 let init = WorkflowInit {
4916 namespace: "default".to_string(),
4917 task_queue: "orig-task-queue".to_string(),
4918 run_id: "run-id".to_string(),
4919 initialize_workflow: init,
4920 };
4921 let base = BaseWorkflowContext::from_raw(
4922 init,
4923 DataConverter::default(),
4924 host.clone(),
4925 None,
4926 Vec::new(),
4927 );
4928 let ctx = WorkflowContext::from_base(base, Rc::new(RefCell::new(TestWorkflow)));
4929
4930 assert_eq!(
4931 ctx.memo().get::<String>("old").unwrap(),
4932 Some("before".to_string())
4933 );
4934 ctx.upsert_memo([("new", Some(MemoValue::new(42_u32))), ("old", None)])
4935 .unwrap();
4936
4937 let current = ctx.memo();
4938 assert_eq!(current.get::<u32>("new").unwrap(), Some(42));
4939 assert_eq!(current.get::<String>("old").unwrap(), None);
4940 let view = ctx.view();
4941 assert_eq!(view.memo().get::<u32>("new").unwrap(), Some(42));
4942 assert_eq!(
4943 view.memo().raw(),
4944 view.raw()
4945 .memo
4946 .as_ref()
4947 .expect("view memo should be present")
4948 );
4949
4950 let commands = host.commands.borrow();
4951 let [command] = commands.as_slice() else {
4952 panic!("expected one modify-properties command");
4953 };
4954 let Some(workflow_command::Variant::ModifyWorkflowProperties(command)) = &command.variant
4955 else {
4956 panic!("expected a modify-properties command");
4957 };
4958 let fields = &command.upserted_memo.as_ref().unwrap().fields;
4959 let payload_converter = PayloadConverter::default();
4960 let removal_payload = payload_converter
4961 .to_payload(
4962 &SerializationContext::new(
4963 &SerializationContextData::Workflow(WorkflowSerializationContext::new()),
4964 &payload_converter,
4965 ),
4966 &MemoValue::new(()),
4967 )
4968 .unwrap();
4969 assert_eq!(fields.get("old"), Some(&removal_payload));
4970 assert_eq!(
4971 u32::from_json_payload(fields.get("new").unwrap()).unwrap(),
4972 42
4973 );
4974 }
4975
4976 #[test]
4977 fn upsert_memo_conversion_failure_does_not_mutate_or_emit_command() {
4978 let host = Rc::new(RecordingHost::default());
4979 let init = InitializeWorkflow {
4980 workflow_type: TestWorkflow.name().to_string(),
4981 ..Default::default()
4982 };
4983 let init = WorkflowInit {
4984 namespace: "default".to_string(),
4985 task_queue: "orig-task-queue".to_string(),
4986 run_id: "run-id".to_string(),
4987 initialize_workflow: init,
4988 };
4989 let base = BaseWorkflowContext::from_raw(
4990 init,
4991 DataConverter::default(),
4992 host.clone(),
4993 None,
4994 Vec::new(),
4995 );
4996 let ctx = WorkflowContext::from_base(base, Rc::new(RefCell::new(TestWorkflow)));
4997 let err = ctx
4998 .upsert_memo([
4999 ("valid", Some(MemoValue::new("value".to_string()))),
5000 ("invalid", Some(MemoValue::new(FailingMemoValue))),
5001 ])
5002 .unwrap_err();
5003
5004 assert_eq!(
5005 err.to_string(),
5006 "Encoding error: memo serialization failure"
5007 );
5008 assert_eq!(ctx.memo().get::<String>("valid").unwrap(), None);
5009 assert!(host.commands.borrow().is_empty());
5010 }
5011
5012 #[test]
5013 fn upsert_search_attributes_unset_removes_from_local_state() {
5014 use temporalio_common_wasm::search_attributes::SearchAttributeKey;
5015
5016 const K: SearchAttributeKey<String> = SearchAttributeKey::keyword("my_kw");
5017
5018 let ctx = test_context();
5019 ctx.upsert_search_attributes([K.value_set("hello".into())]);
5021 assert_eq!(ctx.search_attributes().get(&K), Some("hello".into()));
5022
5023 ctx.upsert_search_attributes([K.value_unset()]);
5024 assert!(!ctx.search_attributes().contains_key(&K));
5025 assert!(ctx.search_attributes().is_empty());
5026 }
5027
5028 #[test]
5029 fn upsert_search_attributes_multiple_updates_last_wins() {
5030 use temporalio_common_wasm::search_attributes::SearchAttributeKey;
5031
5032 const K: SearchAttributeKey<i64> = SearchAttributeKey::int("counter");
5033
5034 let ctx = test_context();
5035 ctx.upsert_search_attributes([K.value_set(1), K.value_set(2)]);
5036 assert_eq!(ctx.search_attributes().get(&K), Some(2));
5037 }
5038
5039 #[test]
5040 fn upsert_search_attributes_merges_with_initial() {
5041 use temporalio_common_wasm::search_attributes::SearchAttributeKey;
5042
5043 const A: SearchAttributeKey<i64> = SearchAttributeKey::int("attr_a");
5044 const B: SearchAttributeKey<String> = SearchAttributeKey::keyword("attr_b");
5045
5046 let init_sa = SearchAttributes::new([A.value_set(1)]).into_proto();
5048 let init = InitializeWorkflow {
5049 workflow_type: TestWorkflow.name().to_string(),
5050 search_attributes: Some(init_sa),
5051 ..Default::default()
5052 };
5053 let init = WorkflowInit {
5054 namespace: "default".to_string(),
5055 task_queue: "tq".to_string(),
5056 run_id: "run-id".to_string(),
5057 initialize_workflow: init,
5058 };
5059 let base = BaseWorkflowContext::from_raw(
5060 init,
5061 DataConverter::default(),
5062 Rc::new(NoopHost),
5063 None,
5064 Vec::new(),
5065 );
5066 let ctx = WorkflowContext::from_base(base, Rc::new(RefCell::new(TestWorkflow)));
5067
5068 assert_eq!(ctx.search_attributes().get(&A), Some(1));
5069
5070 ctx.upsert_search_attributes([B.value_set("hello".into())]);
5072 assert_eq!(ctx.search_attributes().get(&A), Some(1));
5073 assert_eq!(ctx.search_attributes().get(&B), Some("hello".into()));
5074 }
5075
5076 #[test]
5077 fn view_search_attributes_returns_typed() {
5078 use temporalio_common_wasm::search_attributes::SearchAttributeKey;
5079
5080 const K: SearchAttributeKey<bool> = SearchAttributeKey::bool("active");
5081
5082 let init_sa = SearchAttributes::new([K.value_set(true)]).into_proto();
5083 let init = InitializeWorkflow {
5084 workflow_type: TestWorkflow.name().to_string(),
5085 search_attributes: Some(init_sa),
5086 ..Default::default()
5087 };
5088 let init = WorkflowInit {
5089 namespace: "default".to_string(),
5090 task_queue: "tq".to_string(),
5091 run_id: "run-id".to_string(),
5092 initialize_workflow: init,
5093 };
5094 let base = BaseWorkflowContext::from_raw(
5095 init,
5096 DataConverter::default(),
5097 Rc::new(NoopHost),
5098 None,
5099 Vec::new(),
5100 );
5101 let ctx = WorkflowContext::from_base(base, Rc::new(RefCell::new(TestWorkflow)));
5102
5103 let view = ctx.view();
5104 let sa = view
5105 .search_attributes()
5106 .expect("should have search attributes");
5107 assert_eq!(sa.get(&K), Some(true));
5108 }
5109
5110 #[test]
5111 fn workflow_info_retains_raw_initialization() {
5112 let init = InitializeWorkflow {
5113 workflow_type: TestWorkflow.name().to_string(),
5114 identity: "raw-only-identity".to_owned(),
5115 ..Default::default()
5116 };
5117 let expected = init.clone();
5118 let init = WorkflowInit {
5119 namespace: "default".to_string(),
5120 task_queue: "tq".to_string(),
5121 run_id: "run-id".to_string(),
5122 initialize_workflow: init,
5123 };
5124 let base = BaseWorkflowContext::from_raw(
5125 init,
5126 DataConverter::default(),
5127 Rc::new(NoopHost),
5128 None,
5129 Vec::new(),
5130 );
5131 let ctx = WorkflowContext::from_base(base, Rc::new(RefCell::new(TestWorkflow)));
5132 let info = ctx.info();
5133
5134 assert_eq!(info.raw().identity, "raw-only-identity");
5135 assert_eq!(info.raw(), &expected);
5136 assert_eq!(info.into_raw(), expected);
5137 }
5138
5139 #[test]
5140 fn async_context_values_survive_suspension_and_isolate_concurrent_branches() {
5141 struct Label;
5142
5143 impl WorkflowContextKey for Label {
5144 type Value = &'static str;
5145 }
5146
5147 let ctx = test_context();
5148 let first_poll = Rc::new(Cell::new(true));
5149 let second_poll = Rc::new(Cell::new(true));
5150 let first_ctx = ctx.clone();
5151 let first_poll_in_future = first_poll.clone();
5152 let first = ctx.with_context_value::<Label, _>(
5153 "first",
5154 future::poll_fn(move |_| {
5155 assert_eq!(
5156 first_ctx.context_value::<Label>().as_deref(),
5157 Some(&"first")
5158 );
5159 if first_poll_in_future.replace(false) {
5160 Poll::Pending
5161 } else {
5162 Poll::Ready(())
5163 }
5164 }),
5165 );
5166 let second_ctx = ctx.clone();
5167 let second_poll_in_future = second_poll.clone();
5168 let second = ctx.with_context_value::<Label, _>(
5169 "second",
5170 future::poll_fn(move |_| {
5171 assert_eq!(
5172 second_ctx.context_value::<Label>().as_deref(),
5173 Some(&"second")
5174 );
5175 if second_poll_in_future.replace(false) {
5176 Poll::Pending
5177 } else {
5178 Poll::Ready(())
5179 }
5180 }),
5181 );
5182 let mut joined = Box::pin(futures_util::future::join(first, second));
5183 let waker = futures_util::task::noop_waker();
5184 let mut poll_ctx = Context::from_waker(&waker);
5185
5186 assert!(joined.as_mut().poll(&mut poll_ctx).is_pending());
5187 assert!(ctx.context_value::<Label>().is_none());
5188 assert!(joined.as_mut().poll(&mut poll_ctx).is_ready());
5189 assert!(ctx.context_value::<Label>().is_none());
5190
5191 let mut dropped =
5192 Box::pin(ctx.with_context_value::<Label, _>("dropped", future::pending::<()>()));
5193 assert!(dropped.as_mut().poll(&mut poll_ctx).is_pending());
5194 assert!(ctx.context_value::<Label>().is_none());
5195 drop(dropped);
5196 assert!(ctx.context_value::<Label>().is_none());
5197 }
5198
5199 #[test]
5200 fn context_scopes_inherit_shadow_and_restore_after_panic() {
5201 struct Label;
5202 struct OtherLabel;
5203 struct Count;
5204
5205 impl WorkflowContextKey for Label {
5206 type Value = &'static str;
5207 }
5208
5209 impl WorkflowContextKey for OtherLabel {
5210 type Value = &'static str;
5211 }
5212
5213 impl WorkflowContextKey for Count {
5214 type Value = u32;
5215 }
5216
5217 let ctx = test_context();
5218 ctx.with_context_value_sync::<Label, _>("outer", || {
5219 assert_eq!(ctx.context_value::<Label>().as_deref(), Some(&"outer"));
5220 assert!(ctx.context_value::<OtherLabel>().is_none());
5221 ctx.with_context_value_sync::<Count, _>(7, || {
5222 assert_eq!(ctx.context_value::<Label>().as_deref(), Some(&"outer"));
5223 assert_eq!(ctx.context_value::<Count>().as_deref(), Some(&7));
5224 ctx.with_context_value_sync::<Label, _>("inner", || {
5225 assert_eq!(ctx.context_value::<Label>().as_deref(), Some(&"inner"));
5226 });
5227 assert_eq!(ctx.context_value::<Label>().as_deref(), Some(&"outer"));
5228 });
5229 assert!(ctx.context_value::<Count>().is_none());
5230
5231 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5232 ctx.with_context_value_sync::<Label, _>("panic", || panic!("test panic"));
5233 }));
5234 assert!(result.is_err());
5235 assert_eq!(ctx.context_value::<Label>().as_deref(), Some(&"outer"));
5236 });
5237 assert!(ctx.context_value::<Label>().is_none());
5238
5239 let panic_ctx = ctx.clone();
5240 let mut panic_future = Box::pin(ctx.with_context_value::<Label, _>(
5241 "async-panic",
5242 async move {
5243 assert_eq!(
5244 panic_ctx.context_value::<Label>().as_deref(),
5245 Some(&"async-panic")
5246 );
5247 panic!("async test panic");
5248 },
5249 ));
5250 let waker = futures_util::task::noop_waker();
5251 let mut poll_ctx = Context::from_waker(&waker);
5252 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5253 panic_future.as_mut().poll(&mut poll_ctx)
5254 }));
5255 assert!(result.is_err());
5256 assert!(ctx.context_value::<Label>().is_none());
5257 }
5258}