hyperactor/
actor_local.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//! Per-actor local storage, analogous to `thread_local!` but scoped to actor instances.
10//!
11//! Use [`ActorLocal`] to declare static variables whose values are isolated per actor.
12//!
13//! # Example
14//!
15//! ```ignore
16//! use hyperactor::ActorLocal;
17//! use hyperactor::actor_local::Entry;
18//!
19//! static REQUEST_COUNT: ActorLocal<u64> = ActorLocal::new();
20//!
21//! impl Handler<MyMessage> for MyActor {
22//!     async fn handle(&mut self, cx: &Context<Self>, msg: MyMessage) -> Result<(), Error> {
23//!         // Fluent style (most common)
24//!         *REQUEST_COUNT.entry(cx).or_default().get_mut() += 1;
25//!
26//!         // Or with and_modify
27//!         REQUEST_COUNT.entry(cx)
28//!             .and_modify(|v| *v += 1)
29//!             .or_insert(0);
30//!
31//!         // Or pattern matching style
32//!         match REQUEST_COUNT.entry(cx) {
33//!             Entry::Occupied(mut o) => {
34//!                 *o.get_mut() += 1;
35//!             }
36//!             Entry::Vacant(v) => {
37//!                 v.insert(0);
38//!             }
39//!         }
40//!
41//!         Ok(())
42//!     }
43//! }
44//! ```
45//!
46//! # Thread Safety
47//!
48//! Each [`ActorLocal`] has its own internal lock, so accessing different
49//! `ActorLocal` statics concurrently is safe and does not cause deadlocks.
50
51use std::any::Any;
52use std::marker::PhantomData;
53use std::sync::OnceLock;
54
55use crate::context;
56
57#[allow(dead_code)]
58mod weak_map {
59    //! Internal weak map implementation for actor-local storage.
60    //!
61    //! This module provides a typed map keyed by weak references, where entries
62    //! are automatically removed when the key is dropped.
63
64    use std::collections::HashMap;
65    use std::sync::Arc;
66    use std::sync::Mutex;
67    use std::sync::MutexGuard;
68    use std::sync::Weak;
69
70    /// Type-erased trait for WeakMap so WeakKeyInner can hold refs to any WeakMap<T>.
71    trait ErasedWeakMap: Send + Sync {
72        /// Remove entry by raw pointer to WeakKeyInner.
73        fn remove_by_ptr(&self, ptr: *const WeakKeyInner);
74    }
75
76    /// Wrapper around raw pointer for use as HashMap key.
77    /// Implements Hash/Eq based on data pointer address only, ignoring vtable.
78    /// This is necessary because vtables can be duplicated across codegen units.
79    #[derive(Clone, Copy)]
80    struct MapPtr(*const dyn ErasedWeakMap);
81
82    impl MapPtr {
83        /// Extract just the data pointer, ignoring the vtable.
84        fn data_ptr(&self) -> *const () {
85            self.0 as *const ()
86        }
87    }
88
89    impl PartialEq for MapPtr {
90        fn eq(&self, other: &Self) -> bool {
91            self.data_ptr() == other.data_ptr()
92        }
93    }
94
95    impl Eq for MapPtr {}
96
97    impl std::hash::Hash for MapPtr {
98        fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
99            self.data_ptr().hash(state);
100        }
101    }
102
103    // SAFETY: MapPtr is only used as a lookup key; we never dereference
104    // without first upgrading the accompanying Weak reference.
105    unsafe impl Send for MapPtr {}
106    // SAFETY: MapPtr is only used as a lookup key; we never dereference
107    // without first upgrading the accompanying Weak reference.
108    unsafe impl Sync for MapPtr {}
109
110    /// Inner key type, wrapped in Arc and tracked by WeakMaps.
111    struct WeakKeyInner {
112        /// WeakMaps that contain entries for this key.
113        /// On drop, we remove ourselves from all these maps.
114        maps: Mutex<HashMap<MapPtr, Weak<dyn ErasedWeakMap>>>,
115    }
116
117    impl WeakKeyInner {
118        fn unregister_map(&self, map_ptr: *const dyn ErasedWeakMap) {
119            let mut maps = self.maps.lock().unwrap();
120            maps.remove(&MapPtr(map_ptr));
121        }
122    }
123
124    impl Drop for WeakKeyInner {
125        // No deadlock possible: we hold self.maps and call map.remove_by_ptr (which
126        // locks map.entries). The reverse order is in WeakMapInner::drop. However,
127        // deadlock requires both upgrades to succeed, meaning both strong counts > 0.
128        // But we're in Drop, so our strong count is 0, and any Weak::upgrade to us
129        // from the other side will fail. At most one direction's upgrade succeeds.
130        fn drop(&mut self) {
131            let maps = self.maps.lock().unwrap();
132            let self_ptr = self as *const WeakKeyInner;
133            for weak_map in maps.values() {
134                if let Some(map) = weak_map.upgrade() {
135                    map.remove_by_ptr(self_ptr);
136                }
137            }
138        }
139    }
140
141    /// Key type allocated per storage. On drop, clears entries from all WeakMaps.
142    #[derive(Clone)]
143    pub struct WeakKey {
144        inner: Arc<WeakKeyInner>,
145    }
146
147    impl WeakKey {
148        pub fn new() -> Self {
149            Self {
150                inner: Arc::new(WeakKeyInner {
151                    maps: Mutex::new(HashMap::new()),
152                }),
153            }
154        }
155
156        fn as_ptr(&self) -> *const WeakKeyInner {
157            Arc::as_ptr(&self.inner)
158        }
159
160        fn downgrade(&self) -> Weak<WeakKeyInner> {
161            Arc::downgrade(&self.inner)
162        }
163
164        fn register_map(&self, map: Weak<dyn ErasedWeakMap>) {
165            let mut maps = self.inner.maps.lock().unwrap();
166            maps.insert(MapPtr(map.as_ptr()), map);
167        }
168
169        fn unregister_map(&self, map_ptr: *const dyn ErasedWeakMap) {
170            self.inner.unregister_map(map_ptr);
171        }
172
173        #[cfg(test)]
174        pub fn maps_len(&self) -> usize {
175            self.inner.maps.lock().unwrap().len()
176        }
177    }
178
179    /// Inner state of a WeakMap.
180    struct WeakMapInner<T: Send + 'static> {
181        entries: Mutex<HashMap<*const WeakKeyInner, (Weak<WeakKeyInner>, T)>>,
182    }
183
184    // SAFETY: The raw pointer is only used as a key for lookup and is always
185    // validated via the accompanying Weak<WeakKeyInner> before use.
186    unsafe impl<T: Send + 'static> Send for WeakMapInner<T> {}
187    // SAFETY: The raw pointer is only used as a key for lookup. Access to T
188    // is through Mutex which provides exclusive access, so T: Sync is not required.
189    unsafe impl<T: Send + 'static> Sync for WeakMapInner<T> {}
190
191    impl<T: Send + 'static> ErasedWeakMap for WeakMapInner<T> {
192        fn remove_by_ptr(&self, ptr: *const WeakKeyInner) {
193            let mut entries = self.entries.lock().unwrap();
194            entries.remove(&ptr);
195        }
196    }
197
198    impl<T: Send + 'static> Drop for WeakMapInner<T> {
199        // No deadlock possible: see comment on WeakKeyInner::drop. We hold self.entries
200        // and call key.unregister_map (which locks key.maps). Since we're dropping,
201        // our strong count is 0, so any Weak::upgrade to us will fail.
202        fn drop(&mut self) {
203            let entries = self.entries.lock().unwrap();
204            let self_ptr = self as *const WeakMapInner<T> as *const dyn ErasedWeakMap;
205            for (_, (weak_key, _)) in entries.iter() {
206                if let Some(key_inner) = weak_key.upgrade() {
207                    key_inner.unregister_map(self_ptr);
208                }
209            }
210        }
211    }
212
213    fn as_erased<T: Send + 'static>(inner: &Arc<WeakMapInner<T>>) -> Weak<dyn ErasedWeakMap> {
214        Arc::downgrade(inner) as Weak<dyn ErasedWeakMap>
215    }
216
217    /// Typed weak map with internal locking.
218    pub struct WeakMap<T: Send + 'static> {
219        inner: Arc<WeakMapInner<T>>,
220    }
221
222    impl<T: Send + 'static> WeakMap<T> {
223        pub fn new() -> Self {
224            Self {
225                inner: Arc::new(WeakMapInner {
226                    entries: Mutex::new(HashMap::new()),
227                }),
228            }
229        }
230
231        /// Get an entry into the weak map.
232        pub fn entry<'a>(&'a self, key: &'a WeakKey) -> Entry<'a, T> {
233            let key_ptr = key.as_ptr();
234            let guard = self.inner.entries.lock().unwrap();
235
236            if guard.contains_key(&key_ptr) {
237                Entry::Occupied(OccupiedEntry {
238                    guard,
239                    key_ptr,
240                    key,
241                    map_inner: &self.inner,
242                })
243            } else {
244                Entry::Vacant(VacantEntry {
245                    guard,
246                    key_ptr,
247                    key,
248                    map_inner: &self.inner,
249                })
250            }
251        }
252
253        #[cfg(test)]
254        fn contains_key(&self, key: &WeakKey) -> bool {
255            let guard = self.inner.entries.lock().unwrap();
256            guard.contains_key(&key.as_ptr())
257        }
258
259        #[cfg(test)]
260        fn get(&self, key: &WeakKey) -> Option<T>
261        where
262            T: Clone,
263        {
264            let guard = self.inner.entries.lock().unwrap();
265            guard.get(&key.as_ptr()).map(|(_, v)| v.clone())
266        }
267
268        #[cfg(test)]
269        fn len(&self) -> usize {
270            let guard = self.inner.entries.lock().unwrap();
271            guard.len()
272        }
273    }
274
275    /// Entry into weak map storage. Holds the lock until dropped.
276    ///
277    /// This follows the same pattern as [`std::collections::hash_map::Entry`].
278    pub enum Entry<'a, T: Send + 'static> {
279        /// Value exists for this key.
280        Occupied(OccupiedEntry<'a, T>),
281        /// No value for this key.
282        Vacant(VacantEntry<'a, T>),
283    }
284
285    /// Entry for an occupied weak map slot.
286    ///
287    /// Provides access to the stored value and allows replacing or removing it.
288    pub struct OccupiedEntry<'a, T: Send + 'static> {
289        guard: MutexGuard<'a, HashMap<*const WeakKeyInner, (Weak<WeakKeyInner>, T)>>,
290        key_ptr: *const WeakKeyInner,
291        key: &'a WeakKey,
292        map_inner: &'a Arc<WeakMapInner<T>>,
293    }
294
295    /// Entry for a vacant weak map slot.
296    ///
297    /// Allows inserting a value into the slot.
298    pub struct VacantEntry<'a, T: Send + 'static> {
299        guard: MutexGuard<'a, HashMap<*const WeakKeyInner, (Weak<WeakKeyInner>, T)>>,
300        key_ptr: *const WeakKeyInner,
301        key: &'a WeakKey,
302        map_inner: &'a Arc<WeakMapInner<T>>,
303    }
304
305    impl<'a, T: Send + 'static> Entry<'a, T> {
306        /// Ensures a value is in the entry by inserting the default if empty,
307        /// and returns an [`OccupiedEntry`].
308        pub fn or_insert(self, default: T) -> OccupiedEntry<'a, T> {
309            match self {
310                Entry::Occupied(o) => o,
311                Entry::Vacant(v) => v.insert(default),
312            }
313        }
314
315        /// Ensures a value is in the entry by inserting the result of the default
316        /// function if empty, and returns an [`OccupiedEntry`].
317        pub fn or_insert_with<F: FnOnce() -> T>(self, f: F) -> OccupiedEntry<'a, T> {
318            match self {
319                Entry::Occupied(o) => o,
320                Entry::Vacant(v) => v.insert(f()),
321            }
322        }
323
324        /// Provides in-place mutable access to an occupied entry before any
325        /// potential inserts into the map.
326        pub fn and_modify<F: FnOnce(&mut T)>(mut self, f: F) -> Self {
327            if let Entry::Occupied(ref mut o) = self {
328                f(o.get_mut());
329            }
330            self
331        }
332    }
333
334    impl<'a, T: Send + Default + 'static> Entry<'a, T> {
335        /// Ensures a value is in the entry by inserting the default value if empty,
336        /// and returns an [`OccupiedEntry`].
337        pub fn or_default(self) -> OccupiedEntry<'a, T> {
338            self.or_insert_with(T::default)
339        }
340    }
341
342    impl<'a, T: Send + 'static> OccupiedEntry<'a, T> {
343        /// Gets a reference to the value in the entry.
344        pub fn get(&self) -> &T {
345            &self
346                .guard
347                .get(&self.key_ptr)
348                .expect("OccupiedEntry should have value")
349                .1
350        }
351
352        /// Gets a mutable reference to the value in the entry.
353        pub fn get_mut(&mut self) -> &mut T {
354            &mut self
355                .guard
356                .get_mut(&self.key_ptr)
357                .expect("OccupiedEntry should have value")
358                .1
359        }
360
361        /// Sets the value of the entry with the [`OccupiedEntry`]'s key,
362        /// and returns the entry's old value.
363        pub fn insert(&mut self, value: T) -> T {
364            let entry = self
365                .guard
366                .get_mut(&self.key_ptr)
367                .expect("OccupiedEntry should have value");
368            std::mem::replace(&mut entry.1, value)
369        }
370
371        /// Takes the value of the entry out of the map, and returns it.
372        pub fn remove(mut self) -> T {
373            let (_, value) = self
374                .guard
375                .remove(&self.key_ptr)
376                .expect("OccupiedEntry should have value");
377
378            // Unregister this map from the key
379            let map_ptr = Arc::as_ptr(self.map_inner) as *const dyn ErasedWeakMap;
380            self.key.unregister_map(map_ptr);
381
382            value
383        }
384    }
385
386    impl<'a, T: Send + 'static> VacantEntry<'a, T> {
387        /// Sets the value of the entry with the [`VacantEntry`]'s key,
388        /// and returns an [`OccupiedEntry`].
389        pub fn insert(mut self, value: T) -> OccupiedEntry<'a, T> {
390            // Register this map with the key for cleanup on key drop
391            self.key.register_map(as_erased(self.map_inner));
392
393            self.guard
394                .insert(self.key_ptr, (self.key.downgrade(), value));
395
396            OccupiedEntry {
397                guard: self.guard,
398                key_ptr: self.key_ptr,
399                key: self.key,
400                map_inner: self.map_inner,
401            }
402        }
403    }
404
405    #[cfg(test)]
406    mod tests {
407        use super::*;
408
409        #[test]
410        fn test_weak_key_creation() {
411            let key1 = WeakKey::new();
412            let key2 = WeakKey::new();
413
414            // Each key should have a unique pointer
415            assert_ne!(key1.as_ptr(), key2.as_ptr());
416        }
417
418        #[test]
419        fn test_weak_map_basic_operations() {
420            let map: WeakMap<u64> = WeakMap::new();
421            let key = WeakKey::new();
422
423            // Initially empty
424            assert!(!map.contains_key(&key));
425
426            // Insert via entry
427            match map.entry(&key) {
428                Entry::Vacant(v) => {
429                    v.insert(42);
430                }
431                Entry::Occupied(_) => panic!("expected vacant"),
432            }
433
434            // Verify insertion
435            assert!(map.contains_key(&key));
436            assert_eq!(map.get(&key), Some(42));
437
438            // Modify via entry
439            match map.entry(&key) {
440                Entry::Occupied(mut o) => {
441                    *o.get_mut() = 100;
442                }
443                Entry::Vacant(_) => panic!("expected occupied"),
444            }
445
446            // Verify modification
447            assert_eq!(map.get(&key), Some(100));
448
449            // Remove via entry
450            match map.entry(&key) {
451                Entry::Occupied(o) => {
452                    let removed = o.remove();
453                    assert_eq!(removed, 100);
454                }
455                Entry::Vacant(_) => panic!("expected occupied"),
456            }
457
458            // Verify removal
459            assert!(!map.contains_key(&key));
460        }
461
462        #[test]
463        fn test_key_drop_clears_map_entries() {
464            let map: WeakMap<String> = WeakMap::new();
465
466            {
467                let key = WeakKey::new();
468
469                // Insert value via entry
470                map.entry(&key).or_insert("hello".to_string());
471
472                // Verify present
473                assert!(map.contains_key(&key));
474                assert_eq!(map.len(), 1);
475
476                // key drops here
477            }
478
479            // After key drop, entry should be removed
480            assert_eq!(map.len(), 0);
481        }
482
483        #[test]
484        fn test_key_drop_clears_map_entries_with_scoped_key() {
485            let map: WeakMap<String> = WeakMap::new();
486
487            let weak_ref = {
488                let key = WeakKey::new();
489
490                // Insert value via entry
491                map.entry(&key).or_insert("hello".to_string());
492
493                // Verify present
494                assert!(map.contains_key(&key));
495
496                // Return a weak ref so we can verify cleanup happened
497                key.downgrade()
498                // key drops here
499            };
500
501            // The weak ref should now be dead
502            assert!(weak_ref.upgrade().is_none());
503        }
504
505        #[test]
506        fn test_multiple_maps_same_key() {
507            let map1: WeakMap<u64> = WeakMap::new();
508            let map2: WeakMap<String> = WeakMap::new();
509
510            {
511                let key = WeakKey::new();
512
513                // Insert into both maps via entry
514                map1.entry(&key).or_insert(42);
515                map2.entry(&key).or_insert("test".to_string());
516
517                // Verify both present
518                assert!(map1.contains_key(&key));
519                assert!(map2.contains_key(&key));
520                assert_eq!(map1.len(), 1);
521                assert_eq!(map2.len(), 1);
522
523                // key drops here
524            }
525
526            // After key drop, both maps should be cleared
527            assert_eq!(map1.len(), 0);
528            assert_eq!(map2.len(), 0);
529        }
530
531        #[test]
532        fn test_no_duplicate_map_registration() {
533            let map: WeakMap<u64> = WeakMap::new();
534            let key = WeakKey::new();
535
536            // Insert same key multiple times (should only register map once)
537            map.entry(&key).or_insert(1);
538            // Re-entry on occupied doesn't register again
539            *map.entry(&key).or_insert(2).get_mut() = 3;
540
541            // Should only have one map registered
542            assert_eq!(key.maps_len(), 1);
543        }
544
545        #[test]
546        fn test_unregister_map_on_remove() {
547            let map: WeakMap<u64> = WeakMap::new();
548            let key = WeakKey::new();
549
550            // Insert
551            map.entry(&key).or_insert(42);
552            assert_eq!(key.maps_len(), 1);
553
554            // Remove via entry
555            if let Entry::Occupied(o) = map.entry(&key) {
556                o.remove();
557            }
558
559            // Map should be unregistered
560            assert_eq!(key.maps_len(), 0);
561        }
562
563        #[test]
564        fn test_concurrent_access_different_maps() {
565            let map1: WeakMap<u64> = WeakMap::new();
566            let map2: WeakMap<u64> = WeakMap::new();
567            let key = WeakKey::new();
568
569            // Set up entries in both maps
570            map1.entry(&key).or_insert(1);
571            map2.entry(&key).or_insert(2);
572
573            // Verify both have correct values
574            assert_eq!(map1.get(&key), Some(1));
575            assert_eq!(map2.get(&key), Some(2));
576        }
577
578        #[test]
579        fn test_entry_or_default() {
580            let map: WeakMap<u64> = WeakMap::new();
581            let key = WeakKey::new();
582
583            // or_default on vacant
584            *map.entry(&key).or_default().get_mut() = 42;
585            assert_eq!(map.get(&key), Some(42));
586        }
587
588        #[test]
589        fn test_entry_and_modify() {
590            let map: WeakMap<u64> = WeakMap::new();
591            let key = WeakKey::new();
592
593            // and_modify on vacant (no-op), then or_insert
594            map.entry(&key).and_modify(|v| *v += 10).or_insert(5);
595            assert_eq!(map.get(&key), Some(5));
596
597            // and_modify on occupied
598            map.entry(&key).and_modify(|v| *v += 10).or_insert(0);
599            assert_eq!(map.get(&key), Some(15));
600        }
601
602        #[test]
603        fn test_map_drop_unregisters_from_keys() {
604            let key = WeakKey::new();
605
606            {
607                let map: WeakMap<u64> = WeakMap::new();
608                map.entry(&key).or_insert(42);
609
610                // Key should have the map registered
611                assert_eq!(key.maps_len(), 1);
612
613                // map drops here
614            }
615
616            // After map drop, key should have no maps registered
617            assert_eq!(key.maps_len(), 0);
618        }
619
620        #[test]
621        fn test_multiple_maps_drop_unregisters_all() {
622            let key = WeakKey::new();
623
624            {
625                let map1: WeakMap<u64> = WeakMap::new();
626                let map2: WeakMap<String> = WeakMap::new();
627
628                map1.entry(&key).or_insert(42);
629                map2.entry(&key).or_insert("test".to_string());
630
631                // Key should have both maps registered
632                assert_eq!(key.maps_len(), 2);
633
634                // Drop map1 first
635                drop(map1);
636                assert_eq!(key.maps_len(), 1);
637
638                // map2 drops here
639            }
640
641            // After all maps dropped, key should have no maps registered
642            assert_eq!(key.maps_len(), 0);
643        }
644    }
645}
646
647use weak_map::WeakKey;
648use weak_map::WeakMap;
649
650/// Type alias for the type-erased value stored in ActorLocalStorage.
651type ErasedValue = Box<dyn Any + Send>;
652
653/// Storage container for actor-local values.
654///
655/// Each actor instance has its own [`ActorLocalStorage`], which holds a
656/// type-erased map. When the storage is dropped, all entries in the map
657/// are automatically cleaned up.
658pub struct ActorLocalStorage {
659    map: WeakMap<ErasedValue>,
660}
661
662impl Default for ActorLocalStorage {
663    fn default() -> Self {
664        Self::new()
665    }
666}
667
668impl ActorLocalStorage {
669    /// Create a new empty storage.
670    pub fn new() -> Self {
671        Self {
672            map: WeakMap::new(),
673        }
674    }
675
676    /// Get a reference to the map.
677    fn map(&self) -> &WeakMap<ErasedValue> {
678        &self.map
679    }
680}
681
682/// Per-actor local storage slot.
683///
684/// Declare as a static and access from handler methods via the context:
685///
686/// ```ignore
687/// use hyperactor::actor_local::Entry;
688///
689/// static MY_LOCAL: ActorLocal<MyType> = ActorLocal::new();
690///
691/// fn handle(&mut self, cx: &Context<Self>, msg: MyMessage) {
692///     // Get an entry (holds the lock)
693///     let mut entry = MY_LOCAL.entry(cx);
694///
695///     // Use fluent API
696///     *MY_LOCAL.entry(cx).or_default().get_mut() += 1;
697///
698///     // Or pattern match
699///     match MY_LOCAL.entry(cx) {
700///         Entry::Occupied(mut o) => {
701///             let value = o.get_mut();
702///             // modify value...
703///         }
704///         Entry::Vacant(v) => {
705///             v.insert(initial_value);
706///         }
707///     }
708/// }
709/// ```
710///
711/// Each `ActorLocal` has its own internal lock, so accessing multiple
712/// `ActorLocal` statics simultaneously is safe and will not deadlock.
713pub struct ActorLocal<T: Send + 'static> {
714    key: OnceLock<WeakKey>,
715    _marker: PhantomData<fn() -> T>,
716}
717
718// SAFETY: ActorLocal stores a WeakKey (behind OnceLock) which is Send + Sync.
719unsafe impl<T: Send + 'static> Send for ActorLocal<T> {}
720// SAFETY: ActorLocal stores a WeakKey (behind OnceLock) which is Send + Sync.
721unsafe impl<T: Send + 'static> Sync for ActorLocal<T> {}
722
723impl<T: Send + 'static> ActorLocal<T> {
724    /// Create a new actor-local storage slot.
725    pub const fn new() -> Self {
726        Self {
727            key: OnceLock::new(),
728            _marker: PhantomData,
729        }
730    }
731
732    /// Get or initialize the WeakKey.
733    fn key(&self) -> &WeakKey {
734        self.key.get_or_init(WeakKey::new)
735    }
736
737    /// Get an entry into actor-local storage.
738    ///
739    /// The returned [`Entry`] holds the lock until dropped, allowing
740    /// mutable access without requiring `Clone`.
741    pub fn entry<'a, Cx: context::Actor>(&'a self, cx: &'a Cx) -> Entry<'a, T> {
742        let map = cx.instance().locals().map();
743        let key = self.key();
744        Entry::new(map.entry(key))
745    }
746}
747
748impl<T: Send + 'static> Default for ActorLocal<T> {
749    fn default() -> Self {
750        Self::new()
751    }
752}
753
754impl<T: Send + 'static> Clone for ActorLocal<T> {
755    fn clone(&self) -> Self {
756        Self {
757            key: self.key.clone(),
758            _marker: PhantomData,
759        }
760    }
761}
762
763/// Entry into actor-local storage with type-safe access.
764///
765/// This wraps the underlying type-erased entry and provides typed access
766/// through boxing and downcasting.
767pub enum Entry<'a, T: Send + 'static> {
768    /// Value exists for this key.
769    Occupied(OccupiedEntry<'a, T>),
770    /// No value for this key.
771    Vacant(VacantEntry<'a, T>),
772}
773
774impl<'a, T: Send + 'static> Entry<'a, T> {
775    fn new(entry: weak_map::Entry<'a, ErasedValue>) -> Self {
776        match entry {
777            weak_map::Entry::Occupied(o) => Entry::Occupied(OccupiedEntry(o, PhantomData)),
778            weak_map::Entry::Vacant(v) => Entry::Vacant(VacantEntry(v, PhantomData)),
779        }
780    }
781
782    /// Ensures a value is in the entry by inserting the default if empty,
783    /// and returns an [`OccupiedEntry`].
784    pub fn or_insert(self, default: T) -> OccupiedEntry<'a, T> {
785        match self {
786            Entry::Occupied(o) => o,
787            Entry::Vacant(v) => v.insert(default),
788        }
789    }
790
791    /// Ensures a value is in the entry by inserting the result of the default
792    /// function if empty, and returns an [`OccupiedEntry`].
793    pub fn or_insert_with<F: FnOnce() -> T>(self, f: F) -> OccupiedEntry<'a, T> {
794        match self {
795            Entry::Occupied(o) => o,
796            Entry::Vacant(v) => v.insert(f()),
797        }
798    }
799
800    /// Provides in-place mutable access to an occupied entry before any
801    /// potential inserts into the map.
802    pub fn and_modify<F: FnOnce(&mut T)>(mut self, f: F) -> Self {
803        if let Entry::Occupied(ref mut o) = self {
804            f(o.get_mut());
805        }
806        self
807    }
808}
809
810impl<'a, T: Send + Default + 'static> Entry<'a, T> {
811    /// Ensures a value is in the entry by inserting the default value if empty,
812    /// and returns an [`OccupiedEntry`].
813    pub fn or_default(self) -> OccupiedEntry<'a, T> {
814        self.or_insert_with(T::default)
815    }
816}
817
818/// Entry for an occupied actor-local slot with typed access.
819pub struct OccupiedEntry<'a, T: Send + 'static>(
820    weak_map::OccupiedEntry<'a, ErasedValue>,
821    PhantomData<fn() -> T>,
822);
823
824impl<'a, T: Send + 'static> OccupiedEntry<'a, T> {
825    /// Gets a reference to the value in the entry.
826    pub fn get(&self) -> &T {
827        self.0
828            .get()
829            .downcast_ref::<T>()
830            .expect("type mismatch in actor-local storage")
831    }
832
833    /// Gets a mutable reference to the value in the entry.
834    pub fn get_mut(&mut self) -> &mut T {
835        self.0
836            .get_mut()
837            .downcast_mut::<T>()
838            .expect("type mismatch in actor-local storage")
839    }
840
841    /// Sets the value of the entry, returning the old value.
842    pub fn insert(&mut self, value: T) -> T {
843        let old = self.0.insert(Box::new(value));
844        *old.downcast::<T>()
845            .expect("type mismatch in actor-local storage")
846    }
847
848    /// Takes the value out of the entry.
849    pub fn remove(self) -> T {
850        let value = self.0.remove();
851        *value
852            .downcast::<T>()
853            .expect("type mismatch in actor-local storage")
854    }
855}
856
857/// Entry for a vacant actor-local slot with typed access.
858pub struct VacantEntry<'a, T: Send + 'static>(
859    weak_map::VacantEntry<'a, ErasedValue>,
860    PhantomData<fn() -> T>,
861);
862
863impl<'a, T: Send + 'static> VacantEntry<'a, T> {
864    /// Sets the value of the entry, returning an occupied entry.
865    pub fn insert(self, value: T) -> OccupiedEntry<'a, T> {
866        OccupiedEntry(self.0.insert(Box::new(value)), PhantomData)
867    }
868}
869
870#[cfg(test)]
871mod tests {
872    use super::*;
873
874    #[test]
875    fn test_actor_local_storage_creation() {
876        let storage = ActorLocalStorage::new();
877        // Just verify it can be created
878        drop(storage);
879    }
880
881    #[test]
882    fn test_storage_drop_clears_all_actor_locals() {
883        let local1: ActorLocal<u64> = ActorLocal::new();
884        let local2: ActorLocal<String> = ActorLocal::new();
885
886        {
887            let storage = ActorLocalStorage::new();
888            let map = storage.map();
889
890            // Insert into both locals via entry
891            map.entry(local1.key()).or_insert(Box::new(42u64));
892            map.entry(local2.key())
893                .or_insert(Box::new("test".to_string()));
894
895            // Verify present and keys registered with the map
896            assert!(matches!(
897                map.entry(local1.key()),
898                weak_map::Entry::Occupied(_)
899            ));
900            assert!(matches!(
901                map.entry(local2.key()),
902                weak_map::Entry::Occupied(_)
903            ));
904            assert_eq!(local1.key().maps_len(), 1);
905            assert_eq!(local2.key().maps_len(), 1);
906
907            // storage drops here
908        }
909
910        // After storage drop, keys should have no maps registered
911        assert_eq!(local1.key().maps_len(), 0);
912        assert_eq!(local2.key().maps_len(), 0);
913    }
914
915    #[test]
916    fn test_multiple_storages_same_local() {
917        let local: ActorLocal<u64> = ActorLocal::new();
918
919        let storage1 = ActorLocalStorage::new();
920        let storage2 = ActorLocalStorage::new();
921
922        let map1 = storage1.map();
923        let map2 = storage2.map();
924        let key = local.key();
925
926        // Insert for both storages via entry
927        map1.entry(key).or_insert(Box::new(100u64));
928        map2.entry(key).or_insert(Box::new(200u64));
929
930        // Verify both present with correct values
931        assert_eq!(
932            *map1
933                .entry(key)
934                .or_insert(Box::new(0u64))
935                .get()
936                .downcast_ref::<u64>()
937                .unwrap(),
938            100
939        );
940        assert_eq!(
941            *map2
942                .entry(key)
943                .or_insert(Box::new(0u64))
944                .get()
945                .downcast_ref::<u64>()
946                .unwrap(),
947            200
948        );
949
950        // Drop storage1
951        drop(storage1);
952
953        // Only storage2's entry should remain
954        assert_eq!(
955            *map2
956                .entry(key)
957                .or_insert(Box::new(0u64))
958                .get()
959                .downcast_ref::<u64>()
960                .unwrap(),
961            200
962        );
963    }
964
965    #[test]
966    fn test_concurrent_access_different_locals() {
967        static LOCAL1: ActorLocal<u64> = ActorLocal::new();
968        static LOCAL2: ActorLocal<u64> = ActorLocal::new();
969
970        let storage = ActorLocalStorage::new();
971        let map = storage.map();
972
973        // Set up entries via entry API
974        map.entry(LOCAL1.key()).or_insert(Box::new(1u64));
975        map.entry(LOCAL2.key()).or_insert(Box::new(2u64));
976
977        // Verify correct values
978        assert_eq!(
979            *map.entry(LOCAL1.key())
980                .or_insert(Box::new(0u64))
981                .get()
982                .downcast_ref::<u64>()
983                .unwrap(),
984            1
985        );
986        assert_eq!(
987            *map.entry(LOCAL2.key())
988                .or_insert(Box::new(0u64))
989                .get()
990                .downcast_ref::<u64>()
991                .unwrap(),
992            2
993        );
994    }
995
996    #[test]
997    fn test_actor_local_clone_shares_key() {
998        let local1: ActorLocal<u64> = ActorLocal::new();
999        // Initialize the key before cloning
1000        let _ = local1.key();
1001        let local2 = local1.clone();
1002
1003        let storage = ActorLocalStorage::new();
1004        let map = storage.map();
1005
1006        // Insert via local1's key
1007        map.entry(local1.key()).or_insert(Box::new(42u64));
1008
1009        // Should be visible via local2's key (same key)
1010        assert_eq!(
1011            *map.entry(local2.key())
1012                .or_insert(Box::new(0u64))
1013                .get()
1014                .downcast_ref::<u64>()
1015                .unwrap(),
1016            42
1017        );
1018    }
1019
1020    #[test]
1021    fn test_entry_fluent_api() {
1022        let local: ActorLocal<u64> = ActorLocal::new();
1023        let storage = ActorLocalStorage::new();
1024        let map = storage.map();
1025        let key = local.key();
1026
1027        // Test or_default via Entry
1028        let entry = Entry::<u64>::new(map.entry(key));
1029        *entry.or_default().get_mut() = 42;
1030
1031        let entry = Entry::<u64>::new(map.entry(key));
1032        assert_eq!(*entry.or_default().get(), 42);
1033
1034        // Test and_modify
1035        let entry = Entry::<u64>::new(map.entry(key));
1036        entry.and_modify(|v| *v += 10).or_default();
1037
1038        let entry = Entry::<u64>::new(map.entry(key));
1039        assert_eq!(*entry.or_default().get(), 52);
1040    }
1041
1042    #[test]
1043    fn test_entry_pattern_matching() {
1044        let local: ActorLocal<u64> = ActorLocal::new();
1045        let storage = ActorLocalStorage::new();
1046        let map = storage.map();
1047        let key = local.key();
1048
1049        // Initially vacant
1050        let entry = Entry::<u64>::new(map.entry(key));
1051        match entry {
1052            Entry::Vacant(v) => {
1053                v.insert(100);
1054            }
1055            Entry::Occupied(_) => panic!("expected vacant"),
1056        }
1057
1058        // Now occupied
1059        let entry = Entry::<u64>::new(map.entry(key));
1060        match entry {
1061            Entry::Occupied(mut o) => {
1062                assert_eq!(*o.get(), 100);
1063                *o.get_mut() = 200;
1064            }
1065            Entry::Vacant(_) => panic!("expected occupied"),
1066        }
1067
1068        // Verify modification
1069        let entry = Entry::<u64>::new(map.entry(key));
1070        assert_eq!(*entry.or_default().get(), 200);
1071
1072        // Test remove
1073        let entry = Entry::<u64>::new(map.entry(key));
1074        match entry {
1075            Entry::Occupied(o) => {
1076                assert_eq!(o.remove(), 200);
1077            }
1078            Entry::Vacant(_) => panic!("expected occupied"),
1079        }
1080
1081        // Should be vacant again
1082        let entry = Entry::<u64>::new(map.entry(key));
1083        assert!(matches!(entry, Entry::Vacant(_)));
1084    }
1085
1086    #[test]
1087    fn test_occupied_entry_insert() {
1088        let local: ActorLocal<String> = ActorLocal::new();
1089        let storage = ActorLocalStorage::new();
1090        let map = storage.map();
1091        let key = local.key();
1092
1093        // Insert initial value
1094        let entry = Entry::<String>::new(map.entry(key));
1095        entry.or_insert("hello".to_string());
1096
1097        // Replace via insert and get old value
1098        let entry = Entry::<String>::new(map.entry(key));
1099        match entry {
1100            Entry::Occupied(mut o) => {
1101                let old = o.insert("world".to_string());
1102                assert_eq!(old, "hello");
1103            }
1104            Entry::Vacant(_) => panic!("expected occupied"),
1105        }
1106
1107        // Verify new value
1108        let entry = Entry::<String>::new(map.entry(key));
1109        assert_eq!(*entry.or_insert("".to_string()).get(), "world");
1110    }
1111}