Skip to main content

hyperactor/
port.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! Port identifiers.
10
11use std::fmt;
12use std::num::ParseIntError;
13use std::str::FromStr;
14
15use enum_as_inner::EnumAsInner;
16use serde::Deserialize;
17use serde::Serialize;
18use typeuri::Named;
19
20use crate::id::Label;
21use crate::id::Uid;
22
23/// A port identifier within an actor.
24#[derive(
25    Clone,
26    EnumAsInner,
27    PartialEq,
28    Eq,
29    Hash,
30    PartialOrd,
31    Ord,
32    Serialize,
33    Deserialize
34)]
35pub enum Port {
36    /// Ephemeral ports, indexed starting at 0.
37    Ephemeral(u64),
38    /// Handler ports, indexed by uid. The label usually carries the type name.
39    Handler(Uid),
40    /// Control ports.
41    Control(ControlPort),
42}
43
44/// Runtime-owned control ports.
45#[derive(
46    Clone,
47    Copy,
48    Debug,
49    PartialEq,
50    Eq,
51    Hash,
52    PartialOrd,
53    Ord,
54    Serialize,
55    Deserialize
56)]
57pub enum ControlPort {
58    /// Actor introspection.
59    Introspect,
60    /// Actor lifecycle signals.
61    Signal,
62    /// Actor lifecycle status.
63    Status,
64}
65
66/// Errors that can occur when parsing a [`ControlPort`].
67#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
68pub enum ControlPortParseError {
69    /// The control port name is not known.
70    #[error("unknown control port {0:?}")]
71    Unknown(String),
72}
73
74/// Errors that can occur when parsing a [`Port`].
75#[derive(Debug, thiserror::Error)]
76pub enum PortParseError {
77    /// The ephemeral port index is invalid.
78    #[error("invalid ephemeral port: {0}")]
79    InvalidEphemeral(#[from] ParseIntError),
80    /// The handler port uid is invalid.
81    #[error("invalid handler port: {0}")]
82    InvalidHandler(#[from] crate::id::UidParseError),
83}
84
85impl Port {
86    /// Create an ephemeral port.
87    pub fn ephemeral(index: u64) -> Self {
88        Self::Ephemeral(index)
89    }
90
91    /// Create a port for handler message type `M`.
92    pub fn handler<M: Named>() -> Self {
93        Self::handler_id(M::typehash(), Some(Label::strip(handler_label::<M>())))
94    }
95
96    /// Create a handler port from a type hash and optional display label.
97    pub fn handler_id(typehash: u64, label: Option<Label>) -> Self {
98        Self::Handler(Uid::Instance(typehash, label))
99    }
100
101    /// Create a control port.
102    pub fn control(port: ControlPort) -> Self {
103        Self::Control(port)
104    }
105
106    /// The ephemeral port index, if this is an ephemeral port.
107    pub fn ephemeral_index(&self) -> Option<u64> {
108        match self {
109            Self::Ephemeral(index) => Some(*index),
110            _ => None,
111        }
112    }
113
114    /// A numeric representation for telemetry and raw protocol fields.
115    pub fn as_u64(&self) -> u64 {
116        match self {
117            Self::Ephemeral(index) => *index,
118            Self::Handler(uid) => uid.instance_value().unwrap_or_else(|| {
119                panic!("handler port should be identified by an instance uid: {uid}")
120            }),
121            Self::Control(ControlPort::Introspect) => 0,
122            Self::Control(ControlPort::Signal) => 1,
123            Self::Control(ControlPort::Status) => 2,
124        }
125    }
126}
127
128impl From<u64> for Port {
129    fn from(v: u64) -> Self {
130        Port::Ephemeral(v)
131    }
132}
133
134impl fmt::Display for Port {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        match self {
137            Self::Ephemeral(index) => write!(f, "{index}"),
138            Self::Handler(uid) => write!(f, "{uid}"),
139            Self::Control(port) => write!(f, "{port}"),
140        }
141    }
142}
143
144impl fmt::Debug for Port {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        match self {
147            Self::Ephemeral(index) => write!(f, "Port::Ephemeral({index})"),
148            Self::Handler(uid) => write!(f, "Port::Handler({uid})"),
149            Self::Control(port) => write!(f, "Port::Control({port:?})"),
150        }
151    }
152}
153
154impl FromStr for Port {
155    type Err = PortParseError;
156
157    fn from_str(s: &str) -> Result<Self, Self::Err> {
158        if s.bytes().all(|ch| ch.is_ascii_digit()) {
159            return Ok(Self::Ephemeral(s.parse()?));
160        }
161        let uid = s.parse()?;
162        match uid {
163            Uid::Instance(_, _) => Ok(Self::Handler(uid)),
164            Uid::Singleton(_) => Err(PortParseError::InvalidHandler(
165                crate::id::UidParseError::InvalidSyntax(s.to_string()),
166            )),
167        }
168    }
169}
170
171impl fmt::Display for ControlPort {
172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173        match self {
174            Self::Introspect => f.write_str("introspect"),
175            Self::Signal => f.write_str("signal"),
176            Self::Status => f.write_str("status"),
177        }
178    }
179}
180
181impl FromStr for ControlPort {
182    type Err = ControlPortParseError;
183
184    fn from_str(s: &str) -> Result<Self, Self::Err> {
185        match s {
186            "introspect" => Ok(Self::Introspect),
187            "signal" => Ok(Self::Signal),
188            "status" => Ok(Self::Status),
189            _ => Err(ControlPortParseError::Unknown(s.to_string())),
190        }
191    }
192}
193
194fn handler_label<M: Named>() -> &'static str {
195    M::typename()
196        .rsplit("::")
197        .next()
198        .unwrap_or_else(M::typename)
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    struct TestMsg;
206
207    impl Named for TestMsg {
208        fn typename() -> &'static str {
209            "test::TestMsg"
210        }
211    }
212
213    #[test]
214    fn test_handler_port() {
215        let port = Port::handler::<TestMsg>();
216        assert!(port.is_handler(), "handler ports should be explicit");
217        assert_eq!(port.as_u64(), TestMsg::typehash());
218    }
219
220    #[test]
221    fn test_ephemeral_port() {
222        let port = Port::from(42);
223        assert!(port.is_ephemeral(), "decimal ports should be ephemeral");
224        assert!(!port.is_handler(), "ephemeral ports should not be handlers");
225        assert_eq!(port.as_u64(), 42);
226    }
227
228    #[test]
229    fn test_display_fromstr_roundtrip_ephemeral() {
230        let port = Port::from(12345);
231        assert_eq!(port.to_string(), "12345");
232        let parsed: Port = port.to_string().parse().expect("parse port");
233        assert_eq!(port, parsed);
234    }
235
236    #[test]
237    fn test_display_fromstr_roundtrip_handler() {
238        let port = Port::handler::<TestMsg>();
239        let s = port.to_string();
240        let parsed: Port = s.parse().expect("parse port");
241        assert_eq!(port, parsed);
242    }
243
244    #[test]
245    fn test_control_port_status() {
246        assert_eq!(ControlPort::Status.to_string(), "status");
247        assert_eq!(
248            "status".parse::<ControlPort>().expect("parse status"),
249            ControlPort::Status
250        );
251        assert_eq!(Port::control(ControlPort::Status).as_u64(), 2);
252    }
253
254    #[test]
255    fn test_debug() {
256        let port = Port::from(42);
257        assert_eq!(format!("{:?}", port), "Port::Ephemeral(42)");
258    }
259}