1use std::sync::RwLock;
8use std::sync::atomic::{AtomicU32, Ordering};
9use std::time::{Duration, Instant};
10
11use chrono::{DateTime, Utc};
12use dashmap::DashMap;
13use serde::{Deserialize, Serialize};
14use tokio::sync::mpsc;
15
16use crate::error::{KernelError, KernelResult};
17use crate::ipc::KernelMessage;
18
19const DEFAULT_PIPE_CAPACITY: usize = 256;
21
22const DEFAULT_TTL_SECS: u64 = 60;
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct PipeInfo {
28 pub name: String,
30 pub capacity: usize,
32 pub created_at: DateTime<Utc>,
34 pub receiver_count: u32,
36 pub sender_count: u32,
38 pub ttl_after_empty_secs: u64,
40}
41
42pub struct NamedPipe {
44 pub name: String,
46 pub capacity: usize,
48 pub created_at: DateTime<Utc>,
50 sender: mpsc::Sender<KernelMessage>,
52 pub receiver_count: AtomicU32,
54 pub sender_count: AtomicU32,
56 pub ttl_after_empty: Duration,
58 pub last_active: RwLock<Instant>,
60}
61
62impl NamedPipe {
63 pub fn info(&self) -> PipeInfo {
65 PipeInfo {
66 name: self.name.clone(),
67 capacity: self.capacity,
68 created_at: self.created_at,
69 receiver_count: self.receiver_count.load(Ordering::Relaxed),
70 sender_count: self.sender_count.load(Ordering::Relaxed),
71 ttl_after_empty_secs: self.ttl_after_empty.as_secs(),
72 }
73 }
74
75 fn touch(&self) {
77 if let Ok(mut last) = self.last_active.write() {
78 *last = Instant::now();
79 }
80 }
81
82 pub fn is_expired(&self) -> bool {
84 let senders = self.sender_count.load(Ordering::Relaxed);
85 let receivers = self.receiver_count.load(Ordering::Relaxed);
86 if senders > 0 || receivers > 0 {
87 return false;
88 }
89 if let Ok(last) = self.last_active.read() {
90 last.elapsed() >= self.ttl_after_empty
91 } else {
92 false
93 }
94 }
95}
96
97pub struct NamedPipeRegistry {
99 pipes: DashMap<String, NamedPipe>,
100}
101
102impl NamedPipeRegistry {
103 pub fn new() -> Self {
105 Self {
106 pipes: DashMap::new(),
107 }
108 }
109
110 pub fn create(&self, name: impl Into<String>) -> KernelResult<mpsc::Receiver<KernelMessage>> {
115 self.create_with_options(name, DEFAULT_PIPE_CAPACITY, DEFAULT_TTL_SECS)
116 }
117
118 pub fn create_with_options(
120 &self,
121 name: impl Into<String>,
122 capacity: usize,
123 ttl_secs: u64,
124 ) -> KernelResult<mpsc::Receiver<KernelMessage>> {
125 let name = name.into();
126 if self.pipes.contains_key(&name) {
127 return Err(KernelError::Ipc(format!(
128 "named pipe '{name}' already exists"
129 )));
130 }
131
132 let (tx, rx) = mpsc::channel(capacity);
133 let pipe = NamedPipe {
134 name: name.clone(),
135 capacity,
136 created_at: Utc::now(),
137 sender: tx,
138 receiver_count: AtomicU32::new(1),
139 sender_count: AtomicU32::new(0),
140 ttl_after_empty: Duration::from_secs(ttl_secs),
141 last_active: RwLock::new(Instant::now()),
142 };
143
144 self.pipes.insert(name, pipe);
145 Ok(rx)
146 }
147
148 pub fn connect_sender(&self, name: &str) -> KernelResult<mpsc::Sender<KernelMessage>> {
152 let pipe = self
153 .pipes
154 .get(name)
155 .ok_or_else(|| KernelError::Ipc(format!("named pipe '{name}' not found")))?;
156
157 pipe.sender_count.fetch_add(1, Ordering::Relaxed);
158 pipe.touch();
159 Ok(pipe.sender.clone())
160 }
161
162 pub async fn send(&self, name: &str, msg: KernelMessage) -> KernelResult<()> {
167 let pipe = self
168 .pipes
169 .get(name)
170 .ok_or_else(|| KernelError::Ipc(format!("named pipe '{name}' not found")))?;
171
172 pipe.touch();
173 pipe.sender.try_send(msg).map_err(|e| match e {
174 mpsc::error::TrySendError::Full(_) => {
175 KernelError::Ipc(format!("named pipe '{name}' is full"))
176 }
177 mpsc::error::TrySendError::Closed(_) => {
178 KernelError::Ipc(format!("named pipe '{name}' receiver dropped"))
179 }
180 })
181 }
182
183 pub fn disconnect_sender(&self, name: &str) {
185 if let Some(pipe) = self.pipes.get(name) {
186 let prev = pipe.sender_count.fetch_sub(1, Ordering::Relaxed);
187 if prev == 0 {
188 pipe.sender_count.store(0, Ordering::Relaxed);
190 }
191 pipe.touch();
192 }
193 }
194
195 pub fn disconnect_receiver(&self, name: &str) {
197 if let Some(pipe) = self.pipes.get(name) {
198 let prev = pipe.receiver_count.fetch_sub(1, Ordering::Relaxed);
199 if prev == 0 {
200 pipe.receiver_count.store(0, Ordering::Relaxed);
201 }
202 pipe.touch();
203 }
204 }
205
206 pub fn remove(&self, name: &str) -> bool {
208 self.pipes.remove(name).is_some()
209 }
210
211 pub fn exists(&self, name: &str) -> bool {
213 self.pipes.contains_key(name)
214 }
215
216 pub fn list(&self) -> Vec<String> {
218 self.pipes.iter().map(|e| e.key().clone()).collect()
219 }
220
221 pub fn info(&self, name: &str) -> Option<PipeInfo> {
223 self.pipes.get(name).map(|p| p.info())
224 }
225
226 pub fn list_info(&self) -> Vec<PipeInfo> {
228 self.pipes.iter().map(|e| e.value().info()).collect()
229 }
230
231 pub fn len(&self) -> usize {
233 self.pipes.len()
234 }
235
236 pub fn is_empty(&self) -> bool {
238 self.pipes.is_empty()
239 }
240
241 pub fn cleanup_expired(&self) -> Vec<String> {
245 let expired: Vec<String> = self
246 .pipes
247 .iter()
248 .filter(|e| e.value().is_expired())
249 .map(|e| e.key().clone())
250 .collect();
251
252 for name in &expired {
253 self.pipes.remove(name);
254 }
255
256 expired
257 }
258}
259
260impl Default for NamedPipeRegistry {
261 fn default() -> Self {
262 Self::new()
263 }
264}
265
266impl clawft_types::Registry for NamedPipeRegistry {
269 type Value = PipeInfo;
270
271 fn get(&self, key: &str) -> Option<Self::Value> {
272 self.info(key)
273 }
274
275 fn list_keys(&self) -> Vec<String> {
276 self.list()
277 }
278
279 fn contains(&self, key: &str) -> bool {
280 self.exists(key)
281 }
282
283 fn count(&self) -> usize {
284 self.len()
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use crate::ipc::{MessagePayload, MessageTarget};
292
293 fn make_msg(text: &str) -> KernelMessage {
294 KernelMessage::new(
295 1,
296 MessageTarget::Process(2),
297 MessagePayload::Text(text.into()),
298 )
299 }
300
301 #[test]
302 fn create_and_exists() {
303 let registry = NamedPipeRegistry::new();
304 let _rx = registry.create("test-pipe").unwrap();
305 assert!(registry.exists("test-pipe"));
306 assert_eq!(registry.len(), 1);
307 }
308
309 #[test]
310 fn create_duplicate_fails() {
311 let registry = NamedPipeRegistry::new();
312 let _rx = registry.create("dup").unwrap();
313 let err = registry.create("dup").unwrap_err();
314 assert!(err.to_string().contains("already exists"));
315 }
316
317 #[tokio::test]
318 async fn send_and_receive_roundtrip() {
319 let registry = NamedPipeRegistry::new();
320 let mut rx = registry.create("pipe-1").unwrap();
321
322 let msg = make_msg("hello pipe");
323 registry.send("pipe-1", msg).await.unwrap();
324
325 let received = rx.recv().await.unwrap();
326 match &received.payload {
327 MessagePayload::Text(t) => assert_eq!(t, "hello pipe"),
328 other => panic!("expected Text, got: {other:?}"),
329 }
330 }
331
332 #[tokio::test]
333 async fn multiple_senders_fan_in() {
334 let registry = NamedPipeRegistry::new();
335 let mut rx = registry.create("fan-in").unwrap();
336
337 let tx1 = registry.connect_sender("fan-in").unwrap();
338 let tx2 = registry.connect_sender("fan-in").unwrap();
339
340 tx1.send(make_msg("from-1")).await.unwrap();
341 tx2.send(make_msg("from-2")).await.unwrap();
342
343 let m1 = rx.recv().await.unwrap();
344 let m2 = rx.recv().await.unwrap();
345
346 let texts: Vec<String> = [m1, m2]
347 .iter()
348 .filter_map(|m| match &m.payload {
349 MessagePayload::Text(t) => Some(t.clone()),
350 _ => None,
351 })
352 .collect();
353 assert!(texts.contains(&"from-1".to_string()));
354 assert!(texts.contains(&"from-2".to_string()));
355 }
356
357 #[tokio::test]
358 async fn capacity_limit_returns_error() {
359 let registry = NamedPipeRegistry::new();
360 let _rx = registry.create_with_options("tiny", 2, 60).unwrap();
361
362 registry.send("tiny", make_msg("1")).await.unwrap();
364 registry.send("tiny", make_msg("2")).await.unwrap();
365
366 let err = registry.send("tiny", make_msg("3")).await.unwrap_err();
368 assert!(err.to_string().contains("full"));
369 }
370
371 #[test]
372 fn send_to_nonexistent_pipe_fails() {
373 let registry = NamedPipeRegistry::new();
374 let rt = tokio::runtime::Builder::new_current_thread()
375 .build()
376 .unwrap();
377 let err = rt
378 .block_on(registry.send("nope", make_msg("x")))
379 .unwrap_err();
380 assert!(err.to_string().contains("not found"));
381 }
382
383 #[test]
384 fn connect_sender_to_nonexistent_fails() {
385 let registry = NamedPipeRegistry::new();
386 let err = registry.connect_sender("nope").unwrap_err();
387 assert!(err.to_string().contains("not found"));
388 }
389
390 #[test]
391 fn remove_pipe() {
392 let registry = NamedPipeRegistry::new();
393 let _rx = registry.create("removable").unwrap();
394 assert!(registry.exists("removable"));
395 assert!(registry.remove("removable"));
396 assert!(!registry.exists("removable"));
397 }
398
399 #[test]
400 fn list_pipes() {
401 let registry = NamedPipeRegistry::new();
402 let _r1 = registry.create("alpha").unwrap();
403 let _r2 = registry.create("beta").unwrap();
404
405 let names = registry.list();
406 assert_eq!(names.len(), 2);
407 assert!(names.contains(&"alpha".to_string()));
408 assert!(names.contains(&"beta".to_string()));
409 }
410
411 #[test]
412 fn pipe_info() {
413 let registry = NamedPipeRegistry::new();
414 let _rx = registry.create_with_options("info-pipe", 128, 120).unwrap();
415
416 let info = registry.info("info-pipe").unwrap();
417 assert_eq!(info.name, "info-pipe");
418 assert_eq!(info.capacity, 128);
419 assert_eq!(info.receiver_count, 1);
420 assert_eq!(info.sender_count, 0);
421 assert_eq!(info.ttl_after_empty_secs, 120);
422 }
423
424 #[test]
425 fn sender_count_tracking() {
426 let registry = NamedPipeRegistry::new();
427 let _rx = registry.create("counted").unwrap();
428
429 let _tx1 = registry.connect_sender("counted").unwrap();
430 let _tx2 = registry.connect_sender("counted").unwrap();
431
432 let info = registry.info("counted").unwrap();
433 assert_eq!(info.sender_count, 2);
434
435 registry.disconnect_sender("counted");
436 let info = registry.info("counted").unwrap();
437 assert_eq!(info.sender_count, 1);
438 }
439
440 #[test]
441 fn ttl_cleanup() {
442 let registry = NamedPipeRegistry::new();
443 let _rx = registry.create_with_options("ephemeral", 16, 0).unwrap();
445
446 registry.disconnect_receiver("ephemeral");
448
449 let expired = registry.cleanup_expired();
451 assert_eq!(expired, vec!["ephemeral".to_string()]);
452 assert!(!registry.exists("ephemeral"));
453 }
454
455 #[test]
456 fn pipe_not_expired_with_senders() {
457 let registry = NamedPipeRegistry::new();
458 let _rx = registry.create_with_options("active", 16, 0).unwrap();
459 let _tx = registry.connect_sender("active").unwrap();
460
461 let expired = registry.cleanup_expired();
463 assert!(expired.is_empty());
464 }
465
466 #[test]
467 fn default_registry() {
468 let registry = NamedPipeRegistry::default();
469 assert!(registry.is_empty());
470 }
471
472 #[test]
473 fn pipe_info_serde_roundtrip() {
474 let info = PipeInfo {
475 name: "test".into(),
476 capacity: 256,
477 created_at: Utc::now(),
478 receiver_count: 1,
479 sender_count: 2,
480 ttl_after_empty_secs: 60,
481 };
482 let json = serde_json::to_string(&info).unwrap();
483 let restored: PipeInfo = serde_json::from_str(&json).unwrap();
484 assert_eq!(restored.name, "test");
485 assert_eq!(restored.sender_count, 2);
486 }
487}