Skip to main content

agave_scheduling_utils/
thread_aware_account_locks.rs

1use {
2    ahash::AHashMap,
3    solana_pubkey::Pubkey,
4    std::{
5        collections::hash_map::Entry,
6        fmt::{Debug, Display},
7        ops::{BitAnd, BitAndAssign, Sub},
8    },
9};
10
11pub const MAX_THREADS: usize = u64::BITS as usize;
12
13/// Identifier for a thread
14pub type ThreadId = usize; // 0..MAX_THREADS-1
15
16type LockCount = u32;
17
18/// A bit-set of threads an account is scheduled or can be scheduled for.
19#[derive(Copy, Clone, PartialEq, Eq)]
20pub struct ThreadSet(u64);
21
22#[derive(Debug)]
23struct AccountWriteLocks {
24    thread_id: ThreadId,
25    lock_count: LockCount,
26}
27
28#[derive(Debug)]
29struct AccountReadLocks {
30    thread_set: ThreadSet,
31    lock_counts: [LockCount; MAX_THREADS],
32}
33
34/// Account locks.
35/// Write Locks - only one thread can hold a write lock at a time.
36///     Contains how many write locks are held by the thread.
37/// Read Locks - multiple threads can hold a read lock at a time.
38///     Contains thread-set for easily checking which threads are scheduled.
39#[derive(Debug, Default)]
40struct AccountLocks {
41    pub write_locks: Option<AccountWriteLocks>,
42    pub read_locks: Option<AccountReadLocks>,
43}
44
45/// `try_lock_accounts` may fail for different reasons:
46#[derive(Debug, PartialEq, Eq)]
47pub enum TryLockError {
48    /// Outstanding conflicts with multiple threads.
49    MultipleConflicts,
50    /// Outstanding conflict (if any) not in `allowed_threads`.
51    ThreadNotAllowed,
52}
53
54/// Thread-aware account locks which allows for scheduling on threads
55/// that already hold locks on the account. This is useful for allowing
56/// queued transactions to be scheduled on a thread while the transaction
57/// is still being executed on the thread.
58#[derive(Debug)]
59pub struct ThreadAwareAccountLocks {
60    /// Number of threads.
61    num_threads: usize, // 0..MAX_THREADS
62    /// Locks for each account. An account should only have an entry if there
63    /// is at least one lock.
64    locks: AHashMap<Pubkey, AccountLocks>,
65}
66
67impl ThreadAwareAccountLocks {
68    /// Creates a new `ThreadAwareAccountLocks` with the given number of threads.
69    pub fn new(num_threads: usize) -> Self {
70        assert!(num_threads > 0, "num threads must be > 0");
71        assert!(
72            num_threads <= MAX_THREADS,
73            "num threads must be <= {MAX_THREADS}"
74        );
75
76        Self {
77            num_threads,
78            locks: AHashMap::new(),
79        }
80    }
81
82    /// Returns the `ThreadId` if the accounts are able to be locked
83    /// for the given thread, otherwise `None` is returned.
84    /// `allowed_threads` is a set of threads that the caller restricts locking to.
85    /// If accounts are schedulable, then they are locked for the thread
86    /// selected by the `thread_selector` function.
87    /// `thread_selector` is only called if all accounts are schdulable, meaning
88    /// that the `thread_set` passed to `thread_selector` is non-empty.
89    pub fn try_lock_accounts<'a>(
90        &mut self,
91        write_account_locks: impl Iterator<Item = &'a Pubkey> + Clone,
92        read_account_locks: impl Iterator<Item = &'a Pubkey> + Clone,
93        allowed_threads: ThreadSet,
94        thread_selector: impl FnOnce(ThreadSet) -> ThreadId,
95    ) -> Result<ThreadId, TryLockError> {
96        let schedulable_threads = self
97            .accounts_schedulable_threads(write_account_locks.clone(), read_account_locks.clone())
98            .ok_or(TryLockError::MultipleConflicts)?;
99        let schedulable_threads = schedulable_threads & allowed_threads;
100        if schedulable_threads.is_empty() {
101            return Err(TryLockError::ThreadNotAllowed);
102        }
103
104        let thread_id = thread_selector(schedulable_threads);
105        self.lock_accounts(write_account_locks, read_account_locks, thread_id);
106        Ok(thread_id)
107    }
108
109    /// Unlocks the accounts for the given thread.
110    pub fn unlock_accounts<'a>(
111        &mut self,
112        write_account_locks: impl Iterator<Item = &'a Pubkey>,
113        read_account_locks: impl Iterator<Item = &'a Pubkey>,
114        thread_id: ThreadId,
115    ) {
116        for account in write_account_locks {
117            self.write_unlock_account(account, thread_id);
118        }
119
120        for account in read_account_locks {
121            self.read_unlock_account(account, thread_id);
122        }
123    }
124
125    /// Returns `ThreadSet` that the given accounts can be scheduled on.
126    fn accounts_schedulable_threads<'a>(
127        &self,
128        write_account_locks: impl Iterator<Item = &'a Pubkey>,
129        read_account_locks: impl Iterator<Item = &'a Pubkey>,
130    ) -> Option<ThreadSet> {
131        let mut schedulable_threads = ThreadSet::any(self.num_threads);
132
133        for account in write_account_locks {
134            schedulable_threads &= self.write_schedulable_threads(account);
135            if schedulable_threads.is_empty() {
136                return None;
137            }
138        }
139
140        for account in read_account_locks {
141            schedulable_threads &= self.read_schedulable_threads(account);
142            if schedulable_threads.is_empty() {
143                return None;
144            }
145        }
146
147        Some(schedulable_threads)
148    }
149
150    /// Returns `ThreadSet` of schedulable threads for the given readable account.
151    fn read_schedulable_threads(&self, account: &Pubkey) -> ThreadSet {
152        self.schedulable_threads::<false>(account)
153    }
154
155    /// Returns `ThreadSet` of schedulable threads for the given writable account.
156    fn write_schedulable_threads(&self, account: &Pubkey) -> ThreadSet {
157        self.schedulable_threads::<true>(account)
158    }
159
160    /// Returns `ThreadSet` of schedulable threads.
161    /// If there are no locks, then all threads are schedulable.
162    /// If only write-locked, then only the thread holding the write lock is schedulable.
163    /// If a mix of locks, then only the write thread is schedulable.
164    /// If only read-locked, the only write-schedulable thread is if a single thread
165    ///   holds all read locks. Otherwise, no threads are write-schedulable.
166    /// If only read-locked, all threads are read-schedulable.
167    fn schedulable_threads<const WRITE: bool>(&self, account: &Pubkey) -> ThreadSet {
168        match self.locks.get(account) {
169            None => ThreadSet::any(self.num_threads),
170            Some(AccountLocks {
171                write_locks: None,
172                read_locks: Some(read_locks),
173            }) => {
174                if WRITE {
175                    read_locks
176                        .thread_set
177                        .only_one_contained()
178                        .map(ThreadSet::only)
179                        .unwrap_or_else(ThreadSet::none)
180                } else {
181                    ThreadSet::any(self.num_threads)
182                }
183            }
184            Some(AccountLocks {
185                write_locks: Some(write_locks),
186                read_locks: None,
187            }) => ThreadSet::only(write_locks.thread_id),
188            Some(AccountLocks {
189                write_locks: Some(write_locks),
190                read_locks: Some(read_locks),
191            }) => {
192                assert_eq!(
193                    read_locks.thread_set.only_one_contained(),
194                    Some(write_locks.thread_id)
195                );
196                read_locks.thread_set
197            }
198            Some(AccountLocks {
199                write_locks: None,
200                read_locks: None,
201            }) => unreachable!(),
202        }
203    }
204
205    /// Add locks for all writable and readable accounts on `thread_id`.
206    fn lock_accounts<'a>(
207        &mut self,
208        write_account_locks: impl Iterator<Item = &'a Pubkey>,
209        read_account_locks: impl Iterator<Item = &'a Pubkey>,
210        thread_id: ThreadId,
211    ) {
212        assert!(
213            thread_id < self.num_threads,
214            "thread_id must be < num_threads"
215        );
216        for account in write_account_locks {
217            self.write_lock_account(account, thread_id);
218        }
219
220        for account in read_account_locks {
221            self.read_lock_account(account, thread_id);
222        }
223    }
224
225    /// Locks the given `account` for writing on `thread_id`.
226    /// Panics if the account is already locked for writing on another thread.
227    fn write_lock_account(&mut self, account: &Pubkey, thread_id: ThreadId) {
228        let entry = self.locks.entry(*account).or_default();
229
230        let AccountLocks {
231            write_locks,
232            read_locks,
233        } = entry;
234
235        if let Some(read_locks) = read_locks {
236            assert_eq!(
237                read_locks.thread_set.only_one_contained(),
238                Some(thread_id),
239                "outstanding read lock must be on same thread"
240            );
241        }
242
243        if let Some(write_locks) = write_locks {
244            assert_eq!(
245                write_locks.thread_id, thread_id,
246                "outstanding write lock must be on same thread"
247            );
248            write_locks.lock_count = write_locks.lock_count.wrapping_add(1);
249        } else {
250            *write_locks = Some(AccountWriteLocks {
251                thread_id,
252                lock_count: 1,
253            });
254        }
255    }
256
257    /// Unlocks the given `account` for writing on `thread_id`.
258    /// Panics if the account is not locked for writing on `thread_id`.
259    fn write_unlock_account(&mut self, account: &Pubkey, thread_id: ThreadId) {
260        let Entry::Occupied(mut entry) = self.locks.entry(*account) else {
261            panic!("write lock must exist for account: {account}");
262        };
263
264        let AccountLocks {
265            write_locks: maybe_write_locks,
266            read_locks,
267        } = entry.get_mut();
268
269        let Some(write_locks) = maybe_write_locks else {
270            panic!("write lock must exist for account: {account}");
271        };
272
273        assert_eq!(
274            write_locks.thread_id, thread_id,
275            "outstanding write lock must be on same thread"
276        );
277
278        write_locks.lock_count = write_locks.lock_count.wrapping_sub(1);
279        if write_locks.lock_count == 0 {
280            *maybe_write_locks = None;
281            if read_locks.is_none() {
282                entry.remove();
283            }
284        }
285    }
286
287    /// Locks the given `account` for reading on `thread_id`.
288    /// Panics if the account is already locked for writing on another thread.
289    fn read_lock_account(&mut self, account: &Pubkey, thread_id: ThreadId) {
290        let AccountLocks {
291            write_locks,
292            read_locks,
293        } = self.locks.entry(*account).or_default();
294
295        if let Some(write_locks) = write_locks {
296            assert_eq!(
297                write_locks.thread_id, thread_id,
298                "outstanding write lock must be on same thread"
299            );
300        }
301
302        match read_locks {
303            Some(read_locks) => {
304                read_locks.thread_set.insert(thread_id);
305                read_locks.lock_counts[thread_id] =
306                    read_locks.lock_counts[thread_id].wrapping_add(1);
307            }
308            None => {
309                let mut lock_counts = [0; MAX_THREADS];
310                lock_counts[thread_id] = 1;
311                *read_locks = Some(AccountReadLocks {
312                    thread_set: ThreadSet::only(thread_id),
313                    lock_counts,
314                });
315            }
316        }
317    }
318
319    /// Unlocks the given `account` for reading on `thread_id`.
320    /// Panics if the account is not locked for reading on `thread_id`.
321    fn read_unlock_account(&mut self, account: &Pubkey, thread_id: ThreadId) {
322        let Entry::Occupied(mut entry) = self.locks.entry(*account) else {
323            panic!("read lock must exist for account: {account}");
324        };
325
326        let AccountLocks {
327            write_locks,
328            read_locks: maybe_read_locks,
329        } = entry.get_mut();
330
331        let Some(read_locks) = maybe_read_locks else {
332            panic!("read lock must exist for account: {account}");
333        };
334
335        assert!(
336            read_locks.thread_set.contains(thread_id),
337            "outstanding read lock must be on same thread"
338        );
339
340        read_locks.lock_counts[thread_id] = read_locks.lock_counts[thread_id].wrapping_sub(1);
341        if read_locks.lock_counts[thread_id] == 0 {
342            read_locks.thread_set.remove(thread_id);
343            if read_locks.thread_set.is_empty() {
344                *maybe_read_locks = None;
345                if write_locks.is_none() {
346                    entry.remove();
347                }
348            }
349        }
350    }
351}
352
353impl BitAnd for ThreadSet {
354    type Output = Self;
355
356    fn bitand(self, rhs: Self) -> Self::Output {
357        Self(self.0 & rhs.0)
358    }
359}
360
361impl BitAndAssign for ThreadSet {
362    fn bitand_assign(&mut self, rhs: Self) {
363        self.0 &= rhs.0;
364    }
365}
366
367impl Sub for ThreadSet {
368    type Output = Self;
369
370    fn sub(self, rhs: Self) -> Self::Output {
371        Self(self.0 & !rhs.0)
372    }
373}
374
375impl Display for ThreadSet {
376    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
377        write!(f, "ThreadSet({:#0width$b})", self.0, width = MAX_THREADS)
378    }
379}
380
381impl Debug for ThreadSet {
382    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
383        Display::fmt(self, f)
384    }
385}
386
387impl ThreadSet {
388    #[inline(always)]
389    pub const fn none() -> Self {
390        Self(0b0)
391    }
392
393    #[inline(always)]
394    pub const fn any(num_threads: usize) -> Self {
395        if num_threads == MAX_THREADS {
396            Self(u64::MAX)
397        } else {
398            Self(Self::as_flag(num_threads).wrapping_sub(1))
399        }
400    }
401
402    #[inline(always)]
403    pub const fn only(thread_id: ThreadId) -> Self {
404        Self(Self::as_flag(thread_id))
405    }
406
407    #[inline(always)]
408    pub fn num_threads(&self) -> u32 {
409        self.0.count_ones()
410    }
411
412    #[inline(always)]
413    pub fn only_one_contained(&self) -> Option<ThreadId> {
414        (self.num_threads() == 1).then_some(self.0.trailing_zeros() as ThreadId)
415    }
416
417    #[inline(always)]
418    pub fn is_empty(&self) -> bool {
419        self == &Self::none()
420    }
421
422    #[inline(always)]
423    pub fn contains(&self, thread_id: ThreadId) -> bool {
424        self.0 & Self::as_flag(thread_id) != 0
425    }
426
427    #[inline(always)]
428    pub fn insert(&mut self, thread_id: ThreadId) {
429        self.0 |= Self::as_flag(thread_id);
430    }
431
432    #[inline(always)]
433    pub fn remove(&mut self, thread_id: ThreadId) {
434        self.0 &= !Self::as_flag(thread_id);
435    }
436
437    #[inline(always)]
438    pub fn contained_threads_iter(self) -> impl Iterator<Item = ThreadId> {
439        ThreadSetIterator(self.0)
440    }
441
442    #[inline(always)]
443    const fn as_flag(thread_id: ThreadId) -> u64 {
444        0b1 << thread_id
445    }
446}
447
448struct ThreadSetIterator(u64);
449
450impl Iterator for ThreadSetIterator {
451    type Item = ThreadId;
452
453    fn next(&mut self) -> Option<Self::Item> {
454        if self.0 == 0 {
455            None
456        } else {
457            // Find the first set bit by counting trailing zeros.
458            // This is guaranteed to be < 64 because self.0 != 0.
459            let thread_id = self.0.trailing_zeros() as ThreadId;
460            // Clear the lowest set bit. The subtraction is safe because
461            // we know that self.0 != 0.
462            // Example (with 4 bits):
463            //  self.0 = 0b1010           // initial value
464            //  self.0 - 1 = 0b1001       // all bits at or after the lowest set bit are flipped
465            //  0b1010 & 0b1001 = 0b1000  // the lowest bit has been cleared
466            self.0 &= self.0.wrapping_sub(1);
467            Some(thread_id)
468        }
469    }
470}
471
472#[cfg(test)]
473mod tests {
474    use super::*;
475
476    const TEST_NUM_THREADS: usize = 4;
477    const TEST_ANY_THREADS: ThreadSet = ThreadSet::any(TEST_NUM_THREADS);
478
479    // Simple thread selector to select the first schedulable thread
480    fn test_thread_selector(thread_set: ThreadSet) -> ThreadId {
481        thread_set.contained_threads_iter().next().unwrap()
482    }
483
484    #[test]
485    #[should_panic(expected = "num threads must be > 0")]
486    fn test_too_few_num_threads() {
487        ThreadAwareAccountLocks::new(0);
488    }
489
490    #[test]
491    #[should_panic(expected = "num threads must be <=")]
492    fn test_too_many_num_threads() {
493        ThreadAwareAccountLocks::new(MAX_THREADS + 1);
494    }
495
496    #[test]
497    fn test_try_lock_accounts_none() {
498        let pk1 = Pubkey::new_unique();
499        let pk2 = Pubkey::new_unique();
500        let mut locks = ThreadAwareAccountLocks::new(TEST_NUM_THREADS);
501        locks.read_lock_account(&pk1, 2);
502        locks.read_lock_account(&pk1, 3);
503        assert_eq!(
504            locks.try_lock_accounts(
505                [&pk1].into_iter(),
506                [&pk2].into_iter(),
507                TEST_ANY_THREADS,
508                test_thread_selector
509            ),
510            Err(TryLockError::MultipleConflicts)
511        );
512    }
513
514    #[test]
515    fn test_try_lock_accounts_one() {
516        let pk1 = Pubkey::new_unique();
517        let pk2 = Pubkey::new_unique();
518        let mut locks = ThreadAwareAccountLocks::new(TEST_NUM_THREADS);
519        locks.write_lock_account(&pk2, 3);
520
521        assert_eq!(
522            locks.try_lock_accounts(
523                [&pk1].into_iter(),
524                [&pk2].into_iter(),
525                TEST_ANY_THREADS,
526                test_thread_selector
527            ),
528            Ok(3)
529        );
530    }
531
532    #[test]
533    fn test_try_lock_accounts_one_not_allowed() {
534        let pk1 = Pubkey::new_unique();
535        let pk2 = Pubkey::new_unique();
536        let mut locks = ThreadAwareAccountLocks::new(TEST_NUM_THREADS);
537        locks.write_lock_account(&pk2, 3);
538
539        assert_eq!(
540            locks.try_lock_accounts(
541                [&pk1].into_iter(),
542                [&pk2].into_iter(),
543                ThreadSet::none(),
544                test_thread_selector
545            ),
546            Err(TryLockError::ThreadNotAllowed)
547        );
548    }
549
550    #[test]
551    fn test_try_lock_accounts_multiple() {
552        let pk1 = Pubkey::new_unique();
553        let pk2 = Pubkey::new_unique();
554        let mut locks = ThreadAwareAccountLocks::new(TEST_NUM_THREADS);
555        locks.read_lock_account(&pk2, 0);
556        locks.read_lock_account(&pk2, 0);
557
558        assert_eq!(
559            locks.try_lock_accounts(
560                [&pk1].into_iter(),
561                [&pk2].into_iter(),
562                TEST_ANY_THREADS - ThreadSet::only(0), // exclude 0
563                test_thread_selector
564            ),
565            Ok(1)
566        );
567    }
568
569    #[test]
570    fn test_try_lock_accounts_any() {
571        let pk1 = Pubkey::new_unique();
572        let pk2 = Pubkey::new_unique();
573        let mut locks = ThreadAwareAccountLocks::new(TEST_NUM_THREADS);
574        assert_eq!(
575            locks.try_lock_accounts(
576                [&pk1].into_iter(),
577                [&pk2].into_iter(),
578                TEST_ANY_THREADS,
579                test_thread_selector
580            ),
581            Ok(0)
582        );
583    }
584
585    #[test]
586    fn test_accounts_schedulable_threads_no_outstanding_locks() {
587        let pk1 = Pubkey::new_unique();
588        let locks = ThreadAwareAccountLocks::new(TEST_NUM_THREADS);
589
590        assert_eq!(
591            locks.accounts_schedulable_threads([&pk1].into_iter(), std::iter::empty()),
592            Some(TEST_ANY_THREADS)
593        );
594        assert_eq!(
595            locks.accounts_schedulable_threads(std::iter::empty(), [&pk1].into_iter()),
596            Some(TEST_ANY_THREADS)
597        );
598    }
599
600    #[test]
601    fn test_accounts_schedulable_threads_outstanding_write_only() {
602        let pk1 = Pubkey::new_unique();
603        let pk2 = Pubkey::new_unique();
604        let mut locks = ThreadAwareAccountLocks::new(TEST_NUM_THREADS);
605
606        locks.write_lock_account(&pk1, 2);
607        assert_eq!(
608            locks.accounts_schedulable_threads([&pk1, &pk2].into_iter(), std::iter::empty()),
609            Some(ThreadSet::only(2))
610        );
611        assert_eq!(
612            locks.accounts_schedulable_threads(std::iter::empty(), [&pk1, &pk2].into_iter()),
613            Some(ThreadSet::only(2))
614        );
615    }
616
617    #[test]
618    fn test_accounts_schedulable_threads_outstanding_read_only() {
619        let pk1 = Pubkey::new_unique();
620        let pk2 = Pubkey::new_unique();
621        let mut locks = ThreadAwareAccountLocks::new(TEST_NUM_THREADS);
622
623        locks.read_lock_account(&pk1, 2);
624        assert_eq!(
625            locks.accounts_schedulable_threads([&pk1, &pk2].into_iter(), std::iter::empty()),
626            Some(ThreadSet::only(2))
627        );
628        assert_eq!(
629            locks.accounts_schedulable_threads(std::iter::empty(), [&pk1, &pk2].into_iter()),
630            Some(TEST_ANY_THREADS)
631        );
632
633        locks.read_lock_account(&pk1, 0);
634        assert_eq!(
635            locks.accounts_schedulable_threads([&pk1, &pk2].into_iter(), std::iter::empty()),
636            None
637        );
638        assert_eq!(
639            locks.accounts_schedulable_threads(std::iter::empty(), [&pk1, &pk2].into_iter()),
640            Some(TEST_ANY_THREADS)
641        );
642    }
643
644    #[test]
645    fn test_accounts_schedulable_threads_outstanding_mixed() {
646        let pk1 = Pubkey::new_unique();
647        let pk2 = Pubkey::new_unique();
648        let mut locks = ThreadAwareAccountLocks::new(TEST_NUM_THREADS);
649
650        locks.read_lock_account(&pk1, 2);
651        locks.write_lock_account(&pk1, 2);
652        assert_eq!(
653            locks.accounts_schedulable_threads([&pk1, &pk2].into_iter(), std::iter::empty()),
654            Some(ThreadSet::only(2))
655        );
656        assert_eq!(
657            locks.accounts_schedulable_threads(std::iter::empty(), [&pk1, &pk2].into_iter()),
658            Some(ThreadSet::only(2))
659        );
660    }
661
662    #[test]
663    #[should_panic(expected = "outstanding write lock must be on same thread")]
664    fn test_write_lock_account_write_conflict_panic() {
665        let pk1 = Pubkey::new_unique();
666        let mut locks = ThreadAwareAccountLocks::new(TEST_NUM_THREADS);
667        locks.write_lock_account(&pk1, 0);
668        locks.write_lock_account(&pk1, 1);
669    }
670
671    #[test]
672    #[should_panic(expected = "outstanding read lock must be on same thread")]
673    fn test_write_lock_account_read_conflict_panic() {
674        let pk1 = Pubkey::new_unique();
675        let mut locks = ThreadAwareAccountLocks::new(TEST_NUM_THREADS);
676        locks.read_lock_account(&pk1, 0);
677        locks.write_lock_account(&pk1, 1);
678    }
679
680    #[test]
681    #[should_panic(expected = "write lock must exist")]
682    fn test_write_unlock_account_not_locked() {
683        let pk1 = Pubkey::new_unique();
684        let mut locks = ThreadAwareAccountLocks::new(TEST_NUM_THREADS);
685        locks.write_unlock_account(&pk1, 0);
686    }
687
688    #[test]
689    #[should_panic(expected = "outstanding write lock must be on same thread")]
690    fn test_write_unlock_account_thread_mismatch() {
691        let pk1 = Pubkey::new_unique();
692        let mut locks = ThreadAwareAccountLocks::new(TEST_NUM_THREADS);
693        locks.write_lock_account(&pk1, 1);
694        locks.write_unlock_account(&pk1, 0);
695    }
696
697    #[test]
698    #[should_panic(expected = "outstanding write lock must be on same thread")]
699    fn test_read_lock_account_write_conflict_panic() {
700        let pk1 = Pubkey::new_unique();
701        let mut locks = ThreadAwareAccountLocks::new(TEST_NUM_THREADS);
702        locks.write_lock_account(&pk1, 0);
703        locks.read_lock_account(&pk1, 1);
704    }
705
706    #[test]
707    #[should_panic(expected = "read lock must exist")]
708    fn test_read_unlock_account_not_locked() {
709        let pk1 = Pubkey::new_unique();
710        let mut locks = ThreadAwareAccountLocks::new(TEST_NUM_THREADS);
711        locks.read_unlock_account(&pk1, 1);
712    }
713
714    #[test]
715    #[should_panic(expected = "outstanding read lock must be on same thread")]
716    fn test_read_unlock_account_thread_mismatch() {
717        let pk1 = Pubkey::new_unique();
718        let mut locks = ThreadAwareAccountLocks::new(TEST_NUM_THREADS);
719        locks.read_lock_account(&pk1, 0);
720        locks.read_unlock_account(&pk1, 1);
721    }
722
723    #[test]
724    fn test_write_locking() {
725        let pk1 = Pubkey::new_unique();
726        let mut locks = ThreadAwareAccountLocks::new(TEST_NUM_THREADS);
727        locks.write_lock_account(&pk1, 1);
728        locks.write_lock_account(&pk1, 1);
729        locks.write_unlock_account(&pk1, 1);
730        locks.write_unlock_account(&pk1, 1);
731        assert!(locks.locks.is_empty());
732    }
733
734    #[test]
735    fn test_read_locking() {
736        let pk1 = Pubkey::new_unique();
737        let mut locks = ThreadAwareAccountLocks::new(TEST_NUM_THREADS);
738        locks.read_lock_account(&pk1, 1);
739        locks.read_lock_account(&pk1, 1);
740        locks.read_unlock_account(&pk1, 1);
741        locks.read_unlock_account(&pk1, 1);
742        assert!(locks.locks.is_empty());
743    }
744
745    #[test]
746    #[should_panic(expected = "thread_id must be < num_threads")]
747    fn test_lock_accounts_invalid_thread() {
748        let pk1 = Pubkey::new_unique();
749        let mut locks = ThreadAwareAccountLocks::new(TEST_NUM_THREADS);
750        locks.lock_accounts([&pk1].into_iter(), std::iter::empty(), TEST_NUM_THREADS);
751    }
752
753    #[test]
754    fn test_thread_set() {
755        let mut thread_set = ThreadSet::none();
756        assert!(thread_set.is_empty());
757        assert_eq!(thread_set.num_threads(), 0);
758        assert_eq!(thread_set.only_one_contained(), None);
759        for idx in 0..MAX_THREADS {
760            assert!(!thread_set.contains(idx));
761        }
762
763        thread_set.insert(4);
764        assert!(!thread_set.is_empty());
765        assert_eq!(thread_set.num_threads(), 1);
766        assert_eq!(thread_set.only_one_contained(), Some(4));
767        for idx in 0..MAX_THREADS {
768            assert_eq!(thread_set.contains(idx), idx == 4);
769        }
770
771        thread_set.insert(2);
772        assert!(!thread_set.is_empty());
773        assert_eq!(thread_set.num_threads(), 2);
774        assert_eq!(thread_set.only_one_contained(), None);
775        for idx in 0..MAX_THREADS {
776            assert_eq!(thread_set.contains(idx), idx == 2 || idx == 4);
777        }
778
779        thread_set.remove(4);
780        assert!(!thread_set.is_empty());
781        assert_eq!(thread_set.num_threads(), 1);
782        assert_eq!(thread_set.only_one_contained(), Some(2));
783        for idx in 0..MAX_THREADS {
784            assert_eq!(thread_set.contains(idx), idx == 2);
785        }
786    }
787
788    #[test]
789    fn test_thread_set_any_zero() {
790        let any_threads = ThreadSet::any(0);
791        assert_eq!(any_threads.num_threads(), 0);
792    }
793
794    #[test]
795    fn test_thread_set_any_max() {
796        let any_threads = ThreadSet::any(MAX_THREADS);
797        assert_eq!(any_threads.num_threads(), MAX_THREADS as u32);
798    }
799
800    #[test]
801    fn test_thread_set_iter() {
802        let mut thread_set = ThreadSet::none();
803        assert!(thread_set.contained_threads_iter().next().is_none());
804
805        thread_set.insert(4);
806        assert_eq!(
807            thread_set.contained_threads_iter().collect::<Vec<_>>(),
808            vec![4]
809        );
810
811        thread_set.insert(5);
812        assert_eq!(
813            thread_set.contained_threads_iter().collect::<Vec<_>>(),
814            vec![4, 5]
815        );
816        thread_set.insert(63);
817        assert_eq!(
818            thread_set.contained_threads_iter().collect::<Vec<_>>(),
819            vec![4, 5, 63]
820        );
821
822        thread_set.remove(5);
823        assert_eq!(
824            thread_set.contained_threads_iter().collect::<Vec<_>>(),
825            vec![4, 63]
826        );
827
828        let thread_set = ThreadSet::any(64);
829        assert_eq!(
830            thread_set.contained_threads_iter().collect::<Vec<_>>(),
831            (0..64).collect::<Vec<_>>()
832        );
833    }
834}