Skip to main content

jamjet_core/
timeout.rs

1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use std::time::Duration;
3
4/// Timeout configuration for a workflow execution.
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct TimeoutConfig {
7    /// Maximum time a single node may run before being killed and failed.
8    #[serde(
9        default,
10        serialize_with = "ser_opt_duration",
11        deserialize_with = "de_opt_duration"
12    )]
13    pub node_timeout: Option<Duration>,
14    /// Maximum total time a workflow execution may run.
15    #[serde(
16        default,
17        serialize_with = "ser_opt_duration",
18        deserialize_with = "de_opt_duration"
19    )]
20    pub workflow_timeout: Option<Duration>,
21    /// How often a worker must renew its lease heartbeat.
22    /// If a worker misses this, the lease is reclaimed and the node is re-queued.
23    #[serde(
24        default = "default_heartbeat",
25        serialize_with = "ser_duration",
26        deserialize_with = "de_duration"
27    )]
28    pub heartbeat_interval: Duration,
29    /// Maximum time a human_approval node waits before routing to fallback or failing.
30    #[serde(
31        default,
32        serialize_with = "ser_opt_duration",
33        deserialize_with = "de_opt_duration"
34    )]
35    pub approval_timeout: Option<Duration>,
36}
37
38fn default_heartbeat() -> Duration {
39    Duration::from_secs(30)
40}
41
42impl Default for TimeoutConfig {
43    fn default() -> Self {
44        Self {
45            node_timeout: Some(Duration::from_secs(300)), // 5 min
46            workflow_timeout: None,
47            heartbeat_interval: default_heartbeat(),
48            approval_timeout: None,
49        }
50    }
51}
52
53// Serialize Duration as seconds (integer).
54fn ser_duration<S: Serializer>(d: &Duration, s: S) -> Result<S::Ok, S::Error> {
55    s.serialize_u64(d.as_secs())
56}
57
58fn ser_opt_duration<S: Serializer>(d: &Option<Duration>, s: S) -> Result<S::Ok, S::Error> {
59    match d {
60        Some(d) => s.serialize_some(&d.as_secs()),
61        None => s.serialize_none(),
62    }
63}
64
65// Deserialize Duration from either integer (seconds) or Duration struct.
66fn de_duration<'de, D: Deserializer<'de>>(d: D) -> Result<Duration, D::Error> {
67    #[derive(Deserialize)]
68    #[serde(untagged)]
69    enum DurationRepr {
70        Secs(u64),
71        Struct { secs: u64, nanos: u32 },
72    }
73    match DurationRepr::deserialize(d)? {
74        DurationRepr::Secs(s) => Ok(Duration::from_secs(s)),
75        DurationRepr::Struct { secs, nanos } => Ok(Duration::new(secs, nanos)),
76    }
77}
78
79fn de_opt_duration<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Duration>, D::Error> {
80    #[derive(Deserialize)]
81    #[serde(untagged)]
82    enum OptDurationRepr {
83        Null,
84        Secs(u64),
85        Struct { secs: u64, nanos: u32 },
86    }
87    match Option::<OptDurationRepr>::deserialize(d)? {
88        None | Some(OptDurationRepr::Null) => Ok(None),
89        Some(OptDurationRepr::Secs(s)) => Ok(Some(Duration::from_secs(s))),
90        Some(OptDurationRepr::Struct { secs, nanos }) => Ok(Some(Duration::new(secs, nanos))),
91    }
92}