forest/message_pool/
mpool_locker.rs1use crate::shim::address::Address;
5use ahash::HashMap;
6use parking_lot::Mutex;
7use std::sync::Arc;
8use tokio::sync::OwnedMutexGuard;
9
10#[derive(derive_more::Deref)]
23pub struct MpoolLocker {
24 inner: Mutex<HashMap<Address, Arc<tokio::sync::Mutex<()>>>>,
25}
26
27impl MpoolLocker {
28 pub fn new() -> Self {
30 Self {
31 inner: Mutex::new(HashMap::default()),
32 }
33 }
34
35 pub async fn take_lock(&self, addr: Address) -> OwnedMutexGuard<()> {
38 let mutex = {
39 let mut map = self.lock();
40 map.retain(|_, v| Arc::strong_count(v) > 1);
41 map.entry(addr)
42 .or_insert_with(|| Arc::new(tokio::sync::Mutex::new(())))
43 .clone()
44 };
45 mutex.lock_owned().await
46 }
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52 use tokio::sync::{Barrier, oneshot};
53 use tokio::time::{Duration, timeout};
54
55 #[tokio::test]
56 async fn test_take_lock_serializes_same_address() {
57 let locker = Arc::new(MpoolLocker::new());
58 let addr = Address::new_id(1);
59
60 let (first_acquired_tx, first_acquired_rx) = oneshot::channel();
61 let (release_first_tx, release_first_rx) = oneshot::channel();
62 let (second_acquired_tx, second_acquired_rx) = oneshot::channel();
63
64 let locker2 = locker.clone();
65 let t1 = tokio::spawn(async move {
66 let _guard = locker2.take_lock(addr).await;
67 let _ = first_acquired_tx.send(());
68 let _ = release_first_rx.await;
69 });
70
71 first_acquired_rx.await.unwrap();
73
74 let locker3 = locker.clone();
75 let t2 = tokio::spawn(async move {
76 let _guard = locker3.take_lock(addr).await;
77 let _ = second_acquired_tx.send(());
78 });
79
80 assert!(
82 timeout(Duration::from_millis(50), second_acquired_rx)
83 .await
84 .is_err(),
85 "second task should not acquire the same address lock while first holds it"
86 );
87
88 let _ = release_first_tx.send(());
89 t1.await.unwrap();
90 t2.await.unwrap();
91 }
92
93 #[tokio::test]
94 async fn test_take_lock_allows_different_addresses() {
95 let locker = Arc::new(MpoolLocker::new());
96 let addr_a = Address::new_id(1);
97 let addr_b = Address::new_id(2);
98
99 let acquired_barrier = Arc::new(Barrier::new(2));
100
101 let locker2 = locker.clone();
102 let barrier_a = acquired_barrier.clone();
103 let t1 = tokio::spawn(async move {
104 let _guard = locker2.take_lock(addr_a).await;
105 barrier_a.wait().await;
106 });
107
108 let locker3 = locker.clone();
109 let barrier_b = acquired_barrier.clone();
110 let t2 = tokio::spawn(async move {
111 let _guard = locker3.take_lock(addr_b).await;
112 barrier_b.wait().await;
113 });
114
115 timeout(Duration::from_millis(200), async {
116 t1.await.unwrap();
117 t2.await.unwrap();
118 })
119 .await
120 .expect("different address locks should be acquired in parallel");
121 }
122
123 #[tokio::test]
124 async fn test_take_lock_prunes_idle_entries() {
125 let locker = MpoolLocker::new();
126 let addr_a = Address::new_id(1);
127 let addr_b = Address::new_id(2);
128
129 {
130 let _guard = locker.take_lock(addr_a).await;
131 assert_eq!(locker.inner.lock().len(), 1);
132 }
133 let _guard_b = locker.take_lock(addr_b).await;
135 assert_eq!(
136 locker.inner.lock().len(),
137 1,
138 "idle entry for addr_a should have been pruned"
139 );
140 }
141}