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}