Skip to main content

autocore_std/
shm.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3use std::time::Duration;
4use log::{info, warn};
5use shared_memory::Shmem;
6use tokio::sync::Mutex;
7use mechutil::shm::ShmContext;
8
9/// Wrapper for Shared Memory pointer (stored as address).
10#[derive(Debug, Clone, Copy)]
11pub struct ShmPtr(pub usize);
12
13impl ShmPtr {
14    pub fn new<T>(ptr: *mut T) -> Self {
15        Self(ptr as usize)
16    }
17
18    pub fn as_ptr<T>(&self) -> *mut T {
19        self.0 as *mut T
20    }
21}
22
23/// Wrapper for Shmem to implement Send + Sync
24pub struct ThreadSafeShmem(pub Shmem);
25
26unsafe impl Send for ThreadSafeShmem {}
27unsafe impl Sync for ThreadSafeShmem {}
28
29impl std::fmt::Debug for ThreadSafeShmem {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        f.debug_tuple("ThreadSafeShmem")
32            .field(&self.0.get_os_id())
33            .finish()
34    }
35}
36
37impl std::ops::Deref for ThreadSafeShmem {
38    type Target = Shmem;
39    fn deref(&self) -> &Self::Target {
40        &self.0
41    }
42}
43
44/// Send+Sync wrapper for mechutil's SHM context handle.
45#[derive(Clone)]
46pub struct SendShmContext(pub Arc<Mutex<Option<Arc<ShmContext>>>>);
47
48unsafe impl Send for SendShmContext {}
49unsafe impl Sync for SendShmContext {}
50
51/// A generic store for mapping names to Shared Memory pointers.
52///
53/// `_context` keeps the `Arc<ShmContext>` (and therefore the underlying
54/// `Shmem` mapping) alive for as long as any `ShmMap` clone exists. Without
55/// this anchor, dropping or replacing the original `Arc<ShmContext>` (e.g.
56/// when a new `configure_shm` arrives) would `munmap` the segment and leave
57/// the map's raw pointers dangling — and the next write through one of them
58/// would segfault.
59#[derive(Debug, Clone)]
60pub struct ShmMap {
61    pointers: Arc<HashMap<String, ShmPtr>>,
62    _context: Option<Arc<ShmContext>>,
63}
64
65impl ShmMap {
66    /// Build an `ShmMap` with no anchored context. Only safe when the caller
67    /// guarantees that the pointers' backing memory outlives every `ShmMap`
68    /// clone. Prefer `with_context` for anything sourced from a real
69    /// `ShmContext::resolve` / `resolve_shm_pointers` call.
70    pub fn new(pointers: HashMap<String, ShmPtr>) -> Self {
71        Self {
72            pointers: Arc::new(pointers),
73            _context: None,
74        }
75    }
76
77    /// Build an `ShmMap` that anchors its lifetime to `context`. The `Arc`
78    /// keeps the `Shmem` mapping alive even if its original owner (e.g. the
79    /// IPC client's `shm_context` slot) is later replaced or dropped.
80    pub fn with_context(pointers: HashMap<String, ShmPtr>, context: Arc<ShmContext>) -> Self {
81        Self {
82            pointers: Arc::new(pointers),
83            _context: Some(context),
84        }
85    }
86
87    pub fn get<T>(&self, name: &str) -> Option<*mut T> {
88        self.pointers.get(name).map(|p| p.as_ptr())
89    }
90    
91    pub fn get_raw(&self, name: &str) -> Option<usize> {
92        self.pointers.get(name).map(|p| p.0)
93    }
94
95    /// Read a value from shared memory. 
96    /// SAFETY: Caller must ensure T matches the type of data at the pointer.
97    pub unsafe fn read<T: Copy>(&self, name: &str) -> Option<T> {
98        self.get::<T>(name).map(|ptr| *ptr)
99    }
100
101    /// Write a value to shared memory.
102    /// SAFETY: Caller must ensure T matches the type of data at the pointer.
103    pub unsafe fn write<T: Copy>(&self, name: &str, value: T) -> bool {
104        if let Some(ptr) = self.get::<T>(name) {
105            *ptr = value;
106            true
107        } else {
108            false
109        }
110    }
111
112    /// Returns the number of pointers in the map.
113    pub fn len(&self) -> usize {
114        self.pointers.len()
115    }
116}
117
118/// Helper to wait for SHM context and resolve pointers.
119/// 
120/// * `ctx_handle`: The SendShmContext wrapper.
121/// * `names`: List of variable names to resolve.
122/// * `timeout_ms`: Max time to wait for configuration.
123pub async fn resolve_shm_pointers(
124    ctx_handle: SendShmContext,
125    names: Vec<String>,
126    timeout_ms: u64
127) -> Option<ShmMap> {
128    tokio::task::spawn_blocking(move || {
129        let ctx = ctx_handle;
130        let shm_ctx = &ctx.0;
131        let mut attempts = 0;
132        let max_attempts = timeout_ms / 100;
133
134        loop {
135            // Use try_lock to avoid needing async lock in blocking task
136            if let Ok(guard) = shm_ctx.try_lock() {
137                if let Some(ref ctx) = *guard {
138                    info!("SHM context received, resolving {} pointers...", names.len());
139                    let mut pointers = HashMap::new();
140                    let mut mapped = 0;
141
142                    for name in &names {
143                        if let Some(ptr) = unsafe { ctx.get_pointer(name) } {
144                            pointers.insert(name.clone(), ShmPtr::new(ptr));
145                            mapped += 1;
146                        }
147                    }
148                    info!("SHM pointers resolved: {}/{}", mapped, names.len());
149                    // Clone the Arc<ShmContext> into the map so its mmap stays
150                    // valid even if the IPC client later replaces the slot
151                    // (e.g. on a second configure_shm).
152                    return Some(ShmMap::with_context(pointers, Arc::clone(ctx)));
153                }
154            }
155            
156            attempts += 1;
157            if attempts > max_attempts {
158                warn!("Timed out waiting for SHM context after {}ms", timeout_ms);
159                return None;
160            }
161            
162            std::thread::sleep(Duration::from_millis(100));
163        }
164    }).await.unwrap_or(None)
165}