chore: initial release v0.1.0
Runtime-agnostic oneshot broadcast channel for Rust.
This commit is contained in:
64 files changed
+23966
No files matched your search
@@ -0,0 +1,246 @@
|
||||
//! 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);
|
||||
}
|
||||
Reference in new issue
Block a user