Skip to main content

temporalio_sdk/
interceptors.rs

1//! User-definable interceptors are defined in this module
2
3use crate::{
4    Worker, WorkerRunError,
5    activities::{ActivityContext, ActivityError, ActivityInfo},
6};
7use anyhow::bail;
8use futures_util::future::{BoxFuture, LocalBoxFuture};
9use std::{
10    any::Any,
11    collections::HashMap,
12    sync::{Arc, OnceLock},
13};
14use temporalio_common::{
15    data_converters::{
16        GenericPayloadConverter, PayloadConversionError, SerializationContext, TemporalSerializable,
17    },
18    protos::{
19        coresdk::{
20            workflow_activation::{WorkflowActivation, remove_from_cache::EvictionReason},
21            workflow_completion::WorkflowActivationCompletion,
22        },
23        temporal::api::common::v1::Payload,
24    },
25};
26
27mod activity_execution_value {
28    use super::*;
29
30    pub trait Sealed {
31        fn to_activity_payload(
32            &self,
33            context: &SerializationContext<'_>,
34        ) -> Result<Payload, PayloadConversionError>;
35    }
36
37    impl<T> Sealed for T
38    where
39        T: Any + TemporalSerializable + Send + Sync,
40    {
41        fn to_activity_payload(
42            &self,
43            context: &SerializationContext<'_>,
44        ) -> Result<Payload, PayloadConversionError> {
45            context.converter.to_payload(context, self)
46        }
47    }
48}
49
50/// Implementors can intercept certain actions that happen within the Worker.
51///
52/// Advanced usage only.
53/// **Experimental:** This API may change or be removed.
54#[async_trait::async_trait(?Send)]
55pub trait WorkerInterceptor: Send + Sync {
56    /// Intercept the running of a worker.
57    fn run_worker<'a>(
58        &'a self,
59        input: RunWorkerInput<'a>,
60        next: Next<'a, RunWorkerInput<'a>, LocalBoxFuture<'a, Result<(), WorkerRunError>>>,
61    ) -> LocalBoxFuture<'a, Result<(), WorkerRunError>> {
62        next.run(input)
63    }
64
65    /// Intercept the running of a worker created for workflow replay.
66    fn with_workflow_replay_worker<'a>(
67        &'a self,
68        input: WithWorkflowReplayWorkerInput<'a>,
69        next: Next<
70            'a,
71            WithWorkflowReplayWorkerInput<'a>,
72            LocalBoxFuture<'a, Result<(), WorkerRunError>>,
73        >,
74    ) -> LocalBoxFuture<'a, Result<(), WorkerRunError>> {
75        next.run(input)
76    }
77
78    /// Called every time a workflow activation completes (just before sending the completion to
79    /// core).
80    async fn on_workflow_activation_completion(&self, _completion: &WorkflowActivationCompletion) {}
81    /// Called after the worker has initiated shutdown and the workflow/activity polling loops
82    /// have exited, but just before waiting for the inner core worker shutdown
83    fn on_shutdown(&self, _sdk_worker: &Worker) {}
84    /// Called every time a workflow is about to be activated
85    async fn on_workflow_activation(
86        &self,
87        _activation: &WorkflowActivation,
88    ) -> Result<(), anyhow::Error> {
89        Ok(())
90    }
91}
92
93/// Continuation for an interceptor operation.
94///
95/// Interceptor implementations call [`Next::run`] to invoke the next step of the chain.
96pub struct Next<'a, I, O> {
97    inner: Box<dyn FnOnce(I) -> O + Send + 'a>,
98}
99
100/// Input to [`WorkerInterceptor::run_worker`].
101#[derive(Debug)]
102#[non_exhaustive]
103pub struct RunWorkerInput<'a> {
104    /// The worker being run.
105    pub worker: &'a mut Worker,
106}
107
108impl<'a> RunWorkerInput<'a> {
109    pub(crate) fn new(worker: &'a mut Worker) -> Self {
110        Self { worker }
111    }
112}
113
114/// Input to [`WorkerInterceptor::with_workflow_replay_worker`].
115#[derive(Debug)]
116#[non_exhaustive]
117pub struct WithWorkflowReplayWorkerInput<'a> {
118    /// The worker created for this replay operation.
119    pub worker: &'a mut Worker,
120}
121
122impl<'a> WithWorkflowReplayWorkerInput<'a> {
123    pub(crate) fn new(worker: &'a mut Worker) -> Self {
124        Self { worker }
125    }
126}
127
128pub(crate) fn call_run_worker<'a>(
129    interceptors: &'a [Arc<dyn WorkerInterceptor>],
130    input: RunWorkerInput<'a>,
131    terminal: Next<'a, RunWorkerInput<'a>, LocalBoxFuture<'a, Result<(), WorkerRunError>>>,
132) -> LocalBoxFuture<'a, Result<(), WorkerRunError>> {
133    if let Some((interceptor, remaining)) = interceptors.split_first() {
134        let next = Next::new(move |input| call_run_worker(remaining, input, terminal));
135        interceptor.run_worker(input, next)
136    } else {
137        terminal.run(input)
138    }
139}
140
141pub(crate) fn call_with_workflow_replay_worker<'a>(
142    interceptors: &'a [Arc<dyn WorkerInterceptor>],
143    input: WithWorkflowReplayWorkerInput<'a>,
144    terminal: Next<
145        'a,
146        WithWorkflowReplayWorkerInput<'a>,
147        LocalBoxFuture<'a, Result<(), WorkerRunError>>,
148    >,
149) -> LocalBoxFuture<'a, Result<(), WorkerRunError>> {
150    if let Some((interceptor, remaining)) = interceptors.split_first() {
151        let next =
152            Next::new(move |input| call_with_workflow_replay_worker(remaining, input, terminal));
153        interceptor.with_workflow_replay_worker(input, next)
154    } else {
155        terminal.run(input)
156    }
157}
158
159impl<'a, I, O> Next<'a, I, O> {
160    pub(crate) fn new(f: impl FnOnce(I) -> O + Send + 'a) -> Self {
161        Self { inner: Box::new(f) }
162    }
163
164    /// Continue the call chain with the provided input.
165    pub fn run(self, input: I) -> O {
166        (self.inner)(input)
167    }
168}
169
170/// Activity execution data passed to [`ActivityInboundInterceptor::execute_activity`].
171#[non_exhaustive]
172pub struct ExecuteActivityInput {
173    context: ActivityContext,
174    args: Box<dyn Any + Send + Sync>,
175}
176
177impl ExecuteActivityInput {
178    pub(crate) fn new(context: ActivityContext, args: Box<dyn Any + Send + Sync>) -> Self {
179        Self { context, args }
180    }
181
182    pub(crate) fn into_parts(self) -> (ActivityContext, Box<dyn Any + Send + Sync>) {
183        (self.context, self.args)
184    }
185
186    /// Information about the activity execution.
187    pub fn activity_info(&self) -> &ActivityInfo {
188        self.context.info()
189    }
190
191    /// Headers attached to this activity.
192    pub fn headers(&self) -> &HashMap<String, Payload> {
193        self.context.headers()
194    }
195
196    /// Mutably access headers attached to this activity.
197    pub fn headers_mut(&mut self) -> &mut HashMap<String, Payload> {
198        self.context.headers_mut()
199    }
200
201    /// Attempt to access the decoded activity arguments as a concrete type.
202    pub fn args_ref<T: Any>(&self) -> Option<&T> {
203        self.args.downcast_ref()
204    }
205
206    /// Attempt to mutably access the decoded activity arguments as a concrete type.
207    pub fn args_mut<T: Any>(&mut self) -> Option<&mut T> {
208        self.args.downcast_mut()
209    }
210}
211
212/// Type-erased activity output carried through the activity interceptor chain.
213pub trait ActivityExecutionValue:
214    Any + TemporalSerializable + Send + Sync + activity_execution_value::Sealed
215{
216    /// Access this value as [`Any`] for type-specific inspection.
217    fn as_any(&self) -> &dyn Any;
218}
219
220impl<T> ActivityExecutionValue for T
221where
222    T: Any + TemporalSerializable + Send + Sync,
223{
224    fn as_any(&self) -> &dyn Any {
225        self
226    }
227}
228
229impl dyn ActivityExecutionValue {
230    /// Attempt to access the activity output as a concrete type.
231    pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
232        self.as_any().downcast_ref()
233    }
234
235    pub(crate) fn serialize_payload(
236        &self,
237        context: &SerializationContext<'_>,
238    ) -> Result<Payload, PayloadConversionError> {
239        self.to_activity_payload(context)
240    }
241}
242
243/// Result of an activity execution carried through the interceptor chain.
244pub type ExecuteActivityResult = Result<Box<dyn ActivityExecutionValue>, ActivityError>;
245
246/// Future produced by activity inbound interceptors.
247pub type ExecuteActivityOutput<'a> = BoxFuture<'a, ExecuteActivityResult>;
248
249/// Inbound interceptor for activity calls coming from the server.
250///
251/// Must be implemented by inbound activity interceptors.
252pub trait ActivityInboundInterceptor: Send + Sync + 'static {
253    /// Called to invoke the activity.
254    fn execute_activity<'a>(
255        &'a self,
256        input: ExecuteActivityInput,
257        next: Next<'a, ExecuteActivityInput, ExecuteActivityOutput<'a>>,
258    ) -> ExecuteActivityOutput<'a> {
259        next.run(input)
260    }
261}
262
263/// An interceptor which causes the worker's run function to exit early if nondeterminism errors are
264/// encountered
265pub struct FailOnNondeterminismInterceptor {}
266#[async_trait::async_trait(?Send)]
267impl WorkerInterceptor for FailOnNondeterminismInterceptor {
268    async fn on_workflow_activation(
269        &self,
270        activation: &WorkflowActivation,
271    ) -> Result<(), anyhow::Error> {
272        if matches!(
273            activation.eviction_reason(),
274            Some(EvictionReason::Nondeterminism)
275        ) {
276            bail!("Workflow is being evicted because of nondeterminism! {activation}");
277        }
278        Ok(())
279    }
280}
281
282/// An interceptor that allows you to fetch the exit value of the workflow if and when it is set
283#[derive(Default)]
284pub struct ReturnWorkflowExitValueInterceptor {
285    result_value: Arc<OnceLock<Payload>>,
286}
287
288impl ReturnWorkflowExitValueInterceptor {
289    /// Can be used to fetch the workflow result if/when it is determined
290    pub fn result_handle(&self) -> Arc<OnceLock<Payload>> {
291        self.result_value.clone()
292    }
293}
294
295#[async_trait::async_trait(?Send)]
296impl WorkerInterceptor for ReturnWorkflowExitValueInterceptor {
297    async fn on_workflow_activation_completion(&self, c: &WorkflowActivationCompletion) {
298        if let Some(v) = c.complete_workflow_execution_value() {
299            let _ = self.result_value.set(v.clone());
300        }
301    }
302}