#lock-free #cursor #intrusive

atomic_list

Lock-free circular ring of intrusive ref-counted nodes plus shared cursors

17 stable releases

Uses new Rust 2024

2.0.0 Jan 24, 2026
1.9.3 Jan 24, 2026
1.8.2 Dec 28, 2025
1.7.0 Nov 30, 2025
0.1.0 Nov 5, 2025

#240 in Concurrency


Used in ubq

MIT license

81KB
1.5K SLoC

AtomicList

AtomicList is an experimental circular list built directly from intrusive, reference-counted nodes. The crate ships nodes and cursors only—there is no separate list container—so you keep a Node<T> (and optionally a shared cursor::Cursor) as your handle into the ring. A sync::Node<T> acts like a tiny Arc<T> that also stores strong and weak successors, letting CAS loops splice nodes in and out of a ring without locks or external hazard-pointer schemes.

Current surface area

  • Node<T> / WeakNode<T>: Arc/Weak-like handles with embedded successor edges.
  • Lock-free splice helpers: push_before, pop_when, and remove rearrange a ring while maintaining the self-loop invariants on detached nodes.
  • Successor introspection: load_next_* plus resolve_next follow weak breadcrumbs after a node has been popped out.
  • Shared traversal: cursor::Cursor is an atomic cursor that multiple holders advance together.
  • Reusable atomics: atm_p exports generic atomic pointer wrappers used by the list but also usable for Arc/Weak.

Working with nodes

use atomic_list::sync::Node;

let root = Node::new("root");
root.push_before("worker-1", |cur| *cur == "root").unwrap();
root.push_before("worker-2", |_| true).unwrap();

// Remove the first node whose payload ends with "1".
let removed = Node::pop_when(&root, |cur| cur.ends_with('1')).unwrap();
assert_eq!(*removed, "worker-1");

// The ring closes back over the gap.
let worker_2 = Node::load_next_strong(&root);
assert_eq!(worker_2.to_string(), "worker-2");
assert!(Node::ptr_eq(
    &Node::load_next_strong(&worker_2),
    &root
));

// Detached nodes keep a weak breadcrumb into the live ring.
assert_eq!(
    Node::resolve_next(&removed).unwrap().to_string(),
    "root"
);
assert_eq!(Node::into_inner(removed), Some("worker-1"));

Coordinated traversal

use atomic_list::{cursor::Cursor, sync::Node};

let head = Node::new(0);
for i in 1..=3 {
    head.push_before(i, |_| true).unwrap();
}

// Multiple holders share the same atomic cursor position.
let mut cursor_a = Cursor::new(head.clone());
let mut cursor_b = cursor_a.clone();

assert_eq!(*cursor_a.next().unwrap(), 3);
assert_eq!(*cursor_b.next().unwrap(), 3);

// Advancing from either handle moves everyone forward.
assert_eq!(*cursor_a.next().unwrap(), 2);
assert_eq!(*cursor_b.next().unwrap(), 2);

Notes

  • Pure atomics: no hazard pointers or epoch GC, just ref-counted nodes.
  • There is no separate AtomicList struct; hang on to a Node<T> as your entry point into the ring.
  • The API is experimental and may still shift as iteration ergonomics improve.

License

This project is distributed under the terms of the MIT license. See LICENSE for details.

No runtime deps