Skip to content

Commit 240ec42

Browse files
committed
irc: Add test and doc
1 parent ca29c90 commit 240ec42

2 files changed

Lines changed: 292 additions & 2 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ fastrand = "2.3"
3737
criterion2 = { version="3.0.2"}
3838
captains-log = {version="0", features = ["ringfile"] }
3939
log = { version="0"}
40+
crossfire = "3.1"
4041

4142
[[bench]]
4243
name = "avl"

src/irc.rs

Lines changed: 291 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,60 @@
1+
//! Intrusive Reference Counter (Irc)
2+
//!
3+
//! `Irc` is an intrusive reference counting smart pointer, similar to `Arc` but without weak reference support.
4+
//! It requires the inner type to implement [IrcItem] trait to provide a counter field.
5+
//!
6+
//! The underlayer of `Irc` is Box, unlike `Arc` which wrap a hidden ArcInner on your inner types,
7+
//! Irc use the same memory location of your inner types.
8+
//!
9+
//! [IrcItem::on_drop] in the trait allow you to have the ownship of underlying inner memory after the reference count of Irc is dropped.
10+
//!
11+
//! # Example
12+
//!
13+
//! ```rust
14+
//! use embed_collections::irc::{Irc, IrcItem};
15+
//! use core::sync::atomic::{AtomicUsize, AtomicBool, Ordering};
16+
//! use crossfire::oneshot;
17+
//! use std::thread;
18+
//! use std::time::Duration;
19+
//!
20+
//! // Usually we use Irc for some large structure, but we show a simple demo here.
21+
//! struct MyItem {
22+
//! is_done: AtomicBool,
23+
//! counter: AtomicUsize,
24+
//! done_tx: Option<oneshot::TxOneshot<Box<MyItem>>>,
25+
//! }
26+
//!
27+
//! unsafe impl IrcItem<()> for MyItem {
28+
//! type Counter = AtomicUsize;
29+
//! fn counter(&self) -> &Self::Counter {
30+
//! &self.counter
31+
//! }
32+
//!
33+
//! // overwrite default behavior to send the item through channel
34+
//! fn on_drop(mut this: Box<Self>) {
35+
//! let done_tx = this.done_tx.take().unwrap();
36+
//! done_tx.send(this);
37+
//! }
38+
//! }
39+
//!
40+
//! let (done_tx, done_rx) = oneshot::oneshot();
41+
//! let boxed_item = Box::new(MyItem {
42+
//! is_done: AtomicBool::new(false),
43+
//! counter: AtomicUsize::new(0),
44+
//! done_tx: Some(done_tx),
45+
//! });
46+
//!
47+
//! // Convert from Box to Irc, which does not have additional allocation.
48+
//! let item = Irc::<_, ()>::from(boxed_item);
49+
//! thread::spawn(move || {
50+
//! thread::sleep(Duration::from_secs(1));
51+
//! item.is_done.store(true, Ordering::SeqCst);
52+
//! drop(item);
53+
//! });
54+
//! let item: Box<MyItem> = done_rx.recv().unwrap();
55+
//! assert!(item.is_done.load(Ordering::SeqCst));
56+
//! ```
57+
158
use crate::{Pointer, SmartPointer};
259
use alloc::boxed::Box;
360
use atomic_traits::{
@@ -32,7 +89,9 @@ where
3289

3390
/// The default behavior for Irc is dropping the boxed inner.
3491
///
35-
/// You can overwrite this if you want to send the inner somewhere
92+
/// You can overwrite this if you want to send the inner somewhere.
93+
/// We pass box here to reduce moving cost.
94+
#[allow(clippy::boxed_local)]
3695
#[inline(always)]
3796
fn on_drop(_this: Box<Self>) {}
3897

@@ -44,7 +103,7 @@ where
44103

45104
/// Intrusive reference counter, which support conversion bwteween `Box<T>`.
46105
///
47-
/// It does not support weak reference
106+
/// It does not support weak reference.
48107
pub struct Irc<T: IrcItem<Tag>, Tag> {
49108
inner: NonNull<T>,
50109
_phan: PhantomData<fn(&Tag)>,
@@ -79,6 +138,37 @@ impl<T: IrcItem<Tag>, Tag> Irc<T, Tag> {
79138
///
80139
/// it's possible to return false when counter drop to 1,
81140
/// Because of using Acquire load and Release on drop.
141+
///
142+
/// # Example
143+
///
144+
///
145+
/// ```rust
146+
/// use embed_collections::irc::{Irc, IrcItem};
147+
/// use core::sync::atomic::AtomicUsize;
148+
///
149+
/// struct Tag;
150+
///
151+
/// struct MyItem {
152+
/// value: i32,
153+
/// counter: AtomicUsize,
154+
/// }
155+
///
156+
/// unsafe impl IrcItem<Tag> for MyItem {
157+
/// type Counter = AtomicUsize;
158+
/// fn counter(&self) -> &Self::Counter {
159+
/// &self.counter
160+
/// }
161+
/// }
162+
///
163+
/// // Create a new Irc
164+
/// let irc1 = Irc::<_, Tag>::new(MyItem { value: 10, counter: AtomicUsize::new(0) });
165+
/// assert_eq!(irc1.value, 10);
166+
/// assert!(irc1.is_unique());
167+
///
168+
/// // Clone the Irc
169+
/// let irc2 = irc1.clone();
170+
/// assert_eq!(irc1.strong_count(), 2);
171+
/// assert!(!irc1.is_unique());
82172
#[inline]
83173
pub fn is_unique(&self) -> bool {
84174
// Safety:
@@ -100,6 +190,42 @@ impl<T: IrcItem<Tag>, Tag> Irc<T, Tag> {
100190

101191
impl<T: IrcItem<Tag> + Clone, Tag> Irc<T, Tag> {
102192
/// The Cow function, the same as `Arc::make_mut()`
193+
///
194+
/// # Example
195+
///
196+
/// ```rust
197+
/// use embed_collections::irc::{Irc, IrcItem};
198+
/// use core::sync::atomic::AtomicUsize;
199+
///
200+
/// struct Tag;
201+
/// struct MyItem {
202+
/// value: i32,
203+
/// counter: AtomicUsize,
204+
/// }
205+
///
206+
/// impl Clone for MyItem {
207+
/// fn clone(&self) -> Self {
208+
/// Self { value: self.value, counter: AtomicUsize::new(0) }
209+
/// }
210+
/// }
211+
///
212+
/// unsafe impl IrcItem<Tag> for MyItem {
213+
/// type Counter = AtomicUsize;
214+
/// fn counter(&self) -> &Self::Counter {
215+
/// &self.counter
216+
/// }
217+
/// }
218+
///
219+
/// let mut irc1 = Irc::<_, Tag>::new(MyItem { value: 10, counter: AtomicUsize::new(0) });
220+
/// let irc2 = irc1.clone();
221+
///
222+
/// // This will clone the inner item because it's shared
223+
/// let m = Irc::make_mut(&mut irc1);
224+
/// m.value = 20;
225+
///
226+
/// assert_eq!(irc1.value, 20);
227+
/// assert_eq!(irc2.value, 10);
228+
/// ```
103229
#[inline]
104230
pub fn make_mut(this: &mut Self) -> &mut T {
105231
if !this.is_unique() {
@@ -212,3 +338,166 @@ impl<T: IrcItem<Tag>, Tag> SmartPointer for Irc<T, Tag> {
212338
Irc::new(inner)
213339
}
214340
}
341+
342+
#[cfg(test)]
343+
mod tests {
344+
use super::*;
345+
use crate::test::{CounterI32, alive_count, reset_alive_count};
346+
use core::sync::atomic::AtomicUsize;
347+
use std::thread;
348+
349+
struct Tag;
350+
351+
struct TestItem {
352+
value: CounterI32,
353+
counter: AtomicUsize,
354+
}
355+
356+
impl TestItem {
357+
fn new(val: i32) -> Self {
358+
Self { value: CounterI32::new(val), counter: AtomicUsize::new(0) }
359+
}
360+
}
361+
362+
impl Clone for TestItem {
363+
fn clone(&self) -> Self {
364+
Self { value: self.value.clone(), counter: AtomicUsize::new(0) }
365+
}
366+
}
367+
368+
unsafe impl IrcItem<Tag> for TestItem {
369+
type Counter = AtomicUsize;
370+
fn counter(&self) -> &Self::Counter {
371+
&self.counter
372+
}
373+
}
374+
375+
#[test]
376+
fn test_basic() {
377+
reset_alive_count();
378+
{
379+
let item = TestItem::new(10);
380+
let irc1 = Irc::<_, Tag>::new(item);
381+
assert_eq!(irc1.value.value, 10);
382+
assert_eq!(irc1.strong_count(), 1);
383+
assert!(irc1.is_unique());
384+
assert_eq!(alive_count(), 1);
385+
386+
let irc2 = irc1.clone();
387+
assert_eq!(irc1.strong_count(), 2);
388+
assert_eq!(irc2.strong_count(), 2);
389+
assert!(!irc1.is_unique());
390+
assert_eq!(alive_count(), 1);
391+
392+
drop(irc1);
393+
assert_eq!(irc2.strong_count(), 1);
394+
assert!(irc2.is_unique());
395+
assert_eq!(alive_count(), 1);
396+
}
397+
assert_eq!(alive_count(), 0);
398+
}
399+
400+
#[test]
401+
fn test_get_mut() {
402+
reset_alive_count();
403+
let mut irc = Irc::<_, Tag>::new(TestItem::new(10));
404+
assert!(Irc::get_mut(&mut irc).is_some());
405+
406+
let _irc2 = irc.clone();
407+
assert!(Irc::get_mut(&mut irc).is_none());
408+
}
409+
410+
#[test]
411+
fn test_make_mut() {
412+
reset_alive_count();
413+
let mut irc = Irc::<_, Tag>::new(TestItem::new(10));
414+
415+
// Unique, no clone
416+
{
417+
let m = Irc::make_mut(&mut irc);
418+
m.value.value = 20;
419+
}
420+
assert_eq!(irc.value.value, 20);
421+
assert_eq!(alive_count(), 1);
422+
423+
// Not unique, should clone
424+
let irc2 = irc.clone();
425+
assert_eq!(alive_count(), 1);
426+
{
427+
let m = Irc::make_mut(&mut irc);
428+
m.value.value = 30;
429+
}
430+
assert_eq!(irc.value.value, 30);
431+
assert_eq!(irc2.value.value, 20);
432+
assert_eq!(alive_count(), 2);
433+
434+
assert!(irc.is_unique());
435+
assert!(irc2.is_unique());
436+
}
437+
438+
#[test]
439+
fn test_multithread_count() {
440+
reset_alive_count();
441+
{
442+
let irc = Irc::<_, Tag>::new(TestItem::new(0));
443+
let mut handles = vec![];
444+
445+
for _ in 0..10 {
446+
let irc_clone = irc.clone();
447+
handles.push(thread::spawn(move || {
448+
for _ in 0..1000 {
449+
let temp = irc_clone.clone();
450+
assert_eq!(temp.value.value, 0);
451+
}
452+
}));
453+
}
454+
455+
for handle in handles {
456+
handle.join().unwrap();
457+
}
458+
459+
assert_eq!(irc.strong_count(), 1);
460+
assert!(irc.is_unique());
461+
assert_eq!(alive_count(), 1);
462+
}
463+
assert_eq!(alive_count(), 0);
464+
}
465+
466+
#[test]
467+
fn test_multithread_drop() {
468+
reset_alive_count();
469+
{
470+
let irc = Irc::<_, Tag>::new(TestItem::new(0));
471+
let mut handles = vec![];
472+
for _ in 0..10 {
473+
let irc_clone = irc.clone();
474+
handles.push(thread::spawn(move || {
475+
for _ in 0..1000 {
476+
let temp = irc_clone.clone();
477+
assert_eq!(temp.value.value, 0);
478+
}
479+
}));
480+
}
481+
drop(irc);
482+
for handle in handles {
483+
handle.join().unwrap();
484+
}
485+
}
486+
assert_eq!(alive_count(), 0);
487+
}
488+
489+
#[test]
490+
fn test_drop_all() {
491+
reset_alive_count();
492+
let irc = Irc::<_, Tag>::new(TestItem::new(0));
493+
let mut clones = vec![];
494+
for _ in 0..100 {
495+
clones.push(irc.clone());
496+
}
497+
assert_eq!(alive_count(), 1);
498+
drop(clones);
499+
assert_eq!(alive_count(), 1);
500+
drop(irc);
501+
assert_eq!(alive_count(), 0);
502+
}
503+
}

0 commit comments

Comments
 (0)