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
13pub type ThreadId = usize; type LockCount = u32;
17
18#[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#[derive(Debug, Default)]
40struct AccountLocks {
41 pub write_locks: Option<AccountWriteLocks>,
42 pub read_locks: Option<AccountReadLocks>,
43}
44
45#[derive(Debug, PartialEq, Eq)]
47pub enum TryLockError {
48 MultipleConflicts,
50 ThreadNotAllowed,
52}
53
54#[derive(Debug)]
59pub struct ThreadAwareAccountLocks {
60 num_threads: usize, locks: AHashMap<Pubkey, AccountLocks>,
65}
66
67impl ThreadAwareAccountLocks {
68 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 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 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 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 fn read_schedulable_threads(&self, account: &Pubkey) -> ThreadSet {
152 self.schedulable_threads::<false>(account)
153 }
154
155 fn write_schedulable_threads(&self, account: &Pubkey) -> ThreadSet {
157 self.schedulable_threads::<true>(account)
158 }
159
160 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 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 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 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 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 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 let thread_id = self.0.trailing_zeros() as ThreadId;
460 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 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), 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}