Files
announcement/tests/arc_refcount_cycles_test.rs
T
swaits e8e55b7676 chore: initial release v0.1.0
Runtime-agnostic oneshot broadcast channel for Rust.
2025-10-13 19:36:37 -06:00

247 lines
7.3 KiB
Rust

//! Arc refcount stress test - rapid announce/drop cycles
//! Tests for memory leaks in rapid channel creation/destruction with Arc values.
use announcement::Announcement;
use std::sync::Arc;
use std::thread;
/// Test: Arc refcount: cycle detection
///
/// Verifies: Arc reference cycles are handled correctly
/// Category: refcount
// Test 1: Rapid Arc refcount cycles (single-threaded)
#[test]
fn test_arc_refcount_rapid_cycles() {
let data = Arc::new(vec![0u8; 1024]); // 1KB test data
let initial_count = Arc::strong_count(&data);
// Run 10,000 create/announce/drop cycles
for i in 0..10_000 {
{
let (announcer, announcement) = Announcement::new();
// Create a few listeners
let l1 = announcement.listener();
let l2 = announcement.listener();
let l3 = announcement.listener();
// Announce the Arc
announcer.announce(data.clone()).unwrap();
// Retrieve values
let _v1 = l1.try_listen().unwrap();
let _v2 = l2.try_listen().unwrap();
let _v3 = l3.try_listen().unwrap();
// Everything drops here when block ends
}
// Periodic check after everything has dropped
if i % 1000 == 0 {
let current_count = Arc::strong_count(&data);
assert_eq!(
current_count, initial_count,
"Refcount leak detected at iteration {}: expected {}, got {}",
i, initial_count, current_count
);
}
}
// Final verification
assert_eq!(
Arc::strong_count(&data),
initial_count,
"Final refcount mismatch after 10k cycles"
);
}
/// Test: Refcount test: arc refcount mixed drop patterns
///
/// Verifies: Verifies arc refcount mixed drop patterns
/// Category: refcount
// Test 2: Rapid Arc cycles with mixed drop patterns
#[test]
fn test_arc_refcount_mixed_drop_patterns() {
let data = Arc::new(vec![42u8; 512]);
let initial_count = Arc::strong_count(&data);
for i in 0..5_000 {
{
let (announcer, announcement) = Announcement::new();
// Create various numbers of listeners
let listener_count = (i % 10) + 1;
let mut listeners = Vec::new();
for _ in 0..listener_count {
listeners.push(announcement.listener());
}
// Announce
announcer.announce(data.clone()).unwrap();
// Drop listeners in different patterns
match i % 4 {
0 => drop(listeners), // Drop all at once
1 => {
// Drop one at a time
for l in listeners {
drop(l);
}
}
2 => {
// Retrieve then drop
let values: Vec<_> =
listeners.iter().map(|l| l.try_listen().unwrap()).collect();
drop(values);
drop(listeners);
}
_ => {
// Drop half, retrieve from rest
let mid = listeners.len() / 2;
drop(listeners.drain(0..mid).collect::<Vec<_>>());
for l in &listeners {
let _ = l.try_listen();
}
}
}
// Block ends - everything drops including announcement
}
// Verify refcount periodically after everything dropped
if i % 500 == 0 {
assert_eq!(Arc::strong_count(&data), initial_count);
}
}
assert_eq!(Arc::strong_count(&data), initial_count);
}
/// Test: Arc refcount: cycle detection
///
/// Verifies: Arc reference cycles are handled correctly
/// Category: refcount
// Test 3: Concurrent Arc refcount stress
#[test]
fn test_arc_refcount_concurrent_cycles() {
let data = Arc::new(vec![0u8; 2048]);
let initial_count = Arc::strong_count(&data);
let mut handles = Vec::new();
// 8 threads all doing rapid cycles
for thread_id in 0..8 {
let data_clone = data.clone();
handles.push(thread::spawn(move || {
for i in 0..1_000 {
let (announcer, announcement) = Announcement::new();
let mut listeners = Vec::new();
for _ in 0..5 {
listeners.push(announcement.listener());
}
announcer.announce(data_clone.clone()).unwrap();
// Mix of operations
match i % 3 {
0 => {
// Just drop everything
drop(listeners);
}
1 => {
// Retrieve and verify
for l in &listeners {
let v = l.try_listen().unwrap();
assert_eq!(v.len(), 2048);
}
}
_ => {
// Mixed
let _ = listeners[0].try_listen();
drop(listeners);
}
}
if i % 100 == 0 && thread_id == 0 {
println!("Thread {} completed {} iterations", thread_id, i);
}
}
}));
}
// Wait for all threads
for handle in handles {
handle.join().unwrap();
}
// Final refcount should be back to initial
assert_eq!(
Arc::strong_count(&data),
initial_count,
"Refcount leak after concurrent stress test"
);
}
/// Test: Refcount test: arc refcount with announcer drop
///
/// Verifies: Verifies arc refcount with announcer drop
/// Category: refcount
// Test 4: Arc refcount with announcer drop (no announce)
#[test]
fn test_arc_refcount_with_announcer_drop() {
let data = Arc::new(vec![1u8; 256]);
let initial_count = Arc::strong_count(&data);
for _ in 0..10_000 {
let (announcer, announcement) = Announcement::<Arc<Vec<u8>>>::new();
let _l1 = announcement.listener();
let _l2 = announcement.listener();
// Drop announcer WITHOUT announcing
drop(announcer);
// Listeners should see closed, no Arc refcount change for data
// since it was never announced
}
// Refcount should be unchanged
assert_eq!(Arc::strong_count(&data), initial_count);
}
/// Test: Refcount test: arc refcount with listener cloning
///
/// Verifies: Verifies arc refcount with listener cloning
/// Category: refcount
// Test 5: Arc refcount with listener cloning stress
#[test]
fn test_arc_refcount_with_listener_cloning() {
let data = Arc::new(vec![7u8; 512]);
let initial_count = Arc::strong_count(&data);
for _ in 0..5_000 {
let (announcer, announcement) = Announcement::new();
let l1 = announcement.listener();
// Clone listener many times
let mut clones = vec![l1.clone()];
for _ in 0..10 {
clones.push(clones[0].clone());
}
announcer.announce(data.clone()).unwrap();
// All clones retrieve
for l in &clones {
let _v = l.try_listen().unwrap();
}
// Everything drops
}
assert_eq!(Arc::strong_count(&data), initial_count);
}