Skip to content

Commit 21f6ecf

Browse files
committed
Fix flaky tests caused by relaxed load in C++ 'shared_ptr::use_count()' (#450)
Fixes: #284
1 parent d91970d commit 21f6ecf

File tree

2 files changed

+127
-39
lines changed

2 files changed

+127
-39
lines changed

src/support.rs

Lines changed: 96 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
use std::any::type_name;
12
use std::any::Any;
23
use std::borrow::Borrow;
34
use std::borrow::BorrowMut;
45
use std::convert::identity;
56
use std::convert::AsMut;
67
use std::convert::AsRef;
8+
use std::convert::TryFrom;
79
use std::marker::PhantomData;
810
use std::mem::align_of;
911
use std::mem::forget;
@@ -18,6 +20,9 @@ use std::ptr::null_mut;
1820
use std::ptr::NonNull;
1921
use std::rc::Rc;
2022
use std::sync::Arc;
23+
use std::thread::yield_now;
24+
use std::time::Duration;
25+
use std::time::Instant;
2126

2227
// TODO use libc::intptr_t when stable.
2328
// https://doc.rust-lang.org/1.7.0/libc/type.intptr_t.html
@@ -209,12 +214,20 @@ impl<T: Shared> Drop for SharedPtrBase<T> {
209214
pub struct SharedPtr<T: Shared>(SharedPtrBase<T>);
210215

211216
impl<T: Shared> SharedPtr<T> {
212-
pub fn is_null(&self) -> bool {
213-
<T as Shared>::get(&self.0).is_null()
217+
/// Asserts that the number of references to the shared inner value is equal
218+
/// to the `expected` count.
219+
///
220+
/// This function relies on the C++ method `std::shared_ptr::use_count()`,
221+
/// which usually performs a relaxed load. This function will repeatedly call
222+
/// `use_count()` until it returns the expected value, for up to one second.
223+
/// Therefore it should probably not be used in performance critical code.
224+
#[track_caller]
225+
pub fn assert_use_count_eq(&self, expected: usize) {
226+
assert_shared_ptr_use_count_eq("SharedPtr", &self.0, expected);
214227
}
215228

216-
pub fn use_count(&self) -> long {
217-
<T as Shared>::use_count(&self.0)
229+
pub fn is_null(&self) -> bool {
230+
<T as Shared>::get(&self.0).is_null()
218231
}
219232

220233
pub fn take(&mut self) -> Option<SharedRef<T>> {
@@ -267,8 +280,16 @@ impl<T: Shared> From<SharedRef<T>> for SharedPtr<T> {
267280
pub struct SharedRef<T: Shared>(SharedPtrBase<T>);
268281

269282
impl<T: Shared> SharedRef<T> {
270-
pub fn use_count(&self) -> long {
271-
<T as Shared>::use_count(&self.0)
283+
/// Asserts that the number of references to the shared inner value is equal
284+
/// to the `expected` count.
285+
///
286+
/// This function relies on the C++ method `std::shared_ptr::use_count()`,
287+
/// which usually performs a relaxed load. This function will repeatedly call
288+
/// `use_count()` until it returns the expected value, for up to one second.
289+
/// Therefore it should probably not be used in performance critical code.
290+
#[track_caller]
291+
pub fn assert_use_count_eq(&self, expected: usize) {
292+
assert_shared_ptr_use_count_eq("SharedRef", &self.0, expected);
272293
}
273294
}
274295

@@ -303,6 +324,42 @@ impl<T: Shared> Borrow<T> for SharedRef<T> {
303324
}
304325
}
305326

327+
#[track_caller]
328+
fn assert_shared_ptr_use_count_eq<T: Shared>(
329+
wrapper_type_name: &str,
330+
shared_ptr: &SharedPtrBase<T>,
331+
expected: usize,
332+
) {
333+
let mut actual = T::use_count(shared_ptr);
334+
let ok = match long::try_from(expected) {
335+
Err(_) => false, // Non-`long` value can never match actual use count.
336+
Ok(expected) if actual == expected => true, // Fast path.
337+
Ok(expected) => {
338+
pub const RETRY_TIMEOUT: Duration = Duration::from_secs(1);
339+
let start = Instant::now();
340+
loop {
341+
yield_now();
342+
actual = T::use_count(shared_ptr);
343+
if actual == expected {
344+
break true;
345+
} else if start.elapsed() > RETRY_TIMEOUT {
346+
break false;
347+
}
348+
}
349+
}
350+
};
351+
assert!(
352+
ok,
353+
"assertion failed: `{}<{}>` reference count does not match expectation\
354+
\n actual: {}\
355+
\n expected: {}",
356+
wrapper_type_name,
357+
type_name::<T>(),
358+
actual,
359+
expected
360+
);
361+
}
362+
306363
/// A trait for values with static lifetimes that are allocated at a fixed
307364
/// address in memory. Practically speaking, that means they're either a
308365
/// `&'static` reference, or they're heap-allocated in a `Arc`, `Box`, `Rc`,
@@ -672,38 +729,69 @@ mod tests {
672729
forget(take(p));
673730
}
674731

675-
fn use_count(_: &SharedPtrBase<Self>) -> long {
676-
unimplemented!()
732+
fn use_count(p: &SharedPtrBase<Self>) -> long {
733+
match p {
734+
&Self::SHARED_PTR_BASE_A => 1,
735+
&Self::SHARED_PTR_BASE_B => 2,
736+
p if p == &Default::default() => 0,
737+
_ => unreachable!(),
738+
}
677739
}
678740
}
679741

680742
#[test]
681743
fn shared_ptr_and_shared_ref() {
682744
let mut shared_ptr_a1 = SharedPtr(MockSharedObj::SHARED_PTR_BASE_A);
683745
assert!(!shared_ptr_a1.is_null());
746+
shared_ptr_a1.assert_use_count_eq(1);
684747

685748
let shared_ref_a: SharedRef<_> = shared_ptr_a1.take().unwrap();
686749
assert_eq!(shared_ref_a.inner, 11111);
750+
shared_ref_a.assert_use_count_eq(1);
687751

688752
assert!(shared_ptr_a1.is_null());
753+
shared_ptr_a1.assert_use_count_eq(0);
689754

690755
let shared_ptr_a2: SharedPtr<_> = shared_ref_a.into();
691756
assert!(!shared_ptr_a2.is_null());
757+
shared_ptr_a2.assert_use_count_eq(1);
692758
assert_eq!(shared_ptr_a2.unwrap().inner, 11111);
693759

694760
let mut shared_ptr_b1 = SharedPtr(MockSharedObj::SHARED_PTR_BASE_B);
695761
assert!(!shared_ptr_b1.is_null());
762+
shared_ptr_b1.assert_use_count_eq(2);
696763

697764
let shared_ref_b: SharedRef<_> = shared_ptr_b1.take().unwrap();
698765
assert_eq!(shared_ref_b.inner, 22222);
766+
shared_ref_b.assert_use_count_eq(2);
699767

700768
assert!(shared_ptr_b1.is_null());
769+
shared_ptr_b1.assert_use_count_eq(0);
701770

702771
let shared_ptr_b2: SharedPtr<_> = shared_ref_b.into();
703772
assert!(!shared_ptr_b2.is_null());
773+
shared_ptr_b2.assert_use_count_eq(2);
704774
assert_eq!(shared_ptr_b2.unwrap().inner, 22222);
705775
}
706776

777+
#[test]
778+
#[should_panic(expected = "assertion failed: \
779+
`SharedPtr<rusty_v8::support::tests::MockSharedObj>` reference count \
780+
does not match expectation")]
781+
fn shared_ptr_use_count_assertion_failed() {
782+
let shared_ptr: SharedPtr<MockSharedObj> = Default::default();
783+
shared_ptr.assert_use_count_eq(3);
784+
}
785+
786+
#[test]
787+
#[should_panic(expected = "assertion failed: \
788+
`SharedRef<rusty_v8::support::tests::MockSharedObj>` reference count \
789+
does not match expectation")]
790+
fn shared_ref_use_count_assertion_failed() {
791+
let shared_ref = SharedRef(MockSharedObj::SHARED_PTR_BASE_B);
792+
shared_ref.assert_use_count_eq(7);
793+
}
794+
707795
static TEST_OBJ_DROPPED: AtomicBool = AtomicBool::new(false);
708796

709797
struct TestObj {

tests/test_api.rs

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -459,44 +459,44 @@ fn backing_store_segfault() {
459459
let _setup_guard = setup();
460460
let array_buffer_allocator = v8::new_default_allocator().make_shared();
461461
let shared_bs = {
462-
assert_eq!(1, v8::SharedRef::use_count(&array_buffer_allocator));
462+
array_buffer_allocator.assert_use_count_eq(1);
463463
let params = v8::Isolate::create_params()
464464
.array_buffer_allocator(array_buffer_allocator.clone());
465-
assert_eq!(2, v8::SharedRef::use_count(&array_buffer_allocator));
465+
array_buffer_allocator.assert_use_count_eq(2);
466466
let isolate = &mut v8::Isolate::new(params);
467-
assert_eq!(2, v8::SharedRef::use_count(&array_buffer_allocator));
467+
array_buffer_allocator.assert_use_count_eq(2);
468468
let scope = &mut v8::HandleScope::new(isolate);
469469
let context = v8::Context::new(scope);
470470
let scope = &mut v8::ContextScope::new(scope, context);
471471
let ab = v8::ArrayBuffer::new(scope, 10);
472472
let shared_bs = ab.get_backing_store();
473-
assert_eq!(3, v8::SharedRef::use_count(&array_buffer_allocator));
473+
array_buffer_allocator.assert_use_count_eq(3);
474474
shared_bs
475475
};
476-
assert_eq!(1, v8::SharedRef::use_count(&shared_bs));
477-
assert_eq!(2, v8::SharedRef::use_count(&array_buffer_allocator));
476+
shared_bs.assert_use_count_eq(1);
477+
array_buffer_allocator.assert_use_count_eq(2);
478478
drop(array_buffer_allocator);
479479
drop(shared_bs); // Error occurred here.
480480
}
481481

482482
#[test]
483483
fn shared_array_buffer_allocator() {
484484
let alloc1 = v8::new_default_allocator().make_shared();
485-
assert_eq!(1, v8::SharedRef::use_count(&alloc1));
485+
alloc1.assert_use_count_eq(1);
486486

487487
let alloc2 = alloc1.clone();
488-
assert_eq!(2, v8::SharedRef::use_count(&alloc1));
489-
assert_eq!(2, v8::SharedRef::use_count(&alloc2));
488+
alloc1.assert_use_count_eq(2);
489+
alloc2.assert_use_count_eq(2);
490490

491491
let mut alloc2 = v8::SharedPtr::from(alloc2);
492-
assert_eq!(2, v8::SharedRef::use_count(&alloc1));
493-
assert_eq!(2, v8::SharedPtr::use_count(&alloc2));
492+
alloc1.assert_use_count_eq(2);
493+
alloc2.assert_use_count_eq(2);
494494

495495
drop(alloc1);
496-
assert_eq!(1, v8::SharedPtr::use_count(&alloc2));
496+
alloc2.assert_use_count_eq(1);
497497

498498
alloc2.take();
499-
assert_eq!(0, v8::SharedPtr::use_count(&alloc2));
499+
alloc2.assert_use_count_eq(0);
500500
}
501501

502502
#[test]
@@ -514,46 +514,46 @@ fn array_buffer_with_shared_backing_store() {
514514

515515
let bs1 = ab1.get_backing_store();
516516
assert_eq!(ab1.byte_length(), bs1.byte_length());
517-
assert_eq!(2, v8::SharedRef::use_count(&bs1));
517+
bs1.assert_use_count_eq(2);
518518

519519
let bs2 = ab1.get_backing_store();
520520
assert_eq!(ab1.byte_length(), bs2.byte_length());
521-
assert_eq!(3, v8::SharedRef::use_count(&bs1));
522-
assert_eq!(3, v8::SharedRef::use_count(&bs2));
521+
bs1.assert_use_count_eq(3);
522+
bs2.assert_use_count_eq(3);
523523

524524
let bs3 = ab1.get_backing_store();
525525
assert_eq!(ab1.byte_length(), bs3.byte_length());
526-
assert_eq!(4, v8::SharedRef::use_count(&bs1));
527-
assert_eq!(4, v8::SharedRef::use_count(&bs2));
528-
assert_eq!(4, v8::SharedRef::use_count(&bs3));
526+
bs1.assert_use_count_eq(4);
527+
bs2.assert_use_count_eq(4);
528+
bs3.assert_use_count_eq(4);
529529

530530
drop(bs2);
531-
assert_eq!(3, v8::SharedRef::use_count(&bs1));
532-
assert_eq!(3, v8::SharedRef::use_count(&bs3));
531+
bs1.assert_use_count_eq(3);
532+
bs3.assert_use_count_eq(3);
533533

534534
drop(bs1);
535-
assert_eq!(2, v8::SharedRef::use_count(&bs3));
535+
bs3.assert_use_count_eq(2);
536536

537537
let ab2 = v8::ArrayBuffer::with_backing_store(scope, &bs3);
538538
assert_eq!(ab1.byte_length(), ab2.byte_length());
539-
assert_eq!(3, v8::SharedRef::use_count(&bs3));
539+
bs3.assert_use_count_eq(3);
540540

541541
let bs4 = ab2.get_backing_store();
542542
assert_eq!(ab1.byte_length(), bs4.byte_length());
543-
assert_eq!(4, v8::SharedRef::use_count(&bs3));
544-
assert_eq!(4, v8::SharedRef::use_count(&bs4));
543+
bs3.assert_use_count_eq(4);
544+
bs4.assert_use_count_eq(4);
545545

546546
let bs5 = bs4.clone();
547-
assert_eq!(5, v8::SharedRef::use_count(&bs3));
548-
assert_eq!(5, v8::SharedRef::use_count(&bs4));
549-
assert_eq!(5, v8::SharedRef::use_count(&bs5));
547+
bs3.assert_use_count_eq(5);
548+
bs4.assert_use_count_eq(5);
549+
bs5.assert_use_count_eq(5);
550550

551551
drop(bs3);
552-
assert_eq!(4, v8::SharedRef::use_count(&bs4));
553-
assert_eq!(4, v8::SharedRef::use_count(&bs4));
552+
bs4.assert_use_count_eq(4);
553+
bs5.assert_use_count_eq(4);
554554

555555
drop(bs4);
556-
assert_eq!(3, v8::SharedRef::use_count(&bs5));
556+
bs5.assert_use_count_eq(3);
557557
}
558558
}
559559

0 commit comments

Comments
 (0)