datadog_agent_commons/ipc/
session.rs1use std::{
4 fmt,
5 sync::{Arc, Mutex},
6};
7
8use saluki_error::{ErrorContext as _, GenericError};
9use tokio::sync::Notify;
10use tonic::metadata::{Ascii, MetadataValue};
11
12#[derive(Debug)]
19pub struct SessionId(MetadataValue<Ascii>);
20
21impl SessionId {
22 pub fn new(session_id: &str) -> Result<Self, GenericError> {
28 MetadataValue::try_from(session_id)
29 .map(Self)
30 .error_context("Session ID is not valid ASCII")
31 }
32
33 pub fn as_str(&self) -> &str {
35 self.0
36 .to_str()
37 .expect("session ID is ensured to be valid ASCII on creation")
38 }
39
40 pub fn to_grpc_header_value(&self) -> MetadataValue<Ascii> {
42 self.0.clone()
43 }
44}
45
46impl fmt::Display for SessionId {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 write!(f, "{}", self.as_str())
49 }
50}
51
52#[derive(Debug, Default)]
53struct SessionIdHandleInner {
54 session_id: Mutex<Option<SessionId>>,
55 change_notify: Notify,
56}
57
58#[derive(Clone, Debug)]
63pub struct SessionIdHandle {
64 inner: Arc<SessionIdHandleInner>,
65}
66
67impl SessionIdHandle {
68 pub fn empty() -> Self {
70 Self {
71 inner: Arc::new(SessionIdHandleInner::default()),
72 }
73 }
74
75 pub fn update(&self, new_session_id: Option<SessionId>) {
77 if let Ok(mut session_id) = self.inner.session_id.lock() {
78 *session_id = new_session_id;
79 self.inner.change_notify.notify_waiters();
80 }
81 }
82
83 pub fn get(&self) -> Option<SessionId> {
85 self.inner
86 .session_id
87 .lock()
88 .ok()
89 .and_then(|s| (*s).as_ref().map(|session_id| SessionId(session_id.0.clone())))
90 }
91
92 pub async fn wait_for_update(&self) -> SessionId {
94 loop {
95 let updated = self.inner.change_notify.notified();
96 if let Some(session_id) = self.get() {
97 return session_id;
98 }
99
100 updated.await;
101 }
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use std::time::Duration;
108
109 use tokio::time::timeout;
110
111 use super::{SessionId, SessionIdHandle};
112
113 const TEST_TIMEOUT: Duration = Duration::from_secs(5);
116
117 #[tokio::test]
118 async fn wait_for_update_returns_immediately_when_value_already_set() {
119 let handle = SessionIdHandle::empty();
122 handle.update(Some(SessionId::new("already-set").unwrap()));
123
124 let session_id = timeout(TEST_TIMEOUT, handle.wait_for_update())
125 .await
126 .expect("should return without waiting on a notification");
127 assert_eq!(session_id.as_str(), "already-set");
128 }
129
130 #[tokio::test]
131 async fn wait_for_update_wakes_on_concurrent_update() {
132 let handle = SessionIdHandle::empty();
136 let waiter = handle.clone();
137 let waiter_task = tokio::spawn(async move { waiter.wait_for_update().await });
138
139 tokio::task::yield_now().await;
142 handle.update(Some(SessionId::new("session-123").unwrap()));
143
144 let session_id = timeout(TEST_TIMEOUT, waiter_task)
145 .await
146 .expect("waiter should wake within the timeout")
147 .expect("waiter task should not panic");
148 assert_eq!(session_id.as_str(), "session-123");
149 }
150
151 #[tokio::test]
152 async fn wait_for_update_ignores_clears_and_returns_first_non_empty_value() {
153 let handle = SessionIdHandle::empty();
157 let waiter = handle.clone();
158 let waiter_task = tokio::spawn(async move { waiter.wait_for_update().await });
159
160 tokio::task::yield_now().await;
161 handle.update(None);
163 tokio::task::yield_now().await;
164 handle.update(Some(SessionId::new("finally").unwrap()));
165
166 let session_id = timeout(TEST_TIMEOUT, waiter_task)
167 .await
168 .expect("waiter should wake within the timeout")
169 .expect("waiter task should not panic");
170 assert_eq!(session_id.as_str(), "finally");
171 }
172}