Skip to main content

starlang_core/
system_message.rs

1//! System messages for process lifecycle events.
2//!
3//! System messages are internal messages that processes receive in response
4//! to lifecycle events like linked process exits, monitor notifications,
5//! and timeouts.
6
7use crate::{ExitReason, Pid, Ref};
8use serde::{Deserialize, Serialize};
9
10/// System-level messages delivered to processes.
11///
12/// These messages are generated by the runtime in response to process
13/// lifecycle events. They are distinct from user-defined messages and
14/// are handled specially by abstractions like GenServer.
15///
16/// # Examples
17///
18/// ```
19/// use starlang_core::{SystemMessage, ExitReason, Pid, Ref};
20///
21/// // Exit signal from a linked process
22/// let exit = SystemMessage::Exit {
23///     from: Pid::new(),
24///     reason: ExitReason::Normal,
25/// };
26///
27/// // DOWN notification from a monitored process
28/// let down = SystemMessage::Down {
29///     monitor_ref: Ref::from_raw(42),
30///     pid: Pid::new(),
31///     reason: ExitReason::Error("crashed".to_string()),
32/// };
33///
34/// // Timeout notification
35/// let timeout = SystemMessage::Timeout;
36/// ```
37#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
38pub enum SystemMessage {
39    /// Exit signal from a linked process.
40    ///
41    /// When a linked process terminates, an `Exit` message is sent to all
42    /// processes linked to it (if they have `trap_exit` enabled).
43    ///
44    /// If `trap_exit` is disabled, the receiving process will also terminate
45    /// with the same reason (unless the reason is `Normal`).
46    Exit {
47        /// The process that exited.
48        from: Pid,
49        /// The reason for termination.
50        reason: ExitReason,
51    },
52
53    /// Monitor notification that a monitored process terminated.
54    ///
55    /// Unlike exit signals, DOWN messages are always delivered as messages
56    /// and never cause the receiving process to terminate.
57    Down {
58        /// The reference returned when the monitor was created.
59        monitor_ref: Ref,
60        /// The process that was being monitored.
61        pid: Pid,
62        /// The reason for termination.
63        reason: ExitReason,
64    },
65
66    /// Timeout notification.
67    ///
68    /// Delivered when a receive timeout expires or when a GenServer's
69    /// timeout period elapses without receiving any messages.
70    Timeout,
71}
72
73impl SystemMessage {
74    /// Creates a new Exit message.
75    pub fn exit(from: Pid, reason: ExitReason) -> Self {
76        SystemMessage::Exit { from, reason }
77    }
78
79    /// Creates a new Down message.
80    pub fn down(monitor_ref: Ref, pid: Pid, reason: ExitReason) -> Self {
81        SystemMessage::Down {
82            monitor_ref,
83            pid,
84            reason,
85        }
86    }
87
88    /// Returns `true` if this is an Exit message.
89    pub fn is_exit(&self) -> bool {
90        matches!(self, SystemMessage::Exit { .. })
91    }
92
93    /// Returns `true` if this is a Down message.
94    pub fn is_down(&self) -> bool {
95        matches!(self, SystemMessage::Down { .. })
96    }
97
98    /// Returns `true` if this is a Timeout message.
99    pub fn is_timeout(&self) -> bool {
100        matches!(self, SystemMessage::Timeout)
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_exit_message() {
110        let pid = Pid::new();
111        let msg = SystemMessage::exit(pid, ExitReason::Normal);
112
113        assert!(msg.is_exit());
114        assert!(!msg.is_down());
115        assert!(!msg.is_timeout());
116
117        if let SystemMessage::Exit { from, reason } = msg {
118            assert_eq!(from, pid);
119            assert_eq!(reason, ExitReason::Normal);
120        } else {
121            panic!("expected Exit variant");
122        }
123    }
124
125    #[test]
126    fn test_down_message() {
127        let r = Ref::from_raw(100);
128        let pid = Pid::new();
129        let msg = SystemMessage::down(r, pid, ExitReason::Killed);
130
131        assert!(!msg.is_exit());
132        assert!(msg.is_down());
133        assert!(!msg.is_timeout());
134
135        if let SystemMessage::Down {
136            monitor_ref,
137            pid: p,
138            reason,
139        } = msg
140        {
141            assert_eq!(monitor_ref, r);
142            assert_eq!(p, pid);
143            assert_eq!(reason, ExitReason::Killed);
144        } else {
145            panic!("expected Down variant");
146        }
147    }
148
149    #[test]
150    fn test_timeout_message() {
151        let msg = SystemMessage::Timeout;
152
153        assert!(!msg.is_exit());
154        assert!(!msg.is_down());
155        assert!(msg.is_timeout());
156    }
157
158    #[test]
159    fn test_serialization() {
160        let pid1 = Pid::new();
161        let pid2 = Pid::new();
162        let messages = vec![
163            SystemMessage::exit(pid1, ExitReason::Normal),
164            SystemMessage::down(Ref::from_raw(42), pid2, ExitReason::Error("test".into())),
165            SystemMessage::Timeout,
166        ];
167
168        for msg in messages {
169            let bytes = postcard::to_allocvec(&msg).unwrap();
170            let decoded: SystemMessage = postcard::from_bytes(&bytes).unwrap();
171            assert_eq!(msg, decoded);
172        }
173    }
174}