| use super::util::*; |
| use crate::sync::alloc; |
| use crate::Slab; |
| use loom::sync::{Condvar, Mutex}; |
| use loom::thread; |
| use std::sync::{ |
| atomic::{AtomicBool, Ordering}, |
| Arc, |
| }; |
| |
| #[test] |
| fn take_local() { |
| run_model("take_local", || { |
| let slab = Arc::new(Slab::new()); |
| |
| let s = slab.clone(); |
| let t1 = thread::spawn(move || { |
| let idx = s.insert(1).expect("insert"); |
| assert_eq!(s.get(idx).unwrap(), 1); |
| assert_eq!(s.take(idx), Some(1)); |
| assert!(s.get(idx).is_none()); |
| let idx = s.insert(2).expect("insert"); |
| assert_eq!(s.get(idx).unwrap(), 2); |
| assert_eq!(s.take(idx), Some(2)); |
| assert!(s.get(idx).is_none()); |
| }); |
| |
| let s = slab.clone(); |
| let t2 = thread::spawn(move || { |
| let idx = s.insert(3).expect("insert"); |
| assert_eq!(s.get(idx).unwrap(), 3); |
| assert_eq!(s.take(idx), Some(3)); |
| assert!(s.get(idx).is_none()); |
| let idx = s.insert(4).expect("insert"); |
| assert_eq!(s.get(idx).unwrap(), 4); |
| assert_eq!(s.take(idx), Some(4)); |
| assert!(s.get(idx).is_none()); |
| }); |
| |
| let s = slab; |
| let idx1 = s.insert(5).expect("insert"); |
| assert_eq!(s.get(idx1).unwrap(), 5); |
| let idx2 = s.insert(6).expect("insert"); |
| assert_eq!(s.get(idx2).unwrap(), 6); |
| assert_eq!(s.take(idx1), Some(5)); |
| assert!(s.get(idx1).is_none()); |
| assert_eq!(s.get(idx2).unwrap(), 6); |
| assert_eq!(s.take(idx2), Some(6)); |
| assert!(s.get(idx2).is_none()); |
| |
| t1.join().expect("thread 1 should not panic"); |
| t2.join().expect("thread 2 should not panic"); |
| }); |
| } |
| |
| #[test] |
| fn take_remote() { |
| run_model("take_remote", || { |
| let slab = Arc::new(Slab::new()); |
| |
| let idx1 = slab.insert(1).expect("insert"); |
| assert_eq!(slab.get(idx1).unwrap(), 1); |
| let idx2 = slab.insert(2).expect("insert"); |
| assert_eq!(slab.get(idx2).unwrap(), 2); |
| |
| let idx3 = slab.insert(3).expect("insert"); |
| assert_eq!(slab.get(idx3).unwrap(), 3); |
| |
| let s = slab.clone(); |
| let t1 = thread::spawn(move || { |
| assert_eq!(s.get(idx2).unwrap(), 2); |
| assert_eq!(s.take(idx2), Some(2)); |
| }); |
| |
| let s = slab.clone(); |
| let t2 = thread::spawn(move || { |
| assert_eq!(s.get(idx3).unwrap(), 3); |
| assert_eq!(s.take(idx3), Some(3)); |
| }); |
| |
| t1.join().expect("thread 1 should not panic"); |
| t2.join().expect("thread 2 should not panic"); |
| |
| assert_eq!(slab.get(idx1).unwrap(), 1); |
| assert!(slab.get(idx2).is_none()); |
| assert!(slab.get(idx3).is_none()); |
| }); |
| } |
| |
| #[test] |
| fn racy_take() { |
| run_model("racy_take", || { |
| let slab = Arc::new(Slab::new()); |
| |
| let idx = slab.insert(1).expect("insert"); |
| assert_eq!(slab.get(idx).unwrap(), 1); |
| |
| let s1 = slab.clone(); |
| let s2 = slab.clone(); |
| |
| let t1 = thread::spawn(move || s1.take(idx)); |
| let t2 = thread::spawn(move || s2.take(idx)); |
| |
| let r1 = t1.join().expect("thread 1 should not panic"); |
| let r2 = t2.join().expect("thread 2 should not panic"); |
| |
| assert!( |
| r1.is_none() || r2.is_none(), |
| "both threads should not have removed the value" |
| ); |
| assert_eq!( |
| r1.or(r2), |
| Some(1), |
| "one thread should have removed the value" |
| ); |
| assert!(slab.get(idx).is_none()); |
| }); |
| } |
| |
| #[test] |
| fn racy_take_local() { |
| run_model("racy_take_local", || { |
| let slab = Arc::new(Slab::new()); |
| |
| let idx = slab.insert(1).expect("insert"); |
| assert_eq!(slab.get(idx).unwrap(), 1); |
| |
| let s = slab.clone(); |
| let t2 = thread::spawn(move || s.take(idx)); |
| let r1 = slab.take(idx); |
| let r2 = t2.join().expect("thread 2 should not panic"); |
| |
| assert!( |
| r1.is_none() || r2.is_none(), |
| "both threads should not have removed the value" |
| ); |
| assert!( |
| r1.or(r2).is_some(), |
| "one thread should have removed the value" |
| ); |
| assert!(slab.get(idx).is_none()); |
| }); |
| } |
| |
| #[test] |
| fn concurrent_insert_take() { |
| run_model("concurrent_insert_remove", || { |
| let slab = Arc::new(Slab::new()); |
| let pair = Arc::new((Mutex::new(None), Condvar::new())); |
| |
| let slab2 = slab.clone(); |
| let pair2 = pair.clone(); |
| let remover = thread::spawn(move || { |
| let (lock, cvar) = &*pair2; |
| for i in 0..2 { |
| test_println!("--- remover i={} ---", i); |
| let mut next = lock.lock().unwrap(); |
| while next.is_none() { |
| next = cvar.wait(next).unwrap(); |
| } |
| let key = next.take().unwrap(); |
| assert_eq!(slab2.take(key), Some(i)); |
| cvar.notify_one(); |
| } |
| }); |
| |
| let (lock, cvar) = &*pair; |
| for i in 0..2 { |
| test_println!("--- inserter i={} ---", i); |
| let key = slab.insert(i).expect("insert"); |
| |
| let mut next = lock.lock().unwrap(); |
| *next = Some(key); |
| cvar.notify_one(); |
| |
| // Wait for the item to be removed. |
| while next.is_some() { |
| next = cvar.wait(next).unwrap(); |
| } |
| |
| assert!(slab.get(key).is_none()); |
| } |
| |
| remover.join().unwrap(); |
| }) |
| } |
| |
| #[test] |
| fn take_remote_and_reuse() { |
| run_model("take_remote_and_reuse", || { |
| let slab = Arc::new(Slab::new_with_config::<TinyConfig>()); |
| |
| let idx1 = slab.insert(1).expect("insert"); |
| let idx2 = slab.insert(2).expect("insert"); |
| let idx3 = slab.insert(3).expect("insert"); |
| let idx4 = slab.insert(4).expect("insert"); |
| |
| assert_eq!(slab.get(idx1).unwrap(), 1, "slab: {:#?}", slab); |
| assert_eq!(slab.get(idx2).unwrap(), 2, "slab: {:#?}", slab); |
| assert_eq!(slab.get(idx3).unwrap(), 3, "slab: {:#?}", slab); |
| assert_eq!(slab.get(idx4).unwrap(), 4, "slab: {:#?}", slab); |
| |
| let s = slab.clone(); |
| let t1 = thread::spawn(move || { |
| assert_eq!(s.take(idx1), Some(1), "slab: {:#?}", s); |
| }); |
| |
| let idx1 = slab.insert(5).expect("insert"); |
| t1.join().expect("thread 1 should not panic"); |
| |
| assert_eq!(slab.get(idx1).unwrap(), 5, "slab: {:#?}", slab); |
| assert_eq!(slab.get(idx2).unwrap(), 2, "slab: {:#?}", slab); |
| assert_eq!(slab.get(idx3).unwrap(), 3, "slab: {:#?}", slab); |
| assert_eq!(slab.get(idx4).unwrap(), 4, "slab: {:#?}", slab); |
| }); |
| } |
| |
| fn store_when_free<C: crate::Config>(slab: &Arc<Slab<usize, C>>, t: usize) -> usize { |
| loop { |
| test_println!("try store {:?}", t); |
| if let Some(key) = slab.insert(t) { |
| test_println!("inserted at {:#x}", key); |
| return key; |
| } |
| test_println!("retrying; slab is full..."); |
| thread::yield_now(); |
| } |
| } |
| |
| struct TinierConfig; |
| |
| impl crate::Config for TinierConfig { |
| const INITIAL_PAGE_SIZE: usize = 2; |
| const MAX_PAGES: usize = 1; |
| } |
| |
| #[test] |
| fn concurrent_remove_remote_and_reuse() { |
| let mut model = loom::model::Builder::new(); |
| model.max_branches = 100000; |
| run_builder("concurrent_remove_remote_and_reuse", model, || { |
| let slab = Arc::new(Slab::new_with_config::<TinierConfig>()); |
| |
| let idx1 = slab.insert(1).unwrap(); |
| let idx2 = slab.insert(2).unwrap(); |
| |
| assert_eq!(slab.get(idx1).unwrap(), 1, "slab: {:#?}", slab); |
| assert_eq!(slab.get(idx2).unwrap(), 2, "slab: {:#?}", slab); |
| |
| let s = slab.clone(); |
| let s2 = slab.clone(); |
| |
| let t1 = thread::spawn(move || { |
| s.take(idx1).expect("must remove"); |
| }); |
| |
| let t2 = thread::spawn(move || { |
| s2.take(idx2).expect("must remove"); |
| }); |
| |
| let idx3 = store_when_free(&slab, 3); |
| t1.join().expect("thread 1 should not panic"); |
| t2.join().expect("thread 1 should not panic"); |
| |
| assert!(slab.get(idx1).is_none(), "slab: {:#?}", slab); |
| assert!(slab.get(idx2).is_none(), "slab: {:#?}", slab); |
| assert_eq!(slab.get(idx3).unwrap(), 3, "slab: {:#?}", slab); |
| }); |
| } |
| |
| struct SetDropped { |
| val: usize, |
| dropped: std::sync::Arc<AtomicBool>, |
| } |
| |
| struct AssertDropped { |
| dropped: std::sync::Arc<AtomicBool>, |
| } |
| |
| impl AssertDropped { |
| fn new(val: usize) -> (Self, SetDropped) { |
| let dropped = std::sync::Arc::new(AtomicBool::new(false)); |
| let val = SetDropped { |
| val, |
| dropped: dropped.clone(), |
| }; |
| (Self { dropped }, val) |
| } |
| |
| fn assert_dropped(&self) { |
| assert!( |
| self.dropped.load(Ordering::SeqCst), |
| "value should have been dropped!" |
| ); |
| } |
| } |
| |
| impl Drop for SetDropped { |
| fn drop(&mut self) { |
| self.dropped.store(true, Ordering::SeqCst); |
| } |
| } |
| |
| #[test] |
| fn remove_local() { |
| run_model("remove_local", || { |
| let slab = Arc::new(Slab::new_with_config::<TinyConfig>()); |
| let slab2 = slab.clone(); |
| |
| let (dropped, item) = AssertDropped::new(1); |
| let idx = slab.insert(item).expect("insert"); |
| |
| let guard = slab.get(idx).unwrap(); |
| |
| assert!(slab.remove(idx)); |
| |
| let t1 = thread::spawn(move || { |
| let g = slab2.get(idx); |
| drop(g); |
| }); |
| |
| assert!(slab.get(idx).is_none()); |
| |
| t1.join().expect("thread 1 should not panic"); |
| |
| drop(guard); |
| assert!(slab.get(idx).is_none()); |
| dropped.assert_dropped(); |
| }) |
| } |
| |
| #[test] |
| fn remove_remote() { |
| run_model("remove_remote", || { |
| let slab = Arc::new(Slab::new_with_config::<TinyConfig>()); |
| let slab2 = slab.clone(); |
| |
| let (dropped, item) = AssertDropped::new(1); |
| let idx = slab.insert(item).expect("insert"); |
| |
| assert!(slab.remove(idx)); |
| let t1 = thread::spawn(move || { |
| let g = slab2.get(idx); |
| drop(g); |
| }); |
| |
| t1.join().expect("thread 1 should not panic"); |
| |
| assert!(slab.get(idx).is_none()); |
| dropped.assert_dropped(); |
| }); |
| } |
| |
| #[test] |
| fn remove_remote_during_insert() { |
| run_model("remove_remote_during_insert", || { |
| let slab = Arc::new(Slab::new_with_config::<TinyConfig>()); |
| let slab2 = slab.clone(); |
| |
| let (dropped, item) = AssertDropped::new(1); |
| let idx = slab.insert(item).expect("insert"); |
| |
| let t1 = thread::spawn(move || { |
| let g = slab2.get(idx); |
| assert_ne!(g.as_ref().map(|v| v.val), Some(2)); |
| drop(g); |
| }); |
| |
| let (_, item) = AssertDropped::new(2); |
| assert!(slab.remove(idx)); |
| let idx2 = slab.insert(item).expect("insert"); |
| |
| t1.join().expect("thread 1 should not panic"); |
| |
| assert!(slab.get(idx).is_none()); |
| assert!(slab.get(idx2).is_some()); |
| dropped.assert_dropped(); |
| }); |
| } |
| |
| #[test] |
| fn unique_iter() { |
| run_model("unique_iter", || { |
| let mut slab = Arc::new(Slab::new()); |
| |
| let s = slab.clone(); |
| let t1 = thread::spawn(move || { |
| s.insert(1).expect("insert"); |
| s.insert(2).expect("insert"); |
| }); |
| |
| let s = slab.clone(); |
| let t2 = thread::spawn(move || { |
| s.insert(3).expect("insert"); |
| s.insert(4).expect("insert"); |
| }); |
| |
| t1.join().expect("thread 1 should not panic"); |
| t2.join().expect("thread 2 should not panic"); |
| |
| let slab = Arc::get_mut(&mut slab).expect("other arcs should be dropped"); |
| let items: Vec<_> = slab.unique_iter().map(|&i| i).collect(); |
| assert!(items.contains(&1), "items: {:?}", items); |
| assert!(items.contains(&2), "items: {:?}", items); |
| assert!(items.contains(&3), "items: {:?}", items); |
| assert!(items.contains(&4), "items: {:?}", items); |
| }); |
| } |
| |
| #[test] |
| fn custom_page_sz() { |
| let mut model = loom::model::Builder::new(); |
| model.max_branches = 100000; |
| model.check(|| { |
| let slab = Slab::<usize>::new_with_config::<TinyConfig>(); |
| |
| for i in 0..1024usize { |
| test_println!("{}", i); |
| let k = slab.insert(i).expect("insert"); |
| let v = slab.get(k).expect("get"); |
| assert_eq!(v, i, "slab: {:#?}", slab); |
| } |
| }); |
| } |
| |
| #[test] |
| fn max_refs() { |
| struct LargeGenConfig; |
| |
| // Configure the slab with a very large number of bits for the generation |
| // counter. That way, there will be very few bits for the ref count left |
| // over, and this test won't have to malloc millions of references. |
| impl crate::cfg::Config for LargeGenConfig { |
| const INITIAL_PAGE_SIZE: usize = 2; |
| const MAX_THREADS: usize = 32; |
| const MAX_PAGES: usize = 2; |
| } |
| |
| let mut model = loom::model::Builder::new(); |
| model.max_branches = 100000; |
| model.check(|| { |
| let slab = Slab::new_with_config::<LargeGenConfig>(); |
| let key = slab.insert("hello world").unwrap(); |
| let max = crate::page::slot::RefCount::<LargeGenConfig>::MAX; |
| |
| // Create the maximum number of concurrent references to the entry. |
| let mut refs = (0..max) |
| .map(|_| slab.get(key).unwrap()) |
| // Store the refs in a vec so they don't get dropped immediately. |
| .collect::<Vec<_>>(); |
| |
| assert!(slab.get(key).is_none()); |
| |
| // After dropping a ref, we should now be able to access the slot again. |
| drop(refs.pop()); |
| let ref1 = slab.get(key); |
| assert!(ref1.is_some()); |
| |
| // Ref1 should max out the number of references again. |
| assert!(slab.get(key).is_none()); |
| }) |
| } |
| |
| mod free_list_reuse { |
| use super::*; |
| struct TinyConfig; |
| |
| impl crate::cfg::Config for TinyConfig { |
| const INITIAL_PAGE_SIZE: usize = 2; |
| } |
| |
| #[test] |
| fn local_remove() { |
| run_model("free_list_reuse::local_remove", || { |
| let slab = Slab::new_with_config::<TinyConfig>(); |
| |
| let t1 = slab.insert("hello").expect("insert"); |
| let t2 = slab.insert("world").expect("insert"); |
| assert_eq!( |
| crate::page::indices::<TinyConfig>(t1).1, |
| 0, |
| "1st slot should be on 0th page" |
| ); |
| assert_eq!( |
| crate::page::indices::<TinyConfig>(t2).1, |
| 0, |
| "2nd slot should be on 0th page" |
| ); |
| let t3 = slab.insert("earth").expect("insert"); |
| assert_eq!( |
| crate::page::indices::<TinyConfig>(t3).1, |
| 1, |
| "3rd slot should be on 1st page" |
| ); |
| |
| slab.remove(t2); |
| let t4 = slab.insert("universe").expect("insert"); |
| assert_eq!( |
| crate::page::indices::<TinyConfig>(t4).1, |
| 0, |
| "2nd slot should be reused (0th page)" |
| ); |
| |
| slab.remove(t1); |
| let _ = slab.insert("goodbye").expect("insert"); |
| assert_eq!( |
| crate::page::indices::<TinyConfig>(t4).1, |
| 0, |
| "1st slot should be reused (0th page)" |
| ); |
| }); |
| } |
| |
| #[test] |
| fn local_take() { |
| run_model("free_list_reuse::local_take", || { |
| let slab = Slab::new_with_config::<TinyConfig>(); |
| |
| let t1 = slab.insert("hello").expect("insert"); |
| let t2 = slab.insert("world").expect("insert"); |
| assert_eq!( |
| crate::page::indices::<TinyConfig>(t1).1, |
| 0, |
| "1st slot should be on 0th page" |
| ); |
| assert_eq!( |
| crate::page::indices::<TinyConfig>(t2).1, |
| 0, |
| "2nd slot should be on 0th page" |
| ); |
| let t3 = slab.insert("earth").expect("insert"); |
| assert_eq!( |
| crate::page::indices::<TinyConfig>(t3).1, |
| 1, |
| "3rd slot should be on 1st page" |
| ); |
| |
| assert_eq!(slab.take(t2), Some("world")); |
| let t4 = slab.insert("universe").expect("insert"); |
| assert_eq!( |
| crate::page::indices::<TinyConfig>(t4).1, |
| 0, |
| "2nd slot should be reused (0th page)" |
| ); |
| |
| assert_eq!(slab.take(t1), Some("hello")); |
| let _ = slab.insert("goodbye").expect("insert"); |
| assert_eq!( |
| crate::page::indices::<TinyConfig>(t4).1, |
| 0, |
| "1st slot should be reused (0th page)" |
| ); |
| }); |
| } |
| } |
| |
| #[test] |
| fn vacant_entry() { |
| run_model("vacant_entry", || { |
| let slab = Arc::new(Slab::new()); |
| let entry = slab.vacant_entry().unwrap(); |
| let key: usize = entry.key(); |
| |
| let slab2 = slab.clone(); |
| let t1 = thread::spawn(move || { |
| test_dbg!(slab2.get(key)); |
| }); |
| |
| entry.insert("hello world"); |
| t1.join().unwrap(); |
| |
| assert_eq!(slab.get(key).expect("get"), "hello world"); |
| }); |
| } |
| |
| #[test] |
| fn vacant_entry_2() { |
| run_model("vacant_entry_2", || { |
| let slab = Arc::new(Slab::new()); |
| let entry = slab.vacant_entry().unwrap(); |
| let key: usize = entry.key(); |
| |
| let slab2 = slab.clone(); |
| let slab3 = slab.clone(); |
| let t1 = thread::spawn(move || { |
| test_dbg!(slab2.get(key)); |
| }); |
| |
| entry.insert("hello world"); |
| let t2 = thread::spawn(move || { |
| test_dbg!(slab3.get(key)); |
| }); |
| |
| t1.join().unwrap(); |
| t2.join().unwrap(); |
| assert_eq!(slab.get(key).expect("get"), "hello world"); |
| }); |
| } |
| |
| #[test] |
| fn vacant_entry_remove() { |
| run_model("vacant_entry_remove", || { |
| let slab = Arc::new(Slab::new()); |
| let entry = slab.vacant_entry().unwrap(); |
| let key: usize = entry.key(); |
| |
| let slab2 = slab.clone(); |
| let t1 = thread::spawn(move || { |
| assert!(!slab2.remove(key)); |
| }); |
| |
| t1.join().unwrap(); |
| |
| entry.insert("hello world"); |
| assert_eq!(slab.get(key).expect("get"), "hello world"); |
| }); |
| } |
| |
| #[test] |
| fn owned_entry_send_out_of_local() { |
| run_model("owned_entry_send_out_of_local", || { |
| let slab = Arc::new(Slab::<alloc::Track<String>>::new()); |
| let key1 = slab |
| .insert(alloc::Track::new(String::from("hello"))) |
| .expect("insert item 1"); |
| let key2 = slab |
| .insert(alloc::Track::new(String::from("goodbye"))) |
| .expect("insert item 2"); |
| |
| let item1 = slab.clone().get_owned(key1).expect("get key1"); |
| let item2 = slab.clone().get_owned(key2).expect("get key2"); |
| let slab2 = slab.clone(); |
| |
| test_dbg!(slab.remove(key1)); |
| |
| let t1 = thread::spawn(move || { |
| assert_eq!(item1.get_ref(), &String::from("hello")); |
| drop(item1); |
| }); |
| let t2 = thread::spawn(move || { |
| assert_eq!(item2.get_ref(), &String::from("goodbye")); |
| test_dbg!(slab2.remove(key2)); |
| drop(item2); |
| }); |
| |
| t1.join().unwrap(); |
| t2.join().unwrap(); |
| |
| assert!(slab.get(key1).is_none()); |
| assert!(slab.get(key2).is_none()); |
| }); |
| } |
| |
| #[test] |
| fn owned_entrys_outlive_slab() { |
| run_model("owned_entrys_outlive_slab", || { |
| let slab = Arc::new(Slab::<alloc::Track<String>>::new()); |
| let key1 = slab |
| .insert(alloc::Track::new(String::from("hello"))) |
| .expect("insert item 1"); |
| let key2 = slab |
| .insert(alloc::Track::new(String::from("goodbye"))) |
| .expect("insert item 2"); |
| |
| let item1_1 = slab.clone().get_owned(key1).expect("get key1"); |
| let item1_2 = slab.clone().get_owned(key1).expect("get key1 again"); |
| let item2 = slab.clone().get_owned(key2).expect("get key2"); |
| drop(slab); |
| |
| let t1 = thread::spawn(move || { |
| assert_eq!(item1_1.get_ref(), &String::from("hello")); |
| drop(item1_1); |
| }); |
| |
| let t2 = thread::spawn(move || { |
| assert_eq!(item2.get_ref(), &String::from("goodbye")); |
| drop(item2); |
| }); |
| |
| t1.join().unwrap(); |
| t2.join().unwrap(); |
| |
| assert_eq!(item1_2.get_ref(), &String::from("hello")); |
| }); |
| } |
| |
| #[test] |
| fn owned_entry_ping_pong() { |
| run_model("owned_entry_ping_pong", || { |
| let slab = Arc::new(Slab::<alloc::Track<String>>::new()); |
| let key1 = slab |
| .insert(alloc::Track::new(String::from("hello"))) |
| .expect("insert item 1"); |
| let key2 = slab |
| .insert(alloc::Track::new(String::from("world"))) |
| .expect("insert item 2"); |
| |
| let item1 = slab.clone().get_owned(key1).expect("get key1"); |
| let slab2 = slab.clone(); |
| let slab3 = slab.clone(); |
| |
| let t1 = thread::spawn(move || { |
| assert_eq!(item1.get_ref(), &String::from("hello")); |
| slab2.remove(key1); |
| item1 |
| }); |
| |
| let t2 = thread::spawn(move || { |
| let item2 = slab3.clone().get_owned(key2).unwrap(); |
| assert_eq!(item2.get_ref(), &String::from("world")); |
| slab3.remove(key1); |
| item2 |
| }); |
| |
| let item1 = t1.join().unwrap(); |
| let item2 = t2.join().unwrap(); |
| |
| assert_eq!(item1.get_ref(), &String::from("hello")); |
| assert_eq!(item2.get_ref(), &String::from("world")); |
| }); |
| } |
| |
| #[test] |
| fn owned_entry_drop_from_other_threads() { |
| run_model("owned_entry_drop_from_other_threads", || { |
| let slab = Arc::new(Slab::<alloc::Track<String>>::new()); |
| let key1 = slab |
| .insert(alloc::Track::new(String::from("hello"))) |
| .expect("insert item 1"); |
| let item1 = slab.clone().get_owned(key1).expect("get key1"); |
| |
| let slab2 = slab.clone(); |
| |
| let t1 = thread::spawn(move || { |
| let slab = slab2.clone(); |
| let key2 = slab |
| .insert(alloc::Track::new(String::from("goodbye"))) |
| .expect("insert item 1"); |
| let item2 = slab.clone().get_owned(key2).expect("get key1"); |
| let t2 = thread::spawn(move || { |
| assert_eq!(item2.get_ref(), &String::from("goodbye")); |
| test_dbg!(slab2.remove(key1)); |
| drop(item2) |
| }); |
| assert_eq!(item1.get_ref(), &String::from("hello")); |
| test_dbg!(slab.remove(key2)); |
| drop(item1); |
| (t2, key2) |
| }); |
| |
| let (t2, key2) = t1.join().unwrap(); |
| test_dbg!(slab.get(key1)); |
| test_dbg!(slab.get(key2)); |
| |
| t2.join().unwrap(); |
| |
| assert!(slab.get(key1).is_none()); |
| assert!(slab.get(key2).is_none()); |
| }); |
| } |