Repository: vorner/arc-swap (language: rust)

Issue:
Swap/store operation is not lock-free
The [definition from Wikipedia](https://en.wikipedia.org/wiki/Non-blocking_algorithm#Lock-freedom) for lock-freedom is this:

> In particular, if one thread is suspended, then a lock-free algorithm guarantees that the remaining threads can still make progress. Hence, if two threads can contend for the same mutex lock or spinlock, then the algorithm is not lock-free. (If we suspend one thread that holds the lock, then the second thread will block.)

In the current implementation, if any reader is suspended whilst holding the lock, all writers will be starved, so only the `load` operation is lock-free.


Discussion / hints:
Yes, right… also probably very related to this from reddit:

```
...provided no threads receive the signal in the middle of a load, right? You'd have to pthread_sigmask the signal out prior to calling load (and can unmask it afterward).
```

(hinting that putting store inside a signal handler is probably not good). I guess I'll update the documentation first and then try to come up with some better solution.
In general, you need space proportional to the number of accessors for truly lock/wait free algorithms. That means either doing memory allocation, or restricting the number of accessors to a predetermined limit.

In the past I've implemented concurrent data structures by building-in `Arc`-like handles which either use a `RwLock` and resize their interior when cloned (meaning that cloning a reference to your lock-free data structure is not lock free, but all operations on a pre-existing handle *can* be made lock-free) or simply panic/fail to clone if a predetermined limit is reached. This obviously has a performance impact though.

Another option would be to use a fixed limit, but protect it with a semaphore, and maybe have a separate slower `Handle` type which is not clone-able but has guaranteed lock-free access (by always holding the semaphore).

The semaphore solution is quite neat because it allows you a high degree of control over the amount of contention allowed on the data structure, so there's a scale from being completely lock free to completely contention free (with a binary semaphore).
For now I've updated the docs and the blog post, so they are not misleading.

I'll try to think about how to make swap lock-free too, but maybe I'll just decide I want load as fast as possible (and lock-free), but swap doesn't matter that much. I aim at read-heavy scenarios, mostly, where updates are rather rare.
I've done some experiments with other approaches and they turn out slower in practice. As there's another crate in making (https://github.com/stjepang/atomic) which'll offer structure with similar interface, but using hazard pointers and will have both load and store lock free, I've decided to leave it the way it is, so users have the possibility to choose between the approaches.

I'll keep it open, though, in case someone comes up with an idea how to improve the situation without losing wait-freeness of load or its performance.
Would you consider adding the lock free version an another stuct in your crate?
That depends on circumstances and what exactly you ask about.

If you have a pull request (or plan on writing one) and there would be some significant code reuse between these two structures to warrant living in the same crate, then possibly.

If you ask me to write another implementation in addition to what I've already written (which would be most probably the hazard pointers), then no. I don't have the time and motivation to do so.

However, if you need a data structure that is lock-free on both load and store/balanced in performance, I'd suggest you volunteer to help finish the atomic crate linked above ‒ I believe it is still not released simply due to lack of time, but it works ‒ it probably needs some more polishing touches.

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a8c1dc8..1c99b2d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+# 1.2.0
+
+* Miri and 32 bit tests in CI.
+* Making the writers lock-free. Soft-removing the IndependentStrategy, as it is
+  no longer needed (hidden and the same as the DafultStrategy).
+
 # 1.1.0
 
 * Fix soundness bug around access::Map. Technically a breaking change, but
diff --git a/Cargo.lock b/Cargo.lock
index 84144f8..a985352 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8,7 +8,7 @@ checksum = "0ccb55436ce9ad9dbd99e726649f8ae4ae3644f5011b822ed805358da37e7e00"
 
 [[package]]
 name = "arc-swap"
-version = "1.1.0"
+version = "1.2.0"
 dependencies = [
  "adaptive-barrier",
  "criterion",
diff --git a/Cargo.toml b/Cargo.toml
index 7812687..f19c86c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "arc-swap"
-version = "1.1.0"
+version = "1.2.0"
 authors = ["Michal 'vorner' Vaner <vorner@vorner.cz>"]
 description = "Atomically swappable Arc"
 documentation = "https://docs.rs/arc-swap"
@@ -17,12 +17,12 @@ appveyor = { repository = "vorner/arc-swap" }
 maintenance = { status = "actively-developed" }
 
 [features]
-# Enable some more, experimental strategies.
-# **These are not part of the stability guarantees** and may be changed, renamed
-# or removed at any time.
-experimental-strategies = []
 # ArcSwapWeak (for std::sycn::Weak) support
 weak = []
+# Some strategies used for testing few internal cornercases. *DO NOT USE* (no stability guarantees and their performance is likely very bad).
+internal-test-strategies = []
+# Possibly some strategies we are experimenting with. Currently empty. No stability guarantees are included about them.
+experimental-strategies = []
 
 [dependencies]
 
diff --git a/benches/background.rs b/benches/background.rs
index 7ad78d1..be1c2e1 100644
--- a/benches/background.rs
+++ b/benches/background.rs
@@ -6,7 +6,9 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion};
 use crossbeam_utils::thread;
 use once_cell::sync::Lazy;
 
-const ITERS: usize = 100;
+// Mostly a leftover from earlier times, but it still allows one to tweak the number of ops per one
+// iteration of the benchmark easily, so it's left in here.
+const ITERS: usize = 1;
 
 macro_rules! method {
     ($c: expr, $name:ident) => {{
@@ -119,11 +121,6 @@ macro_rules! strategy {
 }
 
 strategy!(arc_swap_b, ArcSwap::<usize>);
-#[cfg(feature = "experimental-strategies")]
-strategy!(
-    arc_swap_helping,
-    arc_swap::ArcSwapAny::<Arc<usize>, arc_swap::strategy::experimental::Helping>
-);
 
 mod arc_swap_option {
     use super::{black_box, ArcSwapOption, Criterion, Lazy};
@@ -325,19 +322,6 @@ mod parking_rwlock {
     }
 }
 
-#[cfg(feature = "experimental-strategies")]
-criterion_group!(
-    benches,
-    arc_swap_b::run_all,
-    arc_swap_helping::run_all,
-    arc_swap_option::run_all,
-    arc_swap_cached::run_all,
-    mutex::run_all,
-    parking_mutex::run_all,
-    rwlock::run_all,
-    parking_rwlock::run_all,
-);
-#[cfg(not(feature = "experimental-strategies"))]
 criterion_group!(
     benches,
     arc_swap_b::run_all,
diff --git a/src/debt.rs b/src/debt.rs
deleted file mode 100644
index 264571c..0000000
--- a/src/debt.rs
+++ /dev/null
@@ -1,263 +0,0 @@
-use std::cell::Cell;
-use std::ptr;
-use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
-
-use super::RefCnt;
-
-const DEBT_SLOT_CNT: usize = 8;
-
-/// One debt slot.
-pub(crate) struct Debt(AtomicUsize);
-
-impl Default for Debt {
-    fn default() -> Self {
-        Debt(AtomicUsize::new(NO_DEBT))
-    }
-}
-
-#[repr(align(64))]
-#[derive(Default)]
-struct Slots([Debt; DEBT_SLOT_CNT]);
-
-/// One thread-local node for debts.
-#[repr(C)]
-struct Node {
-    slots: Slots,
-    next: Option<&'static Node>,
-    in_use: AtomicBool,
-}
-
-impl Default for Node {
-    fn default() -> Self {
-        Node {
-            next: None,
-            in_use: AtomicBool::new(true),
-            slots: Default::default(),
-        }
-    }
-}
-
-impl Node {
-    fn get() -> &'static Self {
-        // Try to find an unused one in the chain and reuse it.
-        traverse(|node| {
-            // Try to claim this node. Nothing is synchronized through this atomic, we only
-            // track if someone claims ownership of it.
-            let claimed = node
-                .in_use
-                .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
-                .is_ok();
-            if claimed {
-                Some(node)
-            } else {
-                None
-            }
-        })
-        // If that didn't work, create a new one and prepend to the list.
-        .unwrap_or_else(|| {
-            let node = Box::leak(Box::new(Node::default()));
-            // Not shared between threads yet, so ordinary write would be fine too.
-            node.in_use.store(true, Ordering::Relaxed);
-            // We don't want to read any data in addition to the head, Relaxed is fine
-            // here.
-            //
-            // We do need to release the data to others, but for that, we acquire in the
-            // compare_exchange below.
-            let mut head = DEBT_HEAD.load(Ordering::Relaxed);
-            loop {
-                node.next = unsafe { head.as_ref() };
-                if let Err(old) = DEBT_HEAD.compare_exchange_weak(
-                    head,
-                    node,
-                    // We need to release *the whole chain* here. For that, we need to
-                    // acquire it first.
-                    Ordering::AcqRel,
-                    Ordering::Relaxed, // Nothing changed, go next round of the loop.
-                ) {
-                    head = old;
-                } else {
-                    return node;
-                }
-            }
-        })
-    }
-}
-
-/// The value of pointer `1` should be pretty safe, for two reasons:
-///
-/// * It's an odd number, but the pointers we have are likely aligned at least to the word size,
-///   because the data at the end of the `Arc` has the counters.
-/// * It's in the very first page where NULL lives, so it's not mapped.
-pub(crate) const NO_DEBT: usize = 1;
-
-/// The head of the debt chain.
-static DEBT_HEAD: AtomicPtr<Node> = AtomicPtr::new(ptr::null_mut());
-
-/// A wrapper around a node pointer, to un-claim the node on thread shutdown.
-struct DebtHead {
-    // Node for this thread.
-    node: Cell<Option<&'static Node>>,
-    // The next slot in round-robin rotation. Heuristically tries to balance the load across them
-    // instead of having all of them stuffed towards the start of the array which gets
-    // unsuccessfully iterated through every time.
-    offset: Cell<usize>,
-}
-
-impl Drop for DebtHead {
-    fn drop(&mut self) {
-        if let Some(node) = self.node.get() {
-            // Nothing synchronized by this atomic.
-            assert!(node.in_use.swap(false, Ordering::Relaxed));
-        }
-    }
-}
-
-thread_local! {
-    /// A debt node assigned to this thread.
-    static THREAD_HEAD: DebtHead = DebtHead {
-        node: Cell::new(None),
-        offset: Cell::new(0),
-    };
-}
-
-/// Goes through the debt linked list.
-///
-/// This traverses the linked list, calling the closure on each node. If the closure returns
-/// `Some`, it terminates with that value early, otherwise it runs to the end.
-fn traverse<R, F: FnMut(&'static Node) -> Option<R>>(mut f: F) -> Option<R> {
-    // Acquire ‒ we want to make sure we read the correct version of data at the end of the
-    // pointer. Any write to the DEBT_HEAD is with Release.
-    //
-    // Note that the other pointers in the chain never change and are *ordinary* pointers. The
-    // whole linked list is synchronized through the head.
-    let mut current = unsafe { DEBT_HEAD.load(Ordering::Acquire).as_ref() };
-    while let Some(node) = current {
-        let result = f(node);
-        if result.is_some() {
-            return result;
-        }
-        current = node.next;
-    }
-    None
-}
-
-impl Debt {
-    /// Creates a new debt.
-    ///
-    /// This stores the debt of the given pointer (untyped, casted into an usize) and returns a
-    /// reference to that slot, or gives up with `None` if all the slots are currently full.
-    ///
-    /// This is technically lock-free on the first call in a given thread and wait-free on all the
-    /// other accesses.
-    #[allow(clippy::new_ret_no_self)]
-    #[inline]
-    pub(crate) fn new(ptr: usize) -> Option<&'static Self> {
-        THREAD_HEAD
-            .try_with(|head| {
-                let node = match head.node.get() {
-                    // Already have my own node (most likely)?
-                    Some(node) => node,
-                    // No node yet, called for the first time in this thread. Set one up.
-                    None => {
-                        let new_node = Node::get();
-                        head.node.set(Some(new_node));
-                        new_node
-                    }
-                };
-                // Check it is in use by *us*
-                debug_assert!(node.in_use.load(Ordering::Relaxed));
-                // Trick with offsets: we rotate through the slots (save the value from last time)
-                // so successive leases are likely to succeed on the first attempt (or soon after)
-                // instead of going through the list of already held ones.
-                let offset = head.offset.get();
-                let len = node.slots.0.len();
-                for i in 0..len {
-                    let i = (i + offset) % len;
-                    // Note: the indexing check is almost certainly optimised out because the len
-                    // is used above. And using .get_unchecked was actually *slower*.
-                    let got_it = node.slots.0[i]
-                        .0
-                        // Try to acquire the slot. Relaxed if it doesn't work is fine, as we don't
-                        // synchronize by it.
-                        .compare_exchange(NO_DEBT, ptr, Ordering::SeqCst, Ordering::Relaxed)
-                        .is_ok();
-                    if got_it {
-                        head.offset.set(i + 1);
-                        return Some(&node.slots.0[i]);
-                    }
-                }
-                None
-            })
-            .ok()
-            .and_then(|new| new)
-    }
-
-    /// Tries to pay the given debt.
-    ///
-    /// If the debt is still there, for the given pointer, it is paid and `true` is returned. If it
-    /// is empty or if there's some other pointer, it is not paid and `false` is returned, meaning
-    /// the debt was paid previously by someone else.
-    ///
-    /// # Notes
-    ///
-    /// * It is possible that someone paid the debt and then someone else put a debt for the same
-    ///   pointer in there. This is fine, as we'll just pay the debt for that someone else.
-    /// * This relies on the fact that the same pointer must point to the same object and
-    ///   specifically to the same type ‒ the caller provides the type, it's destructor, etc.
-    /// * It also relies on the fact the same thing is not stuffed both inside an `Arc` and `Rc` or
-    ///   something like that, but that sounds like a reasonable assumption. Someone storing it
-    ///   through `ArcSwap<T>` and someone else with `ArcSwapOption<T>` will work.
-    #[inline]
-    pub(crate) fn pay<T: RefCnt>(&self, ptr: *const T::Base) -> bool {
-        self.0
-            // If we don't change anything because there's something else, Relaxed is fine.
-            //
-            // The Release works as kind of Mutex. We make sure nothing from the debt-protected
-            // sections leaks below this point.
-            .compare_exchange(ptr as usize, NO_DEBT, Ordering::Release, Ordering::Relaxed)
-            .is_ok()
-    }
-
-    /// Pays all the debts on the given pointer.
-    pub(crate) fn pay_all<T: RefCnt>(ptr: *const T::Base) {
-        let val = unsafe { T::from_ptr(ptr) };
-        // Pre-pay one ref count that can be safely put into a debt slot to pay it.
-        T::inc(&val);
-        traverse::<(), _>(|node| {
-            for slot in &node.slots.0 {
-                if slot
-                    .0
-                    .compare_exchange(ptr as usize, NO_DEBT, Ordering::AcqRel, Ordering::Relaxed)
-                    .is_ok()
-                {
-                    // Pre-pay one more, for another future slot
-                    T::inc(&val);
-                }
-            }
-            None
-        });
-        // Implicit dec by dropping val in here, pair for the above
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use std::sync::Arc;
-
-    /// Checks the assumption that arcs to ZSTs have different pointer values.
-    #[test]
-    fn arc_zst() {
-        struct A;
-        struct B;
-
-        let a = Arc::new(A);
-        let b = Arc::new(B);
-
-        let aref: &A = &a;
-        let bref: &B = &b;
-
-        let aptr = aref as *const _ as usize;
-        let bptr = bref as *const _ as usize;
-        assert_ne!(aptr, bptr);
-    }
-}
diff --git a/src/debt/fast.rs b/src/debt/fast.rs
new file mode 100644
index 0000000..5500e24
--- /dev/null
+++ b/src/debt/fast.rs
@@ -0,0 +1,76 @@
+//! The fast slots for the primary strategy.
+//!
+//! They are faster, but fallible (in case the slots run out or if there's a collision with a
+//! writer thread, this gives up and falls back to secondary strategy).
+//!
+//! They are based on hazard pointer ideas. To acquire one, the pointer is loaded, stored in the
+//! slot and the debt is confirmed by loading it again and checking it is the same.
+//!
+//! # Orderings
+//!
+//! We ensure just one thing here. Since we do both the acquisition of the slot and the exchange of
+//! the pointer in the writer with SeqCst, we are guaranteed to either see the change in case it
+//! hits somewhere in between the two reads of the pointer, or to have successfully acquired it
+//! before the change and before any cleanup of the old pointer happened (in which case we know the
+//! writer will see our debt).
+
+use std::cell::Cell;
+use std::slice::Iter;
+use std::sync::atomic::Ordering::*;
+
+use super::Debt;
+
+const DEBT_SLOT_CNT: usize = 8;
+
+/// Thread-local information for the [`Slots`]
+#[derive(Default)]
+pub(super) struct Local {
+    // The next slot in round-robin rotation. Heuristically tries to balance the load across them
+    // instead of having all of them stuffed towards the start of the array which gets
+    // unsuccessfully iterated through every time.
+    offset: Cell<usize>,
+}
+
+/// Bunch of fast debt slots.
+#[derive(Default)]
+pub(super) struct Slots([Debt; DEBT_SLOT_CNT]);
+
+impl Slots {
+    /// Try to allocate one slot and get the pointer in it.
+    ///
+    /// Fails if there are no free slots.
+    #[inline]
+    pub(super) fn get_debt(&self, ptr: usize, local: &Local) -> Option<&Debt> {
+        // Trick with offsets: we rotate through the slots (save the value from last time)
+        // so successive leases are likely to succeed on the first attempt (or soon after)
+        // instead of going through the list of already held ones.
+        let offset = local.offset.get();
+        let len = self.0.len();
+        for i in 0..len {
+            let i = (i + offset) % len;
+            // Note: the indexing check is almost certainly optimised out because the len
+            // is used above. And using .get_unchecked was actually *slower*.
+            let slot = &self.0[i];
+            if slot.0.load(Relaxed) == Debt::NONE {
+                // We are allowed to split into the check and acquiring the debt. That's because we
+                // are the only ones allowed to change NONE to something else. But we still need a
+                // read-write operation wit SeqCst on it :-(
+                let old = slot.0.swap(ptr, SeqCst);
+                debug_assert_eq!(Debt::NONE, old);
+                local.offset.set(i + 1);
+                return Some(&self.0[i]);
+            }
+        }
+        None
+    }
+}
+
+impl<'a> IntoIterator for &'a Slots {
+    type Item = &'a Debt;
+
+    type IntoIter = Iter<'a, Debt>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.0.iter()
+    }
+}
diff --git a/src/debt/helping.rs b/src/debt/helping.rs
new file mode 100644
index 0000000..ae8029b
--- /dev/null
+++ b/src/debt/helping.rs
@@ -0,0 +1,334 @@
+//! Slots and global/thread local data for the Helping strategy.
+//!
+//! This is inspired (but not an exact copy) of
+//! <https://pvk.ca/Blog/2020/07/07/flatter-wait-free-hazard-pointers/>. The debts are mostly
+//! copies of the ones used by the hybrid strategy, but modified a bit. Just like in the hybrid
+//! strategy, in case the slots run out or when the writer updates the value, the debts are paid by
+//! incrementing the ref count (which is a little slower, but still wait-free/lock-free and still
+//! in order of nanoseconds).
+//!
+//! ## Reader, the fast path
+//!
+//! * Publish an active address ‒ the address we'll be loading stuff from.
+//! * Puts a generation into the control.
+//! * Loads the pointer and puts it to the debt slot.
+//! * Confirms by CaS-replacing the generation back to idle state.
+//!
+//! * Later, we pay it back by CaS-replacing it with the NO_DEPT (like any other slot).
+//!
+//! ## Writer, the non-colliding path
+//!
+//! * Replaces the pointer in the storage.
+//! * The writer walks over all debts. It pays each debt that it is concerned with by bumping the
+//!   reference and replacing the dept with NO_DEPT. The relevant reader will fail in the CaS
+//!   (either because it finds NO_DEPT or other pointer in there) and knows the reference was
+//!   bumped, so it needs to decrement it. Note that it is possible that someone also reuses the
+//!   slot for the _same_ pointer. In that case that reader will set it to NO_DEPT and the newer
+//!   reader will have a pre-paid debt, which is fine.
+//!
+//! ## The collision path
+//!
+//! The reservation of a slot is not atomic, therefore a writer can observe the reservation in
+//! progress. But it doesn't want to wait for it to complete (it wants to be lock-free, which means
+//! it needs to be able to resolve the situation on its own).
+//!
+//! The way it knows it is in progress of the reservation is by seeing a generation in there (it has
+//! a distinct tag). In that case it'll try to:
+//!
+//! * First verify that the reservation is being done for the same address it modified, by reading
+//!   and re-confirming the active_addr slot corresponding to the currently handled node. If it is
+//!   for some other address, the writer doesn't have to be concerned and proceeds to the next slot.
+//! * It does a full load. That is fine, because the writer must be on a different thread than the
+//!   reader and therefore there is at least one free slot. Full load means paying the debt right
+//!   away by incrementing the reference count.
+//! * Then it tries to pass the already fully protected/paid pointer to the reader. It writes it to
+//!   an envelope and CaS-replaces it into the control, instead of the generation (if it fails,
+//!   someone has been faster and it rolls back). We need the envelope because the pointer itself
+//!   doesn't have to be aligned to 4 bytes and we need the space for tags to distinguish the types
+//!   of info in control; we can ensure the envelope is).
+//! * The reader then finds the generation got replaced by a pointer to the envelope and uses that
+//!   pointer inside the envelope. It aborts its own debt. This effectively exchanges the envelopes
+//!   between the threads so each one has an envelope ready for future.
+//!
+//! ## ABA protection
+//!
+//! The generation as pre-reserving the slot allows the writer to make sure it is offering the
+//! loaded pointer to the same reader and that the read value is new enough (and of the same type).
+//!
+//! This solves the general case, but there's also much less frequent but theoretical ABA problem
+//! that could lead to UB, if left unsolved:
+//!
+//! * There is a collision on generation G.
+//! * The writer loads a pointer, bumps it.
+//! * In the meantime, all the 2^30 or 2^62 generations (depending on the usize width) generations
+//!   wrap around.
+//! * The writer stores the outdated and possibly different-typed pointer in there and the reader
+//!   uses it.
+//!
+//! To mitigate that, every time the counter overflows we take the current node and un-assign it
+//! from our current thread. We mark it as in "cooldown" and let it in there until there are no
+//! writers messing with that node any more (if they are not on the node, they can't experience the
+//! ABA problem on it). After that, we are allowed to use it again.
+//!
+//! This doesn't block the reader, it'll simply find *a* node next time ‒ this one, or possibly a
+//! different (or new) one.
+//!
+//! # Orderings
+//!
+//! The linked lists/nodes are already provided for us. So we just need to make sure the debt
+//! juggling is done right. We assume that the local node is ours to write to (others have only
+//! limited right to write to certain fields under certain conditions) and that we are counted into
+//! active writers while we dig through it on the writer end.
+//!
+//! We use SeqCst on a read-write operation both here at the very start of the sequence (storing
+//! the generation into the control) and in the writer on the actual pointer. That establishes a
+//! relation of what has happened first.
+//!
+//! After that we split the time into segments by read-write operations with AcqRel read-write
+//! operations on the control. There's just one control in play for both threads so we don't need
+//! SeqCst and the segments are understood by both the same way. The writer can sometimes use only
+//! load-Acquire on that, because it needs to only read from data written by the reader. It'll
+//! always see data from at least the segment before the observed control value and uses CaS to
+//! send the results back, so it can't go into the past.
+//!
+//! There are two little gotchas:
+//!
+//! * When we read the address we should be loading from, we need to give up if the address does
+//!   not match (we can't simply load from there, because it can be dangling by that point and we
+//!   don't know its type, so we need to use our address for all loading ‒ and we just check they
+//!   match). If we give up, we don't do that CaS into control, therefore we could have given up on
+//!   newer address than the control we have read. For that reason, the address is also stored by
+//!   reader with Release and we read it with Acquire, which'll bring an up to date version of
+//!   control into our thread ‒ and we re-read that one to confirm the address is indeed between
+//!   two same values holding the generation, therefore corresponding to it.
+//! * The destructor doesn't have a SeqCst in the writer, because there was no write in there.
+//!   That's OK. We need to ensure there are no new readers after the "change" we confirm in the
+//!   writer and that change is the destruction ‒ by that time, the destroying thread has exclusive
+//!   ownership and therefore there can be no new readers.
+
+use std::cell::Cell;
+use std::ptr;
+use std::sync::atomic::Ordering::*;
+use std::sync::atomic::{AtomicPtr, AtomicUsize};
+
+use super::Debt;
+use crate::RefCnt;
+
+pub const REPLACEMENT_TAG: usize = 0b01;
+pub const GEN_TAG: usize = 0b10;
+pub const TAG_MASK: usize = 0b11;
+pub const IDLE: usize = 0;
+
+/// Thread local data for the helping strategy.
+#[derive(Default)]
+pub(super) struct Local {
+    // The generation counter.
+    generation: Cell<usize>,
+}
+
+// Make sure the pointers have 2 empty bits. Always.
+#[derive(Default)]
+#[repr(align(4))]
+struct Handover(AtomicUsize);
+
+/// The slots for the helping strategy.
+pub(super) struct Slots {
+    /// The control structure of the slot.
+    ///
+    /// Different threads signal what stage they are in in there. It can contain:
+    ///
+    /// * `IDLE` (nothing is happening, and there may or may not be an active debt).
+    /// * a generation, tagged with GEN_TAG. The reader is trying to acquire a slot right now and a
+    ///   writer might try to help out.
+    /// * A replacement pointer, tagged with REPLACEMENT_TAG. This pointer points to an Handover,
+    ///   containing an already protected value, provided by the writer for the benefit of the
+    ///   reader. The reader should abort its own debt and use this instead. This indirection
+    ///   (storing pointer to the envelope with the actual pointer) is to make sure there's a space
+    ///   for the tag ‒ there is no guarantee the real pointer is aligned to at least 4 bytes, we
+    ///   can however force that for the Handover type.
+    control: AtomicUsize,
+    /// A possibly active debt.
+    slot: Debt,
+    /// If there's a generation in control, this signifies what address the reader is trying to
+    /// load from.
+    active_addr: AtomicUsize,
+    /// A place where a writer can put a replacement value.
+    ///
+    /// Note that this is simply an allocation, and every participating slot contributes one, but
+    /// they may be passed around through the lifetime of the program. It is not accessed directly,
+    /// but through the space_offer thing.
+    ///
+    handover: Handover,
+    /// A pointer to a handover envelope this node currently owns.
+    ///
+    /// A writer makes a switch of its and readers handover when successfully storing a replacement
+    /// in the control.
+    space_offer: AtomicPtr<Handover>,
+}
+
+impl Default for Slots {
+    fn default() -> Self {
+        Slots {
+            control: AtomicUsize::new(IDLE),
+            slot: Debt::default(),
+            // Doesn't matter yet
+            active_addr: AtomicUsize::new(0),
+            // Also doesn't matter
+            handover: Handover::default(),
+            // Here we would like it to point to our handover. But for that we need to be in place
+            // in RAM (effectively pinned, though we use older Rust than Pin, possibly?), so not
+            // yet. See init().
+            space_offer: AtomicPtr::new(ptr::null_mut()),
+        }
+    }
+}
+
+impl Slots {
+    pub(super) fn slot(&self) -> &Debt {
+        &self.slot
+    }
+
+    pub(super) fn get_debt(&self, ptr: usize, local: &Local) -> (usize, bool) {
+        // Incrementing by 4 ensures we always have enough space for 2 bit of tags.
+        let gen = local.generation.get().wrapping_add(4);
+        debug_assert_eq!(gen & GEN_TAG, 0);
+        local.generation.set(gen);
+        // Signal the caller that the node should be sent to a cooldown.
+        let discard = gen == 0;
+        let gen = gen | GEN_TAG;
+        // We will sync by the write to the control. But we also sync the value of the previous
+        // generation/released slot. That way we may re-confirm in the writer that the reader is
+        // not in between here and the compare_exchange below with a stale gen (eg. if we are in
+        // here, the re-confirm there will load the NO_DEPT and we are fine).
+        self.active_addr.store(ptr, Release);
+
+        // We are the only ones allowed to do the IDLE -> * transition and we never leave it in
+        // anything else after an transaction, so this is OK. But we still need a load-store SeqCst
+        // operation here to form a relation between this and the store of the actual pointer in
+        // the writer thread :-(.
+        let prev = self.control.swap(gen, SeqCst);
+        debug_assert_eq!(IDLE, prev, "Left control in wrong state");
+
+        (gen, discard)
+    }
+
+    pub(super) fn help<R, T>(&self, who: &Self, storage_addr: usize, replacement: &R)
+    where
+        T: RefCnt,
+        R: Fn() -> T,
+    {
+        debug_assert_eq!(IDLE, self.control.load(Relaxed));
+        // Also acquires the auxiliary data in other variables.
+        let mut control = who.control.load(Acquire);
+        loop {
+            match control & TAG_MASK {
+                // Nothing to help with
+                IDLE if control == IDLE => break,
+                // Someone has already helped out with that, so we have nothing to do here
+                REPLACEMENT_TAG => break,
+                // Something is going on, let's have a better look.
+                GEN_TAG => {
+                    debug_assert!(
+                        !ptr::eq(self, who),
+                        "Refusing to help myself, makes no sense"
+                    );
+                    // Get the address that other thread is trying to load from. By that acquire,
+                    // we also sync the control into our thread once more and reconfirm that the
+                    // value of the active_addr is in between two same instances, therefore up to
+                    // date to it.
+                    let active_addr = who.active_addr.load(Acquire);
+                    if active_addr != storage_addr {
+                        // Acquire for the same reason as on the top.
+                        let new_control = who.control.load(Acquire);
+                        if new_control == control {
+                            // The other thread is doing something, but to some other ArcSwap, so
+                            // we don't care. Cool, done.
+                            break;
+                        } else {
+                            // The control just changed under our hands, we don't know what to
+                            // trust, so retry.
+                            control = new_control;
+                            continue;
+                        }
+                    }
+
+                    // Now we know this work is for us. Try to create a replacement and offer it.
+                    // This actually does a full-featured load under the hood, but we are currently
+                    // idle and the load doesn't re-enter write, so that's all fine.
+                    let replacement = replacement();
+                    let replace_addr = T::as_ptr(&replacement) as usize;
+                    // If we succeed in helping the other thread, we take their empty space in
+                    // return for us that we pass to them. It's already there, the value is synced
+                    // to us by Acquire on control.
+                    let their_space = who.space_offer.load(Acquire);
+                    // Relaxed is fine, our own thread and nobody but us writes in here.
+                    let my_space = self.space_offer.load(Relaxed);
+                    // Relaxed is fine, we'll sync by the next compare-exchange. If we don't, the
+                    // value won't ever be read anyway.
+                    unsafe {
+                        (*my_space).0.store(replace_addr, Relaxed);
+                    }
+                    // Ensured by the align annotation at the type.
+                    assert_eq!(my_space as usize & TAG_MASK, 0);
+                    let space_addr = (my_space as usize) | REPLACEMENT_TAG;
+                    // Acquire on failure -> same reason as at the top, reading the value.
+                    // Release on success -> we send data to that thread through here. Must be
+                    // AcqRel, because success must be superset of failure. Also, load to get their
+                    // space (it won't have changed, it does when the control is set to IDLE).
+                    match who
+                        .control
+                        .compare_exchange(control, space_addr, AcqRel, Acquire)
+                    {
+                        Ok(_) => {
+                            // We have successfully sent our replacement out (Release) and got
+                            // their space in return (Acquire on that load above).
+                            self.space_offer.store(their_space, Release);
+                            // The ref count went with it, so forget about it here.
+                            T::into_ptr(replacement);
+                            // We have successfully helped out, so we are done.
+                            break;
+                        }
+                        Err(new_control) => {
+                            // Something has changed in between. Let's try again, nothing changed
+                            // (the replacement will get dropped at the end of scope, we didn't do
+                            // anything with the spaces, etc.
+                            control = new_control;
+                        }
+                    }
+                }
+                _ => unreachable!("Invalid control value {:X}", control),
+            }
+        }
+    }
+
+    pub(super) fn init(&mut self) {
+        *self.space_offer.get_mut() = &mut self.handover;
+    }
+
+    pub(super) fn confirm(&self, gen: usize, ptr: usize) -> Result<(), usize> {
+        // Put the slot there and consider it acquire of a „lock“. For that we need swap, not store
+        // only (we need Acquire and Acquire works only on loads). Release is to make sure control
+        // is observable by the other thread (but that's probably not necessary anyway?)
+        let prev = self.slot.0.swap(ptr as usize, AcqRel);
+        debug_assert_eq!(Debt::NONE, prev);
+
+        // Confirm by writing to the control (or discover that we got helped). We stop anyone else
+        // from helping by setting it to IDLE.
+        let control = self.control.swap(IDLE, AcqRel);
+        if control == gen {
+            // Nobody interfered, we have our debt in place and can proceed.
+            Ok(())
+        } else {
+            // Someone put a replacement in there.
+            debug_assert_eq!(control & TAG_MASK, REPLACEMENT_TAG);
+            let handover = (control & !TAG_MASK) as *mut Handover;
+            let replacement = unsafe { &*handover }.0.load(Acquire);
+            // Make sure we advertise the right envelope when we set it to generation next time.
+            self.space_offer.store(handover, Release);
+            // Note we've left the debt in place. The caller should pay it back (without ever
+            // taking advantage of it) to make sure any extra is actually dropped (it is possible
+            // someone provided the replacement *and* paid the debt and we need just one of them).
+            Err(replacement)
+        }
+    }
+}
diff --git a/src/debt/list.rs b/src/debt/list.rs
new file mode 100644
index 0000000..7c1bcc9
--- /dev/null
+++ b/src/debt/list.rs
@@ -0,0 +1,335 @@
+//! A linked list of debt nodes.
+//!
+//! A node may or may not be owned by a thread. Reader debts are allocated in its owned node,
+//! writer walks everything (but may also use some owned values).
+//!
+//! The list is prepend-only ‒ if thread dies, the node lives on (and can be claimed by another
+//! thread later on). This makes the implementation much simpler, since everything here is
+//! `'static` and we don't have to care about knowing when to free stuff.
+//!
+//! The nodes contain both the fast primary slots and a secondary fallback ones.
+//!
+//! # Synchronization
+//!
+//! We synchronize several things here.
+//!
+//! The addition of nodes is synchronized through the head (Load on each read, AcqReal on each
+//! attempt to add another node). Note that certain parts never change after that (they aren't even
+//! atomic) and other things that do change take care of themselves (the debt slots have their own
+//! synchronization, etc).
+//!
+//! The ownership is acquire-release lock pattern.
+//!
+//! Similar, the counting of active writers is an acquire-release lock pattern.
+//!
+//! We also do release-acquire "send" from the start-cooldown to check-cooldown to make sure we see
+//! at least as up to date value of the writers as when the cooldown started. That we if we see 0,
+//! we know it must have happened since then.
+
+use std::cell::Cell;
+use std::ptr;
+use std::slice::Iter;
+use std::sync::atomic::Ordering::*;
+use std::sync::atomic::{AtomicPtr, AtomicUsize};
+
+use super::fast::{Local as FastLocal, Slots as FastSlots};
+use super::helping::{Local as HelpingLocal, Slots as HelpingSlots};
+use super::Debt;
+use crate::RefCnt;
+
+const NODE_UNUSED: usize = 0;
+const NODE_USED: usize = 1;
+const NODE_COOLDOWN: usize = 2;
+
+/// The head of the debt linked list.
+static LIST_HEAD: AtomicPtr<Node> = AtomicPtr::new(ptr::null_mut());
+
+pub struct NodeReservation<'a>(&'a Node);
+
+impl Drop for NodeReservation<'_> {
+    fn drop(&mut self) {
+        self.0.active_writers.fetch_sub(1, Release);
+    }
+}
+
+/// One thread-local node for debts.
+#[repr(C, align(64))]
+pub(crate) struct Node {
+    fast: FastSlots,
+    helping: HelpingSlots,
+    in_use: AtomicUsize,
+    next: Option<&'static Node>,
+    active_writers: AtomicUsize,
+}
+
+impl Default for Node {
+    fn default() -> Self {
+        Node {
+            fast: FastSlots::default(),
+            helping: HelpingSlots::default(),
+            in_use: AtomicUsize::new(NODE_USED),
+            next: None,
+            active_writers: AtomicUsize::new(0),
+        }
+    }
+}
+
+impl Node {
+    /// Goes through the debt linked list.
+    ///
+    /// This traverses the linked list, calling the closure on each node. If the closure returns
+    /// `Some`, it terminates with that value early, otherwise it runs to the end.
+    pub(crate) fn traverse<R, F: FnMut(&'static Node) -> Option<R>>(mut f: F) -> Option<R> {
+        // Acquire ‒ we want to make sure we read the correct version of data at the end of the
+        // pointer. Any write to the DEBT_HEAD is with Release.
+        //
+        // Note that the other pointers in the chain never change and are *ordinary* pointers. The
+        // whole linked list is synchronized through the head.
+        let mut current = unsafe { LIST_HEAD.load(Acquire).as_ref() };
+        while let Some(node) = current {
+            let result = f(node);
+            if result.is_some() {
+                return result;
+            }
+            current = node.next;
+        }
+        None
+    }
+
+    /// Put the current thread node into cooldown
+    fn start_cooldown(&self) {
+        // Trick: Make sure we have an up to date value of the active_writers in this thread, so we
+        // can properly release it below.
+        let _reservation = self.reserve_writer();
+        assert_eq!(NODE_USED, self.in_use.swap(NODE_COOLDOWN, Release));
+    }
+
+    /// Perform a cooldown if the node is ready.
+    ///
+    /// See the ABA protection at the [helping].
+    fn check_cooldown(&self) {
+        // Check if the node is in cooldown, for two reasons:
+        // * Skip most of nodes fast, without dealing with them.
+        // * More importantly, sync the value of active_writers to be at least the value when the
+        //   cooldown started. That way we know the 0 we observe happened some time after
+        //   start_cooldown.
+        if self.in_use.load(Acquire) == NODE_COOLDOWN {
+            // The rest can be nicely relaxed ‒ no memory is being synchronized by these
+            // operations. We just see an up to date 0 and allow someone (possibly us) to claim the
+            // node later on.
+            if self.active_writers.load(Relaxed) == 0 {
+                let _ = self
+                    .in_use
+                    .compare_exchange(NODE_COOLDOWN, NODE_UNUSED, Relaxed, Relaxed);
+            }
+        }
+    }
+
+    /// Mark this node that a writer is currently playing with it.
+    pub fn reserve_writer(&self) -> NodeReservation {
+        self.active_writers.fetch_add(1, Acquire);
+        NodeReservation(self)
+    }
+
+    /// "Allocate" a node.
+    ///
+    /// Either a new one is created, or previous one is reused. The node is claimed to become
+    /// in_use.
+    fn get() -> &'static Self {
+        // Try to find an unused one in the chain and reuse it.
+        Self::traverse(|node| {
+            node.check_cooldown();
+            if node
+                .in_use
+                // We claim a unique control over the generation and the right to write to slots if
+                // they are NO_DEPT
+                .compare_exchange(NODE_UNUSED, NODE_USED, Acquire, Relaxed)
+                .is_ok()
+            {
+                Some(node)
+            } else {
+                None
+            }
+        })
+        // If that didn't work, create a new one and prepend to the list.
+        .unwrap_or_else(|| {
+            let node = Box::leak(Box::new(Node::default()));
+            node.helping.init();
+            // We don't want to read any data in addition to the head, Relaxed is fine
+            // here.
+            //
+            // We do need to release the data to others, but for that, we acquire in the
+            // compare_exchange below.
+            let mut head = LIST_HEAD.load(Relaxed);
+            loop {
+                node.next = unsafe { head.as_ref() };
+                if let Err(old) = LIST_HEAD.compare_exchange_weak(
+                    head, node,
+                    // We need to release *the whole chain* here. For that, we need to
+                    // acquire it first.
+                    AcqRel, Relaxed, // Nothing changed, go next round of the loop.
+                ) {
+                    head = old;
+                } else {
+                    return node;
+                }
+            }
+        })
+    }
+
+    /// Iterate over the fast slots.
+    pub(crate) fn fast_slots(&self) -> Iter<Debt> {
+        self.fast.into_iter()
+    }
+
+    /// Access the helping slot.
+    pub(crate) fn helping_slot(&self) -> &Debt {
+        self.helping.slot()
+    }
+}
+
+/// A wrapper around a node pointer, to un-claim the node on thread shutdown.
+pub(crate) struct LocalNode {
+    /// Node for this thread, if any.
+    ///
+    /// We don't necessarily have to own one, but if we don't, we'll get one before the first use.
+    node: Cell<Option<&'static Node>>,
+
+    /// Thread-local data for the fast slots.
+    fast: FastLocal,
+
+    /// Thread local data for the helping strategy.
+    helping: HelpingLocal,
+}
+
+impl LocalNode {
+    pub(crate) fn with<R, F: FnOnce(&LocalNode) -> R>(f: F) -> R {
+        let f = Cell::new(Some(f));
+        THREAD_HEAD
+            .try_with(|head| {
+                if head.node.get().is_none() {
+                    head.node.set(Some(Node::get()));
+                }
+                let f = f.take().unwrap();
+                f(head)
+            })
+            // During the application shutdown, the thread local storage may be already
+            // deallocated. In that case, the above fails but we still need something. So we just
+            // find or allocate a node and use it just once.
+            //
+            // Note that the situation should be very very rare and not happen often, so the slower
+            // performance doesn't matter that much.
+            .unwrap_or_else(|_| {
+                let tmp_node = LocalNode {
+                    node: Cell::new(Some(Node::get())),
+                    fast: FastLocal::default(),
+                    helping: HelpingLocal::default(),
+                };
+                let f = f.take().unwrap();
+                f(&tmp_node)
+                // Drop of tmp_node -> sends the node we just used into cooldown.
+            })
+    }
+
+    /// Creates a new debt.
+    ///
+    /// This stores the debt of the given pointer (untyped, casted into an usize) and returns a
+    /// reference to that slot, or gives up with `None` if all the slots are currently full.
+    #[inline]
+    pub(crate) fn new_fast(&self, ptr: usize) -> Option<&'static Debt> {
+        let node = &self.node.get().expect("LocalNode::with ensures it is set");
+        debug_assert_eq!(node.in_use.load(Relaxed), NODE_USED);
+        node.fast.get_debt(ptr, &self.fast)
+    }
+
+    /// Initializes a helping slot transaction.
+    ///
+    /// Returns the generation (with tag).
+    pub(crate) fn new_helping(&self, ptr: usize) -> usize {
+        let node = &self.node.get().expect("LocalNode::with ensures it is set");
+        debug_assert_eq!(node.in_use.load(Relaxed), NODE_USED);
+        let (gen, discard) = node.helping.get_debt(ptr, &self.helping);
+        if discard {
+            // Too many generations happened, make sure the writers give the poor node a break for
+            // a while so they don't observe the generation wrapping around.
+            node.start_cooldown();
+            self.node.take();
+        }
+        gen
+    }
+
+    /// Confirm the helping transaction.
+    ///
+    /// The generation comes from previous new_helping.
+    ///
+    /// Will either return a debt with the pointer, or a debt to pay and a replacement (already
+    /// protected) address.
+    pub(crate) fn confirm_helping(
+        &self,
+        gen: usize,
+        ptr: usize,
+    ) -> Result<&'static Debt, (&'static Debt, usize)> {
+        let node = &self.node.get().expect("LocalNode::with ensures it is set");
+        debug_assert_eq!(node.in_use.load(Relaxed), NODE_USED);
+        let slot = node.helping_slot();
+        node.helping
+            .confirm(gen, ptr)
+            .map(|()| slot)
+            .map_err(|repl| (slot, repl))
+    }
+
+    /// The writer side of a helping slot.
+    ///
+    /// This potentially helps the `who` node (uses self as the local node, which must be
+    /// different) by loading the address that one is trying to load.
+    pub(super) fn help<R, T>(&self, who: &Node, storage_addr: usize, replacement: &R)
+    where
+        T: RefCnt,
+        R: Fn() -> T,
+    {
+        let node = &self.node.get().expect("LocalNode::with ensures it is set");
+        debug_assert_eq!(node.in_use.load(Relaxed), NODE_USED);
+        node.helping.help(&who.helping, storage_addr, replacement)
+    }
+}
+
+impl Drop for LocalNode {
+    fn drop(&mut self) {
+        if let Some(node) = self.node.get() {
+            // Release - syncing writes/ownership of this Node
+            node.start_cooldown();
+        }
+    }
+}
+
+thread_local! {
+    /// A debt node assigned to this thread.
+    static THREAD_HEAD: LocalNode = LocalNode {
+        node: Cell::new(None),
+        fast: FastLocal::default(),
+        helping: HelpingLocal::default(),
+    };
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    impl Node {
+        fn is_empty(&self) -> bool {
+            self.fast_slots()
+                .chain(std::iter::once(self.helping_slot()))
+                .all(|d| d.0.load(Relaxed) == Debt::NONE)
+        }
+
+        fn get_thread() -> &'static Self {
+            LocalNode::with(|h| h.node.get().unwrap())
+        }
+    }
+
+    /// A freshly acquired thread local node is empty.
+    #[test]
+    fn new_empty() {
+        assert!(Node::get_thread().is_empty());
+    }
+}
diff --git a/src/debt/mod.rs b/src/debt/mod.rs
new file mode 100644
index 0000000..8a792a2
--- /dev/null
+++ b/src/debt/mod.rs
@@ -0,0 +1,137 @@
+//! Debt handling.
+//!
+//! A debt is a reference count of a smart pointer that is owed. This module provides a lock-free
+//! storage for debts.
+//!
+//! Each thread has its own node with bunch of slots. Only that thread can allocate debts in there,
+//! but others are allowed to inspect and pay them. The nodes form a linked list for the reason of
+//! inspection. The nodes are never removed (even after the thread terminates), but if the thread
+//! gives it up, another (new) thread can claim it.
+//!
+//! The writers walk the whole chain and pay the debts (by bumping the ref counts) of the just
+//! removed pointer.
+//!
+//! Each node has some fast (but fallible) nodes and a fallback node, with different algorithms to
+//! claim them (see the relevant submodules).
+
+use std::sync::atomic::AtomicUsize;
+use std::sync::atomic::Ordering::*;
+
+pub(crate) use self::list::{LocalNode, Node};
+use super::RefCnt;
+
+mod fast;
+mod helping;
+mod list;
+
+/// One debt slot.
+///
+/// It may contain an „owed“ reference count.
+#[derive(Debug)]
+pub(crate) struct Debt(pub(crate) AtomicUsize);
+
+impl Debt {
+    /// The value of pointer `3` should be pretty safe, for two reasons:
+    ///
+    /// * It's an odd number, but the pointers we have are likely aligned at least to the word size,
+    ///   because the data at the end of the `Arc` has the counters.
+    /// * It's in the very first page where NULL lives, so it's not mapped.
+    pub(crate) const NONE: usize = 0b11;
+}
+
+impl Default for Debt {
+    fn default() -> Self {
+        Debt(AtomicUsize::new(Self::NONE))
+    }
+}
+
+impl Debt {
+    /// Tries to pay the given debt.
+    ///
+    /// If the debt is still there, for the given pointer, it is paid and `true` is returned. If it
+    /// is empty or if there's some other pointer, it is not paid and `false` is returned, meaning
+    /// the debt was paid previously by someone else.
+    ///
+    /// # Notes
+    ///
+    /// * It is possible that someone paid the debt and then someone else put a debt for the same
+    ///   pointer in there. This is fine, as we'll just pay the debt for that someone else.
+    /// * This relies on the fact that the same pointer must point to the same object and
+    ///   specifically to the same type ‒ the caller provides the type, it's destructor, etc.
+    /// * It also relies on the fact the same thing is not stuffed both inside an `Arc` and `Rc` or
+    ///   something like that, but that sounds like a reasonable assumption. Someone storing it
+    ///   through `ArcSwap<T>` and someone else with `ArcSwapOption<T>` will work.
+    #[inline]
+    pub(crate) fn pay<T: RefCnt>(&self, ptr: *const T::Base) -> bool {
+        self.0
+            // If we don't change anything because there's something else, Relaxed is fine.
+            //
+            // The Release works as kind of Mutex. We make sure nothing from the debt-protected
+            // sections leaks below this point.
+            //
+            // Note that if it got paid already, it is inside the reference count. We don't
+            // necessarily observe that increment, but whoever destroys the pointer *must* see the
+            // up to date value, with all increments already counted in (the Arc takes care of that
+            // part).
+            .compare_exchange(ptr as usize, Self::NONE, Release, Relaxed)
+            .is_ok()
+    }
+
+    /// Pays all the debts on the given pointer and the storage.
+    pub(crate) fn pay_all<T, R>(ptr: *const T::Base, storage_addr: usize, replacement: R)
+    where
+        T: RefCnt,
+        R: Fn() -> T,
+    {
+        LocalNode::with(|local| {
+            let val = unsafe { T::from_ptr(ptr) };
+            // Pre-pay one ref count that can be safely put into a debt slot to pay it.
+            T::inc(&val);
+
+            Node::traverse::<(), _>(|node| {
+                // Make the cooldown trick know we are poking into this node.
+                let _reservation = node.reserve_writer();
+
+                local.help(node, storage_addr, &replacement);
+
+                let all_slots = node
+                    .fast_slots()
+                    .chain(std::iter::once(node.helping_slot()));
+                for slot in all_slots {
+                    // Note: Release is enough even here. That makes sure the increment is
+                    // visible to whoever might acquire on this slot and can't leak below this.
+                    // And we are the ones doing decrements anyway.
+                    if slot.pay::<T>(ptr) {
+                        // Pre-pay one more, for another future slot
+                        T::inc(&val);
+                    }
+                }
+
+                None
+            });
+            // Implicit dec by dropping val in here, pair for the above
+        })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::sync::Arc;
+
+    /// Checks the assumption that arcs to ZSTs have different pointer values.
+    #[test]
+    fn arc_zst() {
+        struct A;
+        struct B;
+
+        let a = Arc::new(A);
+        let b = Arc::new(B);
+
+        let aref: &A = &a;
+        let bref: &B = &b;
+
+        let aptr = aref as *const _ as usize;
+        let bptr = bref as *const _ as usize;
+        assert_ne!(aptr, bptr);
+    }
+}
diff --git a/src/docs/internal.rs b/src/docs/internal.rs
index 545032b..f5ee001 100644
--- a/src/docs/internal.rs
+++ b/src/docs/internal.rs
@@ -39,11 +39,16 @@
 //! replaced in just the wrong moment and it can't confirm the reservation), unlike the full
 //! hazard-pointers approach, we don't retry, but fall back onto secondary strategy.
 //!
-//! Each reader registers itself so it can be tracked, but only as a number. Each writer first
-//! switches the pointer. Then it takes a snapshot of all the current readers and waits until all of
-//! them confirm bumping their reference count. Only then the writer returns to the caller, handing
-//! it the ownership of the Arc and allowing possible bad things (like being destroyed) to happen to
-//! it. This has its own disadvantages, so it is only the second approach.
+//! The secondary strategy is similar, but a bit more complex (and therefore slower, that's why it
+//! is only a fallback). We first publish an intent to read a pointer (and where we are reading it
+//! from). Then we actually do so and publish the debt, like previously.
+//!
+//! The writer pays the debts as usual. But also, if it sees the intent to read the value, it helps
+//! along, reads it, bumps the reference and passes it to the reader. Therefore, if the reader
+//! fails to do the protection itself, because it got interrupted by a writer, it finds a
+//! ready-made replacement value it can just use and doesn't have to retry. Also, the writer
+//! doesn't have to wait for the reader in any way, because it can just solve its problem and move
+//! on.
 //!
 //! # Unsafety
 //!
@@ -56,31 +61,6 @@
 //! stored there. When the arc is replaced, this 1 is returned to the caller, so we just have to
 //! make sure no more readers access it by that time.
 //!
-//! # Tracking of readers
-//!
-//! The simple way would be to have a count of all readers that could be in the dangerous area
-//! between reading the pointer and bumping the reference count. We could „lock“ the ref count by
-//! incrementing this atomic counter and „unlock“ it when done. The writer would just have to
-//! busy-wait for this number to drop to 0 ‒ then there are no readers at all. This is safe, but a
-//! steady inflow of readers could make a writer wait forever.
-//!
-//! Therefore, we separate readers into two groups, odd and even ones (see below how). When we see
-//! both groups to drop to 0 (not necessarily at the same time, though), we are sure all the
-//! previous readers were flushed ‒ each of them had to be either odd or even.
-//!
-//! To do that, we define a generation. A generation is a number, incremented at certain times and a
-//! reader decides by this number if it is odd or even.
-//!
-//! One of the writers may increment the generation when it sees a zero in the next-generation's
-//! group (if the writer sees 0 in the odd group and the current generation is even, all the current
-//! writers are even ‒ so it remembers it saw odd-zero and increments the generation, so new readers
-//! start to appear in the odd group and the even has a chance to drop to zero later on). Only one
-//! writer does this switch, but all that witness the zero can remember it.
-//!
-//! We also split the reader threads into shards ‒ we have multiple copies of the counters, which
-//! prevents some contention and sharing of the cache lines. The writer reads them all and sums them
-//! up.
-//!
 //! # Leases and debts
 //!
 //! Instead of incrementing the reference count, the pointer reference can be owed. In such case, it
@@ -98,49 +78,25 @@
 //! We have AcqRel (well, SeqCst, but that's included) on the swap and Acquire on the loads. In case
 //! of the double read around the debt allocation, we do that on the *second*, because of ABA.
 //! That's also why that SeqCst on the allocation of debt itself is not enough.
-//!
-//! ## The generation lock
-//!
-//! Second, the dangerous area when we borrowed the pointer but haven't yet incremented its ref
-//! count needs to stay between incrementing and decrementing the reader count (in either group). To
-//! accomplish that, using Acquire on the increment and Release on the decrement would be enough.
-//! The loads in the writer use Acquire to complete the edge and make sure no part of the dangerous
-//! area leaks outside of it in the writers view. This Acquire, however, forms the edge only with
 //! the *latest* decrement. By making both the increment and decrement AcqRel, we effectively chain
 //! the edges together.
 //!
-//! Now the hard part :-). We need to ensure that whatever zero a writer sees is not stale in the
-//! sense that it happened before the switch of the pointer. In other words, we need to make sure
-//! that at the time we start to look for the zeroes, we already see all the current readers. To do
-//! that, we need to synchronize the time lines of the pointer itself and the corresponding group
-//! counters. As these are separate, unrelated, atomics, it calls for SeqCst ‒ on the swap and on
-//! the increment. This'll guarantee that they'll know which happened first (either increment or the
-//! swap), making a base line for the following operations (load of the pointer or looking for
-//! zeroes).
-//!
 //! # Memory orders around debts
 //!
 //! The linked list of debt nodes only grows. The shape of the list (existence of nodes) is
 //! synchronized through Release on creation and Acquire on load on the head pointer.
 //!
 //! The debts work similar to locks ‒ Acquire and Release make all the pointer manipulation at the
-//! interval where it is written down. However, we use the SeqCst on the allocation of the debt for
-//! the same reason we do so with the generation lock.
+//! interval where it is written down. However, we use the SeqCst on the allocation of the debt
+//! because when we see an empty slot, we need to make sure that it happened after we have
+//! overwritten the pointer.
 //!
 //! In case the writer pays the debt, it sees the new enough data (for the same reasons the stale
-//! zeroes are not seen). The reference count on the Arc is AcqRel and makes sure it is not
+//! empties are not seen). The reference count on the Arc is AcqRel and makes sure it is not
 //! destroyed too soon. The writer traverses all the slots, therefore they don't need to synchronize
 //! with each other.
 //!
-//! # Orderings on the rest
-//!
-//! We don't really care much if we use a stale generation number ‒ it only works to route the
-//! readers into one or another bucket, but even if it was completely wrong, it would only slow the
-//! waiting for 0 down. So, the increments of it are just hints.
-//!
-//! All other operations can be Relaxed (they either only claim something, which doesn't need to
-//! synchronize with anything else, or they are failed attempts at something ‒ and another attempt
-//! will be made, the successful one will do the necessary synchronization).
+//! Further details are inside the internal `debt` module.
 //!
 //! [`RefCnt`]: crate::RefCnt
 //! [`Arc`]: std::sync::Arc
diff --git a/src/docs/performance.rs b/src/docs/performance.rs
index 3bebf12..6358c87 100644
--- a/src/docs/performance.rs
+++ b/src/docs/performance.rs
@@ -5,8 +5,10 @@
 //! ## Lock-free readers
 //!
 //! All the read operations are always [lock-free]. Most of the time, they are actually
-//! [wait-free], the notable exception is the first [`load`] access in each thread (across all the
-//! instances of [`ArcSwap`]), as it sets up some thread-local data structures.
+//! [wait-free]. They are [lock-free] from time to time, with at least `usize::MAX / 4` accesses
+//! that are [wait-free] in between.
+//!
+//! Writers are [lock-free].
 //!
 //! Whenever the documentation talks about *contention* in the context of [`ArcSwap`], it talks
 //! about contention on the CPU level ‒ multiple cores having to deal with accessing the same cache
@@ -14,10 +16,6 @@
 //! eventual progress is still guaranteed and the cost is significantly lower than parking threads
 //! as with mutex-style contention.
 //!
-//! Unfortunately writers are *not* [lock-free]. A reader stuck (suspended/killed) in a critical
-//! section (few instructions long in case of [`load`]) may block a writer from completion.
-//! Nevertheless, a steady inflow of new readers nor other writers will not block the writer.
-//!
 //! ## Speeds
 //!
 //! The base line speed of read operations is similar to using an *uncontended* [`Mutex`].
@@ -32,7 +30,8 @@
 //! an *uncontended* [`Mutex`] and on some architectures even slower than uncontended
 //! [`RwLock`]. However, it is faster than either under contention.
 //!
-//! There are some (very unscientific) [benchmarks] within the source code of the library.
+//! There are some (very unscientific) [benchmarks] within the source code of the library, and the
+//! [`DefaultStrategy`][crate::DefaultStrategy] has some numbers measured on my computer.
 //!
 //! The exact numbers are highly dependant on the machine used (both absolute numbers and relative
 //! between different data structures). Not only architectures have a huge impact (eg. x86 vs ARM),
@@ -61,7 +60,7 @@
 //! they no longer use it, contending on the count.
 //!
 //! The other situation derives from internal implementation. The number of borrows each thread can
-//! have at each time (across all [`Guard`]s) is limited. If this limit is exceeded, an onwed
+//! have at each time (across all [`Guard`]s) is limited. If this limit is exceeded, an owned
 //! instance is created instead.
 //!
 //! Therefore, if you intend to hold onto the loaded value for extended time span, you may prefer
diff --git a/src/gen_lock.rs b/src/gen_lock.rs
deleted file mode 100644
index 1998d4f..0000000
--- a/src/gen_lock.rs
+++ /dev/null
@@ -1,283 +0,0 @@
-//! Customization of where and how the generation lock works.
-//!
-//! By default, all the [`ArcSwapAny`](../struct.ArcSwapAny.html) instances share the same
-//! generation lock. This is to save space in them (they have the same size as a single pointer),
-//! because the default lock is quite a large data structure (it's sharded, to prevent too much
-//! contention between different threads). This has the disadvantage that a lock on one instance
-//! influences another instance.
-//!
-//! The things in this module allow customizing how the lock behaves. The default one is
-//! [`Global`](struct.Global.html). If you want to use independent but unsharded lock, use the
-//! [`PrivateUnsharded`](struct.PrivateUnsharded.html) (or the
-//! [`IndependentArcSwap`](../type.IndependentArcSwap.html) type alias).
-//!
-//! Or you can implement your own lock, but you probably should study the internals of the library
-//! first.
-//!
-//! # Not Implemented Yet
-//!
-//! These variants would probably make sense, but haven't been written yet:
-//!
-//! * A lock storage that is shared, but only between a certain group of pointers. It could be
-//!   either as a reference (but then each `ArcSwap` would get a bit bigger), or a macro that could
-//!   generate an independent but global storage.
-
-use std::borrow::Borrow;
-use std::cell::Cell;
-use std::process;
-use std::sync::atomic::{self, AtomicUsize, Ordering};
-use std::thread;
-
-/// Number of shards (see [`Shard`]).
-const SHARD_CNT: usize = 9;
-
-/// If waiting in a spin loop, do a thread yield to the OS scheduler this many iterations
-const YIELD_EVERY: usize = 16;
-
-/// Maximum number of guards in the critical section
-const MAX_GUARDS: usize = (std::isize::MAX) as usize;
-
-/// How many generations we have in the lock.
-pub(crate) const GEN_CNT: usize = 2;
-
-/// A single shard.
-///
-/// This is one copy of place where the library keeps tracks of generation locks. It consists of a
-/// pair of counters and allows double-buffering readers (therefore, even if there's a never-ending
-/// stream of readers coming in, writer will get through eventually).
-///
-/// To avoid contention and sharing of the counters between readers, we don't have one pair of
-/// generation counters, but several. The reader picks one shard and uses that, while the writer
-/// looks through all of them. This is still not perfect (two threads may choose the same ID), but
-/// it helps.
-///
-/// Each [`LockStorage`](trait.LockStorage.html) must provide a (non-empty) array of these.
-#[repr(align(64))]
-#[derive(Default)]
-pub struct Shard(pub(crate) [AtomicUsize; GEN_CNT]);
-
-fn snapshot(shard: &[AtomicUsize; GEN_CNT]) -> [usize; GEN_CNT] {
-    [
-        shard[0].load(Ordering::Acquire),
-        shard[1].load(Ordering::Acquire),
-    ]
-}
-
-impl Borrow<[AtomicUsize; GEN_CNT]> for Shard {
-    #[inline]
-    fn borrow(&self) -> &[AtomicUsize; GEN_CNT] {
-        &self.0
-    }
-}
-
-/// Abstraction of the place where generation locks are stored.
-///
-/// The trait is unsafe because if the trait messes up with the values stored in there in any way
-/// (or makes the values available to something else that messes them up), this can cause UB and
-/// daemons and discomfort to users and such. The library expects it is the only one storing values
-/// there. In other words, it is expected the trait is only a dumb storage and doesn't actively do
-/// anything.
-pub unsafe trait LockStorage: Default {
-    /// Type of one shard.
-    type Shard: Borrow<[AtomicUsize; GEN_CNT]>;
-
-    /// The type for keeping several shards.
-    ///
-    /// In general, it is expected to be a fixed-size array, but different implementations can have
-    /// different sizes.
-    type Shards: AsRef<[Self::Shard]>;
-
-    /// Access to the generation index.
-    ///
-    /// Must return the same instance of the `AtomicUsize` for the lifetime of the storage, must
-    /// start at `0` and the trait itself must not modify it.
-    fn gen_idx(&self) -> &AtomicUsize;
-
-    /// Access to the shards storage.
-    ///
-    /// Must return the same instance of the shards for the lifetime of the storage. Must start
-    /// zeroed-out and the trait itself must not modify it.
-    fn shards(&self) -> &Self::Shards;
-
-    /// Pick one shard of the all selected.
-    ///
-    /// Returns the index of one of the shards. The choice can be arbitrary, but it should be fast
-    /// and avoid collisions.
-    fn choose_shard(&self) -> usize;
-}
-
-static GEN_IDX: AtomicUsize = AtomicUsize::new(0);
-
-macro_rules! sh {
-    () => {
-        Shard([AtomicUsize::new(0), AtomicUsize::new(0)])
-    };
-}
-
-type Shards = [Shard; SHARD_CNT];
-
-/// The global shards.
-static SHARDS: [Shard; SHARD_CNT] = [
-    sh!(),
-    sh!(),
-    sh!(),
-    sh!(),
-    sh!(),
-    sh!(),
-    sh!(),
-    sh!(),
-    sh!(),
-];
-
-/// Global counter of threads.
-///
-/// We specifically don't use ThreadId here, because it is opaque and doesn't give us a number :-(.
-static THREAD_ID_GEN: AtomicUsize = AtomicUsize::new(0);
-
-thread_local! {
-    /// A shard a thread has chosen.
-    ///
-    /// The default value is just a marker it hasn't been set.
-    static THREAD_SHARD: Cell<usize> = Cell::new(SHARD_CNT);
-}
-
-/// The default, global lock.
-///
-/// The lock is stored out-of-band, globally. This means that one `ArcSwap` with this lock storage
-/// is only one machine word large, but a lock on one instance blocks the other, independent ones.
-///
-/// It has several shards so threads are less likely to collide (HW-contend) on them.
-#[derive(Copy, Clone, Default)]
-pub struct Global;
-
-unsafe impl LockStorage for Global {
-    type Shard = Shard;
-    type Shards = Shards;
-
-    #[inline]
-    fn gen_idx(&self) -> &AtomicUsize {
-        &GEN_IDX
-    }
-
-    #[inline]
-    fn shards(&self) -> &Shards {
-        &SHARDS
-    }
-
-    #[inline]
-    fn choose_shard(&self) -> usize {
-        THREAD_SHARD
-            .try_with(|ts| {
-                let mut val = ts.get();
-                if val >= SHARD_CNT {
-                    val = THREAD_ID_GEN.fetch_add(1, Ordering::Relaxed) % SHARD_CNT;
-                    ts.set(val);
-                }
-                val
-            })
-            .unwrap_or(0)
-    }
-}
-
-/// A single „shard“ that is stored inline, inside the corresponding `ArcSwap`. Therefore, locks on
-/// each instance won't influence any other instances. On the other hand, the `ArcSwap` itself gets
-/// bigger and doesn't have multiple shards, so concurrent uses might contend each other a bit.
-///
-/// Note that there`s a type alias [`IndependentArcSwap`](../type.IndependentArcSwap.html) that can
-/// be used instead.
-#[derive(Default)]
-pub struct PrivateUnsharded {
-    gen_idx: AtomicUsize,
-    shard: [[AtomicUsize; GEN_CNT]; 1],
-}
-
-unsafe impl LockStorage for PrivateUnsharded {
-    type Shard = [AtomicUsize; GEN_CNT];
-    type Shards = [Self::Shard; 1];
-
-    #[inline]
-    fn gen_idx(&self) -> &AtomicUsize {
-        &self.gen_idx
-    }
-
-    #[inline]
-    fn shards(&self) -> &[Self::Shard; 1] {
-        &self.shard
-    }
-
-    #[inline]
-    fn choose_shard(&self) -> usize {
-        0
-    }
-}
-
-// TODO: Some nice docs about this thing
-pub(crate) fn wait_for_readers<S: LockStorage>(storage: &S) {
-    let mut seen_group = [false; GEN_CNT];
-    let mut iter = 0usize;
-    let gen_idk = storage.gen_idx();
-    let shards = storage.shards().as_ref();
-
-    loop {
-        // Note that we don't need the snapshot to be consistent. We just need to see both
-        // halves being zero, not necessarily at the same time.
-        let gen = gen_idk.load(Ordering::Relaxed);
-        let groups = shards.iter().fold([0, 0], |[a1, a2], s| {
-            let [v1, v2] = snapshot(s.borrow());
-            [a1 + v1, a2 + v2]
-        });
-        // Should we increment the generation? Is the next one empty?
-        let next_gen = gen.wrapping_add(1);
-        if groups[next_gen % GEN_CNT] == 0 {
-            // Replace it only if someone else didn't do it in the meantime
-            let _ = gen_idk.compare_exchange(gen, next_gen, Ordering::Relaxed, Ordering::Relaxed);
-        }
-        for i in 0..GEN_CNT {
-            seen_group[i] = seen_group[i] || (groups[i] == 0);
-        }
-
-        if seen_group.iter().all(|seen| *seen) {
-            break;
-        }
-
-        iter = iter.wrapping_add(1);
-        if cfg!(not(miri)) {
-            if iter % YIELD_EVERY == 0 {
-                thread::yield_now();
-            } else {
-                atomic::spin_loop_hint();
-            }
-        }
-    }
-}
-
-pub(crate) struct GenLock<'a> {
-    slot: &'a AtomicUsize,
-}
-
-impl<'a> GenLock<'a> {
-    pub(crate) fn new<S: LockStorage + 'a>(storage: &'a S) -> Self {
-        let shard = storage.choose_shard();
-        let gen = storage.gen_idx().load(Ordering::Relaxed) % GEN_CNT;
-        // TODO: Is this still needed? Is the other SeqCst needed, in the writer? Is *there* any?
-        // Or should it be Release in there and SeqCst barrier as part of wait_for_readers?
-        // SeqCst: Acquire, so the dangerous section stays in. SeqCst to sync timelines with the
-        // swap on the ptr in writer thread.
-        let slot = &storage.shards().as_ref()[shard].borrow()[gen];
-        let old = slot.fetch_add(1, Ordering::SeqCst);
-        // The trick is taken from Arc.
-        if old > MAX_GUARDS {
-            process::abort();
-        }
-
-        Self { slot }
-    }
-}
-
-impl Drop for GenLock<'_> {
-    fn drop(&mut self) {
-        // Release, so the dangerous section stays in. Acquire to chain the operations.
-        // Do not drop the inner (maybe we should do into_raw for proper measures?)
-        self.slot.fetch_sub(1, Ordering::AcqRel);
-    }
-}
diff --git a/src/lib.rs b/src/lib.rs
index 1444add..3372122 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -129,7 +129,6 @@ pub mod cache;
 mod compile_fail_tests;
 mod debt;
 pub mod docs;
-mod gen_lock;
 mod ref_cnt;
 pub mod strategy;
 #[cfg(feature = "weak")]
@@ -449,8 +448,6 @@ impl<T: RefCnt, S: Strategy<T>> ArcSwapAny<T, S> {
     }
 
     /// Exchanges the value inside this instance.
-    ///
-    /// Note that this method is *not* lock-free with the [`DefaultStrategy`].
     pub fn swap(&self, new: T) -> T {
         let new = T::into_ptr(new);
         // AcqRel needed to publish the target of the new pointer and get the target of the old
@@ -744,6 +741,8 @@ impl<T, S: Strategy<Option<Arc<T>>>> ArcSwapAny<Option<Arc<T>>, S> {
 /// threads at once. On the other hand, it can't block writes in other instances.
 ///
 /// See the [`IndependentStrategy`] for further details.
+// Being phased out. Will deprecate once we verify in production that the new strategy works fine.
+#[doc(hidden)]
 pub type IndependentArcSwap<T> = ArcSwapAny<Arc<T>, IndependentStrategy>;
 
 /// Arc swap for the [Weak] pointer.
@@ -754,6 +753,8 @@ pub type IndependentArcSwap<T> = ArcSwapAny<Arc<T>, IndependentStrategy>;
 /// This is a type alias only. Most of the methods are described on the
 /// [`ArcSwapAny`](struct.ArcSwapAny.html).
 ///
+/// Needs the `weak` feature turned on.
+///
 /// [Weak]: std::sync::Weak
 #[cfg(feature = "weak")]
 pub type ArcSwapWeak<T> = ArcSwapAny<std::sync::Weak<T>>;
@@ -772,7 +773,9 @@ macro_rules! t {
 
             const ITERATIONS: usize = 10;
 
+            #[allow(deprecated)] // We use "deprecated" testing strategies in here.
             type AS<T> = ArcSwapAny<Arc<T>, $strategy>;
+            #[allow(deprecated)] // We use "deprecated" testing strategies in here.
             type ASO<T> = ArcSwapAny<Option<Arc<T>>, $strategy>;
 
             /// Similar to the one in doc tests of the lib, but more times and more intensive (we
@@ -900,66 +903,6 @@ macro_rules! t {
                 .unwrap();
             }
 
-            /// Accessing the value inside ArcSwap with Guards (and checks for the reference
-            /// counts).
-            #[test]
-            fn load_cnt() {
-                let a = Arc::new(0);
-                let shared = AS::from(Arc::clone(&a));
-                // One in shared, one in a
-                assert_eq!(2, Arc::strong_count(&a));
-                let guard = shared.load();
-                assert_eq!(0, **guard);
-                // The guard doesn't have its own ref count now
-                assert_eq!(2, Arc::strong_count(&a));
-                let guard_2 = shared.load();
-                // Unlike with guard, this does not deadlock
-                shared.store(Arc::new(1));
-                // But now, each guard got a full Arc inside it
-                assert_eq!(3, Arc::strong_count(&a));
-                // And when we get rid of them, they disappear
-                drop(guard_2);
-                assert_eq!(2, Arc::strong_count(&a));
-                let _b = Arc::clone(&guard);
-                assert_eq!(3, Arc::strong_count(&a));
-                // We can drop the guard it came from
-                drop(guard);
-                assert_eq!(2, Arc::strong_count(&a));
-                let guard = shared.load();
-                assert_eq!(1, **guard);
-                drop(shared);
-                // We can still use the guard after the shared disappears
-                assert_eq!(1, **guard);
-                let ptr = Arc::clone(&guard);
-                // One in shared, one in guard
-                assert_eq!(2, Arc::strong_count(&ptr));
-                drop(guard);
-                assert_eq!(1, Arc::strong_count(&ptr));
-            }
-
-            /// There can be only limited amount of leases on one thread. Following ones are
-            /// created, but contain full Arcs.
-            #[test]
-            fn lease_overflow() {
-                let a = Arc::new(0);
-                let shared = AS::from(Arc::clone(&a));
-                assert_eq!(2, Arc::strong_count(&a));
-                let mut guards = (0..1000).map(|_| shared.load()).collect::<Vec<_>>();
-                let count = Arc::strong_count(&a);
-                assert!(count > 2);
-                let guard = shared.load();
-                assert_eq!(count + 1, Arc::strong_count(&a));
-                drop(guard);
-                assert_eq!(count, Arc::strong_count(&a));
-                // When we delete the first one, it didn't have an Arc in it, so the ref count
-                // doesn't drop
-                guards.swap_remove(0);
-                assert_eq!(count, Arc::strong_count(&a));
-                // But new one reuses now vacant the slot and doesn't create a new Arc
-                let _guard = shared.load();
-                assert_eq!(count, Arc::strong_count(&a));
-            }
-
             #[test]
             fn load_null() {
                 let shared = ASO::<usize>::default();
@@ -1237,5 +1180,75 @@ macro_rules! t {
 }
 
 t!(tests_default, DefaultStrategy);
-#[cfg(feature = "experimental-strategies")]
-t!(tests_helping, crate::strategy::experimental::Helping);
+#[cfg(feature = "internal-test-strategies")]
+#[allow(deprecated)]
+t!(
+    tests_full_slots,
+    crate::strategy::test_strategies::FillFastSlots
+);
+
+/// These tests assume details about the used strategy.
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    /// Accessing the value inside ArcSwap with Guards (and checks for the reference
+    /// counts).
+    #[test]
+    fn load_cnt() {
+        let a = Arc::new(0);
+        let shared = ArcSwap::from(Arc::clone(&a));
+        // One in shared, one in a
+        assert_eq!(2, Arc::strong_count(&a));
+        let guard = shared.load();
+        assert_eq!(0, **guard);
+        // The guard doesn't have its own ref count now
+        assert_eq!(2, Arc::strong_count(&a));
+        let guard_2 = shared.load();
+        // Unlike with guard, this does not deadlock
+        shared.store(Arc::new(1));
+        // But now, each guard got a full Arc inside it
+        assert_eq!(3, Arc::strong_count(&a));
+        // And when we get rid of them, they disappear
+        drop(guard_2);
+        assert_eq!(2, Arc::strong_count(&a));
+        let _b = Arc::clone(&guard);
+        assert_eq!(3, Arc::strong_count(&a));
+        // We can drop the guard it came from
+        drop(guard);
+        assert_eq!(2, Arc::strong_count(&a));
+        let guard = shared.load();
+        assert_eq!(1, **guard);
+        drop(shared);
+        // We can still use the guard after the shared disappears
+        assert_eq!(1, **guard);
+        let ptr = Arc::clone(&guard);
+        // One in shared, one in guard
+        assert_eq!(2, Arc::strong_count(&ptr));
+        drop(guard);
+        assert_eq!(1, Arc::strong_count(&ptr));
+    }
+
+    /// There can be only limited amount of leases on one thread. Following ones are
+    /// created, but contain full Arcs.
+    #[test]
+    fn lease_overflow() {
+        let a = Arc::new(0);
+        let shared = ArcSwap::from(Arc::clone(&a));
+        assert_eq!(2, Arc::strong_count(&a));
+        let mut guards = (0..1000).map(|_| shared.load()).collect::<Vec<_>>();
+        let count = Arc::strong_count(&a);
+        assert!(count > 2);
+        let guard = shared.load();
+        assert_eq!(count + 1, Arc::strong_count(&a));
+        drop(guard);
+        assert_eq!(count, Arc::strong_count(&a));
+        // When we delete the first one, it didn't have an Arc in it, so the ref count
+        // doesn't drop
+        guards.swap_remove(0);
+        assert_eq!(count, Arc::strong_count(&a));
+        // But new one reuses now vacant the slot and doesn't create a new Arc
+        let _guard = shared.load();
+        assert_eq!(count, Arc::strong_count(&a));
+    }
+}
diff --git a/src/strategy/experimental.rs b/src/strategy/experimental.rs
deleted file mode 100644
index 0a125f6..0000000
--- a/src/strategy/experimental.rs
+++ /dev/null
@@ -1,44 +0,0 @@
-//! Additional, experimental, strategies.
-//!
-//! Some more strategies are available here, if the `experimental-strategies` feature flag is
-//! enabled.
-//!
-//! Note that these strategies **are not part of the API stability guarantees** and may be changed,
-//! renamed or removed at any point in time. They are also not necessarily as mature. They are here
-//! to allow experimentation.
-//!
-//! If they are deemed good enough, they might mature and be moved from here to some better place.
-//! You're welcome to try them out, see if they fit better and send feedback about them. It is also
-//! possible to add some more strategies in here. But might not be as good idea to use in
-//! production just yet.
-
-use crate::gen_lock::PrivateUnsharded;
-pub use crate::gen_lock::Shard;
-use crate::strategy::gen_lock::GenLockStrategy;
-pub use crate::strategy::helping::Helping;
-
-/// A generation lock.
-///
-/// The generation lock is the fallback strategy of the default hybrid one. This variant has single
-/// shard inside each separate [`ArcSwap`], therefore they don't contend each other. As it doesn't
-/// support the borrowing mode and creates a fully-featured [`Arc`] behind the scenes, it is
-/// potentially slower than the default.
-///
-/// However, it is simpler and can turn out to be faster in certain corner case situations. It is
-/// suitable when:
-///
-/// * A lot of guards would be held or loading the full [`Arc`]s is desirable most of the times,
-///   therefore the advantage of the default strategy is not in play. In that case skipping the
-///   fast path that is nevertheless not used is beneficial.
-/// * The accesses are mostly across different [`ArcSwap`] instances.
-///
-/// Note that each instance has its own generation lock. This means that they are fully independent
-/// of each other, but is larger (each [`ArcSwap`] then is 4 words large instead of 1).
-///
-/// Readers are [wait-free], writers are not even [lock-free].
-///
-/// [`ArcSwap`]: crate::ArcSwap
-/// [`Arc`]: std::sync::Arc
-/// [lock-free]: https://en.wikipedia.org/wiki/Non-blocking_algorithm#Lock-freedom
-/// [wait-free]: https://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom
-pub type SimpleGenLock = GenLockStrategy<PrivateUnsharded>;
diff --git a/src/strategy/gen_lock.rs b/src/strategy/gen_lock.rs
deleted file mode 100644
index f07f3ec..0000000
--- a/src/strategy/gen_lock.rs
+++ /dev/null
@@ -1,76 +0,0 @@
-use std::sync::atomic::{AtomicPtr, Ordering};
-
-use crate::gen_lock::{self, GenLock, LockStorage};
-use crate::strategy::sealed::{CaS, InnerStrategy};
-use crate::RefCnt;
-
-#[derive(Copy, Clone, Default)]
-pub struct GenLockStrategy<L>(pub(crate) L);
-
-impl<T: RefCnt, L: LockStorage> InnerStrategy<T> for GenLockStrategy<L> {
-    type Protected = T;
-
-    #[inline]
-    unsafe fn load(&self, storage: &AtomicPtr<T::Base>) -> Self::Protected {
-        let lock = GenLock::new(&self.0);
-
-        let ptr = storage.load(Ordering::Acquire);
-        let result = T::from_ptr(ptr);
-        T::inc(&result);
-
-        drop(lock);
-
-        result
-    }
-
-    #[inline]
-    unsafe fn wait_for_readers(&self, _: *const T::Base, _: &AtomicPtr<T::Base>) {
-        gen_lock::wait_for_readers(&self.0);
-    }
-}
-
-impl<T: RefCnt, L: LockStorage> CaS<T> for GenLockStrategy<L> {
-    unsafe fn compare_and_swap<C: crate::as_raw::AsRaw<T::Base>>(
-        &self,
-        storage: &AtomicPtr<T::Base>,
-        current: C,
-        new: T,
-    ) -> Self::Protected {
-        let cur_ptr = current.as_raw();
-        let new = T::into_ptr(new);
-
-        // As noted above, this method has either semantics of load or of store. We don't know
-        // which ones upfront, so we need to implement safety measures for both.
-        let lock = GenLock::new(&self.0);
-
-        let swap_result =
-            storage.compare_exchange(cur_ptr, new, Ordering::SeqCst, Ordering::SeqCst);
-        let (previous_ptr, swapped) = match swap_result {
-            Ok(previous) => (previous, true),
-            Err(previous) => (previous, false),
-        };
-
-        // Drop it here, because:
-        // * We can't drop it before the compare_and_swap ‒ in such case, it could get recycled,
-        //   put into the pointer by another thread with a different value and create a fake
-        //   success (ABA).
-        // * We drop it before waiting for readers, because it could have been a Guard with a
-        //   generation lock. In such case, the caller doesn't have it any more and can't check if
-        //   it succeeded, but that's OK.
-        drop(current);
-
-        let previous = T::from_ptr(previous_ptr);
-
-        if swapped {
-            drop(lock);
-            gen_lock::wait_for_readers(&self.0);
-        } else {
-            // We didn't swap. Therefore, we need to bump the count on the old one and release the
-            // new one (blackhole it).
-            T::inc(&previous);
-            T::dec(new);
-        }
-
-        previous
-    }
-}
diff --git a/src/strategy/helping.rs b/src/strategy/helping.rs
deleted file mode 100644
index 2f23944..0000000
--- a/src/strategy/helping.rs
+++ /dev/null
@@ -1,831 +0,0 @@
-//! A strategy where the writer is helping the reader in case of collision.
-
-// # How does this work.
-//
-// This is a bit modified version of hazard pointers. The readers put the currently leased pointers
-// into debt slots. That means the reader owes one reference count. But as long as there's one
-// reference inside the storage, everything is fine.
-//
-// When a writer takes the pointer out, it walks all the old debt slots that got created before the
-// change and pays them all by incrementing the reference count as many times, and erases the debts
-// so the readers knows they need to decrement it after the fact.
-//
-// ## Reader, the fast path
-//
-// * Publish an active address ‒ the address we'll be loading stuff from.
-// * Picks a debt slot (note that we always keep at least one free; if we used the last one, we
-//   immediately pay the debt by bumping the reference count and release it and we never
-//   recursively call load). Mark it with a generation (this is to prevent ABA in writers).
-// * Load from the address.
-// * CaS-replace the generation with the address of the destination, reserving the debt. At this
-//   point, we are good to do our stuff.
-//
-// * Later, we pay it back by CaS-replacing it with the NO_DEPT.
-//
-// ## Writer, the non-colliding path
-//
-// * Replaces the pointer in the storage.
-// * The writer walks over all debts. It pays each debt that it is concerned with by bumping the
-//   reference and replacing the dept with NO_DEPT. The relevant reader will fail in the CaS
-//   (either because it finds NO_DEPT or other pointer in there) and knows the reference was
-//   bumped, so it needs to decrement it. Note that it is possible that someone also reuses the
-//   slot for the _same_ pointer. In that case that reader will set it to NO_DEPT and the newer
-//   reader will have a pre-paid debt, which is fine.
-//
-// ## The collision path
-//
-// The reservation of a slot is not atomic, therefore a writer can observe the reservation in
-// progress. But it doesn't want to wait for it to complete (it wants to be lock-free, which means
-// it needs to be able to resolve the situation on its own).
-//
-// The way it knows it is in progress of the reservation is by seeing a generation in there (it has
-// a distinct tag). In that case it'll try to:
-//
-// * First verify that the reservation is being done for the same address it modified, by reading
-//   and re-confirming the active_addr slot corresponding to the currently handled node. If it is
-//   for some other address, the writer doesn't have to be concerned and proceeds to the next slot.
-// * It does a full load. That is fine, because the writer must be on a different thread than the
-//   reader and therefore there is at least one free slot. Full load means paying the debt right
-//   away by incrementing the reference count.
-// * Then it tries to pass the already fully protected/paid pointer to the reader, by CaS-replacing
-//   the previously observed generation with the pointer. If it fails, it decrements the count
-//   again and retries on the same slot, as it might now contain a debt for the same pointer it is
-//   being concerned.
-// * The reader then finds the generation got replaced by a pointer (tagged with the right tag, so
-//   some other writer doesn't try to "pay" a debt that is actually a negative debt). It can then
-//   proceed to using that one instead of what it loaded (and not ever retry anything).
-//
-// ## ABA protection
-//
-// The generation as pre-reserving the slot allows the writer to make sure it is offering the
-// loaded pointer to the same reader and that the read value is new enough (and of the same type).
-//
-// This solves the general case, but there's also much less frequent but theoretical ABA problem
-// that could lead to UB, if left unsolved:
-//
-// * There is a collision on generation G.
-// * The writer loads a pointer, bumps it.
-// * In the meantime, all the 2^30 or 2^62 generations (depending on the usize width) generations
-//   wrap around.
-// * The writer stores the outdated and possibly different-typed pointer in there and the reader
-//   uses it.
-//
-// To mitigate that, every time the counter overflows we take the current node and un-assign it
-// from our current thread. We mark it as in "cooldown" and let it in there until there are no
-// writers messing with that node any more (if they are not on the node, they can't experience the
-// ABA problem on it). After that, we are allowed to use it again.
-//
-// This doesn't block the reader, it'll simply find *a* node next time ‒ this one, or possibly a
-// different (or new) one.
-//
-// # Orderings
-//
-// The debt linked list is the easy part:
-// * The head is used to synchronize the non-atomic content of it through trivial Acquire /
-//   Release. The non-atomic parts are never written to after publishing the node (publish
-//   pattern).
-// * The in_use field is used as a form of lock, to claim exclusive use / write privilege of some
-//   of the fields, also with Acquire / Release (mutex pattern).
-//
-// The actual debt juggling is a bit more interesting. We need to ensure that:
-//
-// * We don't start using anything before the debt is installed.
-// * We don't release the swapped-out pointer to the caller before all debts acquired from
-//   previously loading the old value are paid (and none are somewhere in process).
-//
-// We use SeqCst both on the change of the pointer and on the initial setting of the generation
-// into the debt slot. That way we can be sure both threads agree on what happened first.
-//
-// Then all things (with the small exception about the current address, see the comments inline)
-// happen on the same debt slot, therefore the time line is well established on that. Everything
-// that synchronizes anything is Acquire/Release as needed there.
-//
-// There's a gotcha in the destructor ‒ that one doesn't have the write with SeqCst on it. But at
-// that time other things must have assured that there's an exclusive access to the ArcSwap
-// (because drop takes &mut self) and that also assures nobody is currently loading anything ‒ all
-// the debts that are there must have existed before that exclusive ownership was acquired by
-// whoever drops it.
-
-use std::borrow::Borrow;
-use std::cell::Cell;
-use std::fmt::{Debug, Formatter, Result as FmtResult};
-use std::mem::{self, ManuallyDrop};
-use std::ops::Deref;
-use std::ptr;
-use std::sync::atomic::Ordering::*;
-use std::sync::atomic::{AtomicPtr, AtomicUsize};
-
-use super::sealed::{CaS, InnerStrategy, Protected};
-use crate::{AsRaw, RefCnt};
-
-const NODE_UNUSED: usize = 0;
-const NODE_USED: usize = 1;
-const NODE_COOLDOWN: usize = 2;
-
-/// A wrapper around a node pointer, to un-claim the node on thread shutdown.
-struct DebtHead {
-    // Node for this thread.
-    node: Cell<Option<&'static Node>>,
-
-    // The generation counter.
-    generation: Cell<usize>,
-
-    // Rotate the slots that are tried first.
-    slot_pos: Cell<usize>,
-}
-
-impl Drop for DebtHead {
-    fn drop(&mut self) {
-        if let Some(node) = self.node.get() {
-            // Release - syncing writes/ownership of this Node
-            // We put it to cooldown, because we implicitly reset the generation count by giving it
-            // to another thread. So just to be on the safe side and make sure no writers sees it
-            // across that reset.
-            assert_eq!(node.in_use.swap(NODE_COOLDOWN, Release), NODE_USED);
-        }
-    }
-}
-
-/// The value of pointer `3` should be pretty safe, for two reasons:
-///
-/// * It's an odd number, but the pointers we have are likely aligned at least to the word size,
-///   because the data at the end of the `Arc` has the counters.
-/// * It's in the very first page where NULL lives, so it's not mapped.
-///
-/// Note that we don't want to use 0 directly, because NULL is a valid pointer.
-const NO_DEBT: usize = 0b11;
-const REPLACEMENT_TAG: usize = 0b01;
-const GEN_TAG: usize = 0b10;
-const TAG_MASK: usize = 0b11;
-const DEBT_SLOT_CNT: usize = 8;
-
-thread_local! {
-    /// A debt node assigned to this thread.
-    static THREAD_HEAD: DebtHead = DebtHead {
-        node: Cell::new(None),
-        generation: Cell::new(0),
-        slot_pos: Cell::new(0),
-    };
-}
-
-/// A mostly copy of the Dept from elsewhere
-///
-/// For now, we keep a separate copy, both for simplicity and because we handle them differently
-/// based on the strategy used. We may either delete one copy eventually or unify them, but for the
-/// experiment, let's just copy-paste..
-struct Debt(AtomicUsize);
-
-impl Debt {
-    #[allow(clippy::new_ret_no_self)]
-    #[inline]
-    fn new(ptr: usize) -> (&'static Debt, bool, usize) {
-        Node::with_thread_head(|head| {
-            let node = head
-                .node
-                .get()
-                .expect("with_thread_head ensures we have one");
-            // Check it is in use by *us*
-            debug_assert_eq!(node.in_use.load(Relaxed), NODE_USED);
-
-            // Incrementing by 4 ensures we always have enough space for 2 bit of tags.
-            let gen = head.generation.get().wrapping_add(4);
-            debug_assert_eq!(gen & GEN_TAG, 0);
-            head.generation.set(gen);
-            let discard = gen == 0;
-            let gen = gen | GEN_TAG;
-            // We will sync by the write to the debt. But we also sync the value of the previous
-            // generation/released slot. That way we may re-confirm in the writer that the reader is
-            // not in between here and the compare_exchange below with a stale gen (eg. if we are in
-            // here, the re-confirm there will load the NO_DEPT and we are fine).
-            node.active_addr.store(ptr, Release);
-            let offset = head.slot_pos.get();
-
-            let len = node.slots.0.len();
-            for raw_i in 0..len {
-                let i = (raw_i + offset) % len;
-                let slot = &node.slots.0[i];
-
-                // We could do load and store separately as we are the only ones allowed to
-                // overwrite a NO_DEPT, but we actually need the SeqCst to be a read-write
-                // operation in here (we need both the release and acquire part of it).
-                if slot
-                    .0
-                    .compare_exchange(NO_DEBT, gen, SeqCst, Relaxed)
-                    .is_ok()
-                {
-                    let mut last = true;
-                    for raw_j in raw_i + 1..len {
-                        let j = (raw_j + offset) % len;
-                        if node.slots.0[j].0.load(Relaxed) == NO_DEBT {
-                            last = false;
-                            // We already discovered this one will be empty, so store the info for
-                            // next time.
-                            head.slot_pos.set(j);
-                            break;
-                        }
-                    }
-                    if last {
-                        // OK, store _this_ one for next time because we are going to free it up in
-                        // a moment.
-                        head.slot_pos.set(i);
-                    }
-                    if discard {
-                        // Cool, we have modified the gen, claimed the slot, that's all we need to
-                        // do while still holding ownership of the node. This is the right time to
-                        // put the node into cooldown and get rid of it. We'll pick a new one (or
-                        // this one, if the cooldown succeeds in between) next time.
-                        node.start_cooldown();
-                    }
-                    return (slot, last, gen);
-                }
-            }
-            unreachable!("Run out of slots in {:#?}", node);
-        })
-    }
-
-    /// Confirms the debt.
-    ///
-    /// Replaces the pre-debt mark (the address we are reading from) with the actual debt and
-    /// confirm it that way.
-    ///
-    /// If it got changed in the meantime, returns the (already protected) replacement value.
-    #[inline]
-    fn confirm(&self, gen: usize, ptr_addr: usize) -> Result<(), usize> {
-        // AcqRel -> Release to publish everything (do we need to publish anything?)/make sure
-        // sutff stays "inside" the lock.
-        // Acquire -> we read a potentially new address, need to bring the pointee in.
-        match self.0.compare_exchange(gen, ptr_addr, AcqRel, Acquire) {
-            Ok(_) => Ok(()),
-            Err(replacement) => {
-                // Nothing synchronized by this, we just clear the slot for future reuse. Release
-                // just to make sure, but should Relaxed be enough?
-                self.0.store(NO_DEBT, Release);
-                Err(replacement)
-            }
-        }
-    }
-
-    /// Tries to pay the given debt.
-    ///
-    /// If the debt is still there, for the given pointer, it is paid and `true` is returned. If it
-    /// is empty or if there's some other pointer, it is not paid and `false` is returned, meaning
-    /// the debt was paid previously by someone else.
-    ///
-    /// # Notes
-    ///
-    /// * It is possible that someone paid the debt and then someone else put a debt for the same
-    ///   pointer in there. This is fine, as we'll just pay the debt for that someone else.
-    /// * This relies on the fact that the same pointer must point to the same object and
-    ///   specifically to the same type ‒ the caller provides the type, it's destructor, etc.
-    /// * It also relies on the fact the same thing is not stuffed both inside an `Arc` and `Rc` or
-    ///   something like that, but that sounds like a reasonable assumption. Someone storing it
-    ///   through `ArcSwap<T>` and someone else with `ArcSwapOption<T>` will work.
-    /// * It is possible the slot got reused and currently contains the same pointer, but tagged as
-    ///   already protected. That is fine, as the raw value will not match and we won't change
-    ///   that.
-    #[inline]
-    fn pay<T: RefCnt>(&self, ptr: *const T::Base) -> bool {
-        self.0
-            // If we don't change anything because there's something else, Relaxed is fine (it's
-            // been already paid)
-            //
-            // The Release works as kind of Mutex. We make sure nothing from the debt-protected
-            // sections leaks below this point.
-            .compare_exchange(ptr as usize, NO_DEBT, Release, Relaxed)
-            .is_ok()
-    }
-
-    /// Pays all the debts on the given pointer.
-    #[inline]
-    fn pay_all<T, R>(ptr: *const T::Base, storage_addr: usize, replacement: R)
-    where
-        T: RefCnt,
-        R: Fn() -> T,
-    {
-        let val = unsafe { T::from_ptr(ptr) };
-        // Pre-pay one ref count that can be safely put into a debt slot to pay it.
-        T::inc(&val);
-
-        Node::traverse::<(), _>(|node| {
-            let _reservation = node.reserve_writer();
-            for slot in &node.slots.0 {
-                loop {
-                    match slot
-                        .0
-                        // We don't need to release anything in here. The writes to the reference
-                        // counts take care of themselves before they drop to 0 (they are all to
-                        // the same atomic, and we are doing the decrements later on). We need to
-                        // acquire any increments done by the other threads though and we need to
-                        // acquire the active_addr
-                        .compare_exchange(ptr as usize, NO_DEBT, Acquire, Acquire)
-                    {
-                        Ok(_) => {
-                            // We just paid the debt by the pre-paid ref count from before.
-                            // Pre-pay one more, for another future slot
-                            T::inc(&val);
-                            break;
-                        }
-                        Err(gen) if gen & TAG_MASK == GEN_TAG => {
-                            // The reader is trying to claim the slot right now. Let's have a look
-                            // at the address where the data should come from and help the reader
-                            // out.
-                            let active_addr = node.active_addr.load(Acquire);
-                            if active_addr != storage_addr {
-                                // Re-confirm the gen matches. That way with the above active_addr
-                                // load and Acquire we make sure the active_addr is not newer than
-                                // the gen and therefore we are not missing a place where we need
-                                // to help (eg. that Acquire makes sure the gen catches up with
-                                // it).
-                                if slot.0.load(Relaxed) == gen {
-                                    // OK, it is really doing something with some other ArcSwap,
-                                    // not interested in that.
-                                    break;
-                                } else {
-                                    // The value changed and we are not sure how useful the
-                                    // active_addr is for us. So retry.
-                                    continue;
-                                }
-                            }
-                            // Get a replacement value and try to donate it.
-                            let replacement = replacement();
-                            let replace_addr = T::as_ptr(&replacement) as usize;
-                            assert_eq!(replace_addr & TAG_MASK, 0, "Not enough space for tags");
-                            let replace_addr = replace_addr | REPLACEMENT_TAG;
-                            if slot
-                                .0
-                                // Release the value we send there. TODO: Do we need the Acquire
-                                // there?
-                                //
-                                // Relaxed on failure: Basically, nothing happened anywhere, all
-                                // data stayed with us and we are going to retry this loop from
-                                // scratch.
-                                .compare_exchange_weak(gen, replace_addr, AcqRel, Relaxed)
-                                .is_ok()
-                            {
-                                // OK, it went it
-                                T::into_ptr(replacement);
-                                break;
-                            }
-                            // else -> replacement is dropped.
-                            // Also, loop once more because the current slot did *not* get
-                            // resolved. Retry and see if the reader already got what it wanted or
-                            // try creating a new replacement.
-                        }
-                        // OK, not interested in this slot. Either no debt or different one.
-                        Err(_) => break,
-                    }
-                }
-            }
-            None
-        });
-        // Implicit dec by dropping val in here, pair for the above T::inc
-    }
-}
-
-impl Default for Debt {
-    fn default() -> Self {
-        Debt(AtomicUsize::new(NO_DEBT))
-    }
-}
-
-#[repr(align(64))]
-#[derive(Default)]
-struct Slots([Debt; DEBT_SLOT_CNT]);
-
-impl Debug for Slots {
-    fn fmt(&self, fmt: &mut Formatter) -> FmtResult {
-        fmt.debug_list()
-            .entries(self.0.iter().map(|s| s.0.load(Relaxed) as *const u8))
-            .finish()
-    }
-}
-
-struct NodeReservation<'a>(&'a Node);
-
-impl Drop for NodeReservation<'_> {
-    fn drop(&mut self) {
-        self.0.active_writers.fetch_sub(1, Release);
-    }
-}
-
-/// One thread-local node for debts.
-#[repr(C)]
-struct Node {
-    slots: Slots,
-    next: Option<&'static Node>,
-    // This would be enough with AtomicU8, but that doesn't exist on 1.31 yet.
-    in_use: AtomicUsize,
-    active_addr: AtomicUsize,
-    /// How many writers are currently interested in this one.
-    ///
-    /// See the ABA protection at the top.
-    active_writers: AtomicUsize,
-}
-
-/// The head of the debt chain.
-static DEBT_HEAD: AtomicPtr<Node> = AtomicPtr::new(ptr::null_mut());
-
-impl Default for Node {
-    fn default() -> Self {
-        Node {
-            next: None,
-            in_use: AtomicUsize::new(NODE_USED),
-            slots: Default::default(),
-            active_addr: AtomicUsize::new(0),
-            active_writers: AtomicUsize::new(0),
-        }
-    }
-}
-
-impl Node {
-    /// Goes through the debt linked list.
-    ///
-    /// This traverses the linked list, calling the closure on each node. If the closure returns
-    /// `Some`, it terminates with that value early, otherwise it runs to the end.
-    fn traverse<R, F: FnMut(&'static Node) -> Option<R>>(mut f: F) -> Option<R> {
-        // Acquire ‒ we want to make sure we read the correct version of data at the end of the
-        // pointer. Any write to the DEBT_HEAD is with Release.
-        //
-        // Note that the other pointers in the chain never change and are *ordinary* pointers. The
-        // whole linked list is synchronized through the head.
-        let mut current = unsafe { DEBT_HEAD.load(Acquire).as_ref() };
-        while let Some(node) = current {
-            let result = f(node);
-            if result.is_some() {
-                return result;
-            }
-            current = node.next;
-        }
-        None
-    }
-
-    /// Put the current thread node into cooldown
-    fn start_cooldown(&self) {
-        assert_eq!(NODE_USED, self.in_use.swap(NODE_COOLDOWN, Release));
-        THREAD_HEAD.with(|head| assert!(ptr::eq(head.node.take().unwrap(), self)));
-    }
-
-    /// Perform a cooldown if the node is ready.
-    ///
-    /// See the ABA protection at the top.
-    fn check_cooldown(&self) {
-        // Cooldown succeeded if it is not being looked at by any writers.
-        // This value may be a little bit outdated. But that is fine, as we get an update at least
-        // every load operation (there's a SeqCst read-write operation on both paths, for readers
-        // this can be only one generation stale which is fine, and for writers, if it doesn't
-        // enter the write, because it is not interested, we don't worry about not seeing that
-        // writer).
-        if self.active_writers.load(Relaxed) == 0 {
-            // Relaxed: we don't synchronize anything by this. It'll get used in real just in a
-            // moment. If it doesn't succeed, then this one wasn't doing a cooldown and that's OK.
-            let _ = self
-                .in_use
-                .compare_exchange(NODE_COOLDOWN, NODE_UNUSED, Relaxed, Relaxed);
-        }
-    }
-
-    /// Mark this node that a writer is currently playing with it.
-    #[inline]
-    fn reserve_writer(&self) -> NodeReservation {
-        self.active_writers.fetch_add(1, Acquire);
-        NodeReservation(self)
-    }
-
-    #[inline(never)]
-    fn get() -> &'static Self {
-        // Try to find an unused one in the chain and reuse it.
-        Self::traverse(|node| {
-            node.check_cooldown();
-            if node
-                .in_use
-                // We claim a unique control over the generation and the right to write to slots if
-                // they are NO_DEPT
-                .compare_exchange(NODE_UNUSED, NODE_USED, Acquire, Relaxed)
-                .is_ok()
-            {
-                Some(node)
-            } else {
-                None
-            }
-        })
-        // If that didn't work, create a new one and prepend to the list.
-        .unwrap_or_else(|| {
-            let node = Box::leak(Box::new(Node::default()));
-            // Not shared between threads yet, so ordinary write would be fine too.
-            node.in_use.store(NODE_USED, Relaxed);
-            // We don't want to read any data in addition to the head, Relaxed is fine
-            // here.
-            //
-            // We do need to release the data to others, but for that, we acquire in the
-            // compare_exchange below.
-            let mut head = DEBT_HEAD.load(Relaxed);
-            loop {
-                node.next = unsafe { head.as_ref() };
-                if let Err(old) = DEBT_HEAD.compare_exchange_weak(
-                    head, node,
-                    // We need to release *the whole chain* here. For that, we need to
-                    // acquire it first.
-                    AcqRel, Relaxed, // Nothing changed, go next round of the loop.
-                ) {
-                    head = old;
-                } else {
-                    return node;
-                }
-            }
-        })
-    }
-
-    #[inline]
-    fn with_thread_head<R, F: FnOnce(&DebtHead) -> R>(f: F) -> R {
-        THREAD_HEAD
-            // FIXME: Can this actually fail sometime? Can people use us during some kind of mutual
-            // thread-local destruction thing?
-            .with(|head| {
-                if head.node.get().is_none() {
-                    head.node.set(Some(Node::get()));
-                }
-                f(head)
-            })
-    }
-}
-
-impl Debug for Node {
-    fn fmt(&self, fmt: &mut Formatter) -> FmtResult {
-        fmt.debug_struct("Node")
-            .field("in_use", &self.in_use.load(Relaxed))
-            .field("next", &self.next)
-            .field(
-                "active_addr",
-                &(self.active_addr.load(Relaxed) as *const u8),
-            )
-            .field("active_writers", &self.active_writers)
-            .field("slots", &self.slots)
-            .finish()
-    }
-}
-
-pub struct Protection<T: RefCnt> {
-    debt: Option<&'static Debt>,
-    ptr: ManuallyDrop<T>,
-}
-
-impl<T: RefCnt> Protection<T> {
-    #[inline]
-    unsafe fn new(ptr: *const T::Base, debt: Option<&'static Debt>) -> Self {
-        Self {
-            debt,
-            ptr: ManuallyDrop::new(T::from_ptr(ptr)),
-        }
-    }
-}
-
-impl<T: RefCnt> Drop for Protection<T> {
-    #[inline]
-    fn drop(&mut self) {
-        match self.debt.take() {
-            // We have our own copy of Arc, so we don't need a protection. Do nothing (but release
-            // the Arc below).
-            None => (),
-            // If we owed something, just return the debt. We don't have a pointer owned, so
-            // nothing to release.
-            Some(debt) => {
-                let ptr = T::as_ptr(&self.ptr);
-                if debt.pay::<T>(ptr) {
-                    return;
-                }
-                // But if the debt was already paid for us, we need to release the pointer, as we
-                // were effectively already in the Unprotected mode.
-            }
-        }
-        // Equivalent to T::dec(ptr)
-        unsafe { ManuallyDrop::drop(&mut self.ptr) };
-    }
-}
-
-impl<T: RefCnt> Protected<T> for Protection<T> {
-    #[inline]
-    fn from_inner(ptr: T) -> Self {
-        Self {
-            debt: None,
-            ptr: ManuallyDrop::new(ptr),
-        }
-    }
-
-    #[inline]
-    fn into_inner(mut self) -> T {
-        // Drop any debt and release any lock held by the given guard and return a
-        // full-featured value that even can outlive the ArcSwap it originated from.
-        match self.debt.take() {
-            None => (), // We have a fully loaded ref-counted pointer.
-            Some(debt) => {
-                let ptr = T::inc(&self.ptr);
-                if !debt.pay::<T>(ptr) {
-                    unsafe { T::dec(ptr) };
-                }
-            }
-        }
-
-        // The ptr::read & forget is something like a cheating move. We can't move it out, because
-        // we have a destructor and Rust doesn't allow us to do that.
-        // (it's basically ManuallyDrop::take, but that one is only since 1.42.0 and that's too new
-        // for us).
-        let inner = unsafe { ptr::read(self.ptr.deref()) };
-        mem::forget(self);
-        inner
-    }
-}
-
-impl<T: RefCnt> Borrow<T> for Protection<T> {
-    #[inline]
-    fn borrow(&self) -> &T {
-        &self.ptr
-    }
-}
-
-/// A strategy where a writer helps the reader in case there's a collision.
-/// Which makes it possible for the writer to move forward. We don't use the generation lock here,
-/// so ever writers are lock-free.
-///
-/// This is inspired (but not an exact copy) of
-/// <https://pvk.ca/Blog/2020/07/07/flatter-wait-free-hazard-pointers/>. The debts are mostly
-/// copies of the ones used by the hybrid strategy, but modified a bit. Just like in the hybrid
-/// strategy, in case the slots run out or when the writer updates the value, the debts are paid by
-/// incrementing the ref count (which is a little slower, but still wait-free/lock-free and still
-/// in order of nanoseconds).
-///
-/// The strategy is:
-///
-/// * Wait-free on readers (except every usize::MAX/4 accesses, when it is only lock-free).
-/// * Lock free on writers.
-/// * Precise (data is dropped as soon as possible).
-#[derive(Copy, Clone, Debug, Default)]
-pub struct Helping;
-
-impl<T: RefCnt> InnerStrategy<T> for Helping {
-    type Protected = Protection<T>;
-
-    #[inline]
-    unsafe fn load(&self, storage: &AtomicPtr<T::Base>) -> Self::Protected {
-        // First, we claim a debt slot and store the address of the atomic pointer there, so the
-        // writer can optionally help us out with loading and protecting something.
-        let (debt, last, gen) = Debt::new(storage as *const _ as usize);
-        // We already synchronized the start of the sequence by SeqCst in the Debt::new vs swap on
-        // the pointer. We just need to make sure to bring the pointee in (this can be newer than
-        // what we got in the Debt)
-        let candidate = storage.load(Acquire);
-        // TODO: We should check this much sooner, possibly when the pointer gets into the storage.
-        assert_eq!(
-            candidate as usize & TAG_MASK,
-            0,
-            "Not enough space for tags"
-        );
-
-        let ptr_addr = candidate as usize;
-        // Try to replace the debt with our candidate.
-        match debt.confirm(gen, ptr_addr) {
-            Ok(()) => {
-                // The fast path -> we got the debt confirmed alright.
-                let result = Protection::new(candidate, Some(debt));
-                if last {
-                    // If we got the last debt slot, we must pay it back before proceeding, so
-                    // others can get a slot too.
-                    Protection::from_inner(Protection::into_inner(result))
-                } else {
-                    result
-                }
-            }
-            Err(replacement) => {
-                // We got a (possibly) different pointer out. But that one is already protected and
-                // the slot is paid back. It's not possible someone could have changed our gen to
-                // something but a replacement.
-                debug_assert_eq!(
-                    replacement & TAG_MASK,
-                    REPLACEMENT_TAG,
-                    "Missing tag on replacement",
-                );
-                debug_assert_eq!(NO_DEBT, debt.0.load(Relaxed));
-                let replacement = replacement & !TAG_MASK; // Remove the tag
-                Protection::new(replacement as *const _, None)
-            }
-        }
-    }
-
-    unsafe fn wait_for_readers(&self, old: *const T::Base, storage: &AtomicPtr<T::Base>) {
-        // The pay_all may need to provide fresh replacement values if someone else is loading from
-        // this particular storage. We do so by the exact same way, by `load` ‒ it's OK, a writer
-        // does not hold a slot and the reader doesn't recurse back into writer, so we won't run
-        // out of slots.
-        let replacement = || Protection::into_inner(self.load(storage));
-        Debt::pay_all::<T, _>(old, storage as *const _ as usize, replacement);
-    }
-}
-
-impl<T: RefCnt> CaS<T> for Helping {
-    unsafe fn compare_and_swap<C: AsRaw<T::Base>>(
-        &self,
-        storage: &AtomicPtr<T::Base>,
-        current: C,
-        new: T,
-    ) -> Self::Protected {
-        loop {
-            let old = <Self as InnerStrategy<T>>::load(self, storage);
-            // Observation of their inequality is enough to make a verdict
-            if old.ptr.deref().as_raw() != current.as_raw() {
-                return old;
-            }
-            // If they are still equal, put the new one in.
-            let new_raw = T::as_ptr(&new);
-            if storage
-                .compare_exchange_weak(current.as_raw(), new_raw, SeqCst, Relaxed)
-                .is_ok()
-            {
-                // We successfully put the new value in. The ref count went in there too.
-                T::into_ptr(new);
-                <Self as InnerStrategy<T>>::wait_for_readers(
-                    self,
-                    old.ptr.deref().as_raw(),
-                    storage,
-                );
-                // We just got one ref count out of the storage and we have one in old. We don't
-                // need two.
-                T::dec(old.ptr.deref().as_raw());
-                return old;
-            }
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use std::sync::Arc;
-
-    use super::*;
-
-    impl Node {
-        fn is_empty(&self) -> bool {
-            self.slots.0.iter().all(|d| d.0.load(Relaxed) == NO_DEBT)
-        }
-
-        fn get_thread() -> &'static Self {
-            Self::with_thread_head(|h| h.node.get().unwrap())
-        }
-    }
-
-    /// A freshly acquired thread local node is empty.
-    #[test]
-    fn new_empty() {
-        assert!(Node::get_thread().is_empty());
-    }
-
-    /// Just plain load, no contention or whatever.
-    #[test]
-    fn load_bare() {
-        // Check everything after loading stuff
-        type T = Arc<usize>;
-        let a = T::new(42);
-        let s = AtomicPtr::new(T::as_ptr(&a) as *mut usize);
-        let p = unsafe { <Helping as InnerStrategy<T>>::load(&Helping, &s) };
-        let pa: &Arc<usize> = p.borrow();
-        assert_eq!(42, **pa);
-        assert_eq!(T::as_ptr(&a), p.ptr.deref().deref());
-        assert_eq!(s.load(Relaxed) as usize, p.debt.unwrap().0.load(Relaxed));
-        assert!(!Node::get_thread().is_empty());
-        assert_eq!(1, Arc::strong_count(&a));
-        // Now we drop the protection and everything shall clean up.
-        drop(p);
-        assert!(Node::get_thread().is_empty());
-    }
-
-    /// A smoke test exercising the generation overflow thing.
-    #[test]
-    fn gen_overflow() {
-        type T = Arc<usize>;
-        let a = T::new(42);
-        let s = AtomicPtr::new(T::as_ptr(&a) as *mut usize);
-        let p = unsafe { <Helping as InnerStrategy<T>>::load(&Helping, &s) };
-        // Force it to overflow during the next one.
-        THREAD_HEAD.with(|h| h.generation.set(usize::MAX / 4 * 4));
-        drop(p);
-        // When it overflows, the node is released back to the pool (no good way to check it got
-        // into cooldown, because some other thread might claim it in between).
-        let p = unsafe { <Helping as InnerStrategy<T>>::load(&Helping, &s) };
-        THREAD_HEAD.with(|h| assert!(h.node.get().is_none()));
-        drop(p);
-        // But next round will pick some node again.
-        let p = unsafe { <Helping as InnerStrategy<T>>::load(&Helping, &s) };
-        THREAD_HEAD.with(|h| assert!(h.node.get().is_some()));
-        drop(p);
-    }
-
-    /// Check some alignment assumptions.
-    ///
-    /// Note that we also check them at runtime, in case someone doesn't run the tests.
-    #[test]
-    fn alignments() {
-        // We don't need _exactly_ this, but that will ensure that the pointer to data is also
-        // aligned to that. Or at least always unaligned to that.
-        assert!(mem::align_of::<Arc<u8>>() >= 4);
-        assert_eq!(Arc::as_ptr(&Arc::new(0u8)) as usize % 4, 0);
-        assert!(mem::align_of::<AtomicUsize>() >= 4);
-    }
-}
diff --git a/src/strategy/hybrid.rs b/src/strategy/hybrid.rs
index cacb844..bf79edf 100644
--- a/src/strategy/hybrid.rs
+++ b/src/strategy/hybrid.rs
@@ -1,13 +1,27 @@
+//! A hybrid strategy.
+//!
+//! This is based on debts ‒ an Arc may owe a reference, but it is marked in the debt. It is either
+//! put back (by stopping using it), or if the pointer is replaced, the writer bumps the reference
+//! count and removes the debt.
+//!
+//! The strategy uses two different slots for the debts. The first ones are faster, but fallible.
+//! If they fail (either because there's interference from a writer at the same time, or because
+//! they are full), the secondary one that is slower, but always succeeds, is used. In the latter
+//! case, the reference is bumped and this secondary debt slot is released, so it is available for
+//! further loads.
+//!
+//! See the [crate::debt] module for the actual slot manipulation. Here we just wrap them into the
+//! strategy.
+
 use std::borrow::Borrow;
 use std::mem::{self, ManuallyDrop};
 use std::ops::Deref;
 use std::ptr;
-use std::sync::atomic::{AtomicPtr, Ordering};
+use std::sync::atomic::AtomicPtr;
+use std::sync::atomic::Ordering::*;
 
-use super::gen_lock::GenLockStrategy;
 use super::sealed::{CaS, InnerStrategy, Protected};
-use crate::debt::Debt;
-use crate::gen_lock::{GenLock, LockStorage};
+use crate::debt::{Debt, LocalNode};
 use crate::ref_cnt::RefCnt;
 
 pub struct HybridProtection<T: RefCnt> {
@@ -16,21 +30,22 @@ pub struct HybridProtection<T: RefCnt> {
 }
 
 impl<T: RefCnt> HybridProtection<T> {
-    #[inline]
-    unsafe fn new(ptr: *const T::Base, debt: Option<&'static Debt>) -> Self {
+    pub(super) unsafe fn new(ptr: *const T::Base, debt: Option<&'static Debt>) -> Self {
         Self {
             debt,
             ptr: ManuallyDrop::new(T::from_ptr(ptr)),
         }
     }
+
+    /// Try getting a dept into a fast slot.
     #[inline]
-    fn attempt(storage: &AtomicPtr<T::Base>) -> Option<Self> {
+    fn attempt(node: &LocalNode, storage: &AtomicPtr<T::Base>) -> Option<Self> {
         // Relaxed is good enough here, see the Acquire below
-        let ptr = storage.load(Ordering::Relaxed);
+        let ptr = storage.load(Relaxed);
         // Try to get a debt slot. If not possible, fail.
-        let debt = Debt::new(ptr as usize)?;
+        let debt = node.new_fast(ptr as usize)?;
 
-        let confirm = storage.load(Ordering::Acquire);
+        let confirm = storage.load(Acquire);
         if ptr == confirm {
             // Successfully got a debt
             Some(unsafe { Self::new(ptr, Some(debt)) })
@@ -44,6 +59,41 @@ impl<T: RefCnt> HybridProtection<T> {
             Some(unsafe { Self::new(ptr, None) })
         }
     }
+
+    /// Get a debt slot using the slower but always successful mechanism.
+    fn fallback(node: &LocalNode, storage: &AtomicPtr<T::Base>) -> Self {
+        // First, we claim a debt slot and store the address of the atomic pointer there, so the
+        // writer can optionally help us out with loading and protecting something.
+        let gen = node.new_helping(storage as *const _ as usize);
+        // We already synchronized the start of the sequence by SeqCst in the new_helping vs swap on
+        // the pointer. We just need to make sure to bring the pointee in (this can be newer than
+        // what we got in the Debt)
+        let candidate = storage.load(Acquire);
+
+        // Try to replace the debt with our candidate. If it works, we get the debt slot to use. If
+        // not, we get a replacement value, already protected and a debt to take care of.
+        match node.confirm_helping(gen, candidate as usize) {
+            Ok(debt) => {
+                // The fast path -> we got the debt confirmed alright.
+                Self::from_inner(unsafe { Self::new(candidate, Some(debt)).into_inner() })
+            }
+            Err((unused_debt, replacement)) => {
+                // The debt is on the candidate we provided and it is unused, we so we just pay it
+                // back right away.
+                if !unused_debt.pay::<T>(candidate) {
+                    unsafe { T::dec(candidate) };
+                }
+                // We got a (possibly) different pointer out. But that one is already protected and
+                // the slot is paid back.
+                unsafe { Self::new(replacement as *mut _, None) }
+            }
+        }
+    }
+
+    #[inline]
+    fn as_ptr(&self) -> *const T::Base {
+        T::as_ptr(self.ptr.deref())
+    }
 }
 
 impl<T: RefCnt> Drop for HybridProtection<T> {
@@ -107,94 +157,76 @@ impl<T: RefCnt> Borrow<T> for HybridProtection<T> {
     }
 }
 
+pub trait Config {
+    // Mostly for testing, way to disable the fast slo
+    const USE_FAST: bool;
+}
+
 #[derive(Clone, Default)]
-pub struct HybridStrategy<F> {
-    fallback: F,
+pub struct DefaultConfig;
+
+impl Config for DefaultConfig {
+    const USE_FAST: bool = true;
 }
 
-impl<T, F> InnerStrategy<T> for HybridStrategy<F>
+#[derive(Clone, Default)]
+pub struct HybridStrategy<Cfg> {
+    _config: Cfg,
+}
+
+impl<T, Cfg> InnerStrategy<T> for HybridStrategy<Cfg>
 where
     T: RefCnt,
-    F: InnerStrategy<T, Protected = T>,
+    Cfg: Config,
 {
     type Protected = HybridProtection<T>;
     unsafe fn load(&self, storage: &AtomicPtr<T::Base>) -> Self::Protected {
-        HybridProtection::attempt(storage).unwrap_or_else(|| {
-            let loaded = self.fallback.load(storage);
-            HybridProtection {
-                debt: None,
-                ptr: ManuallyDrop::new(loaded),
-            }
+        LocalNode::with(|node| {
+            let fast = if Cfg::USE_FAST {
+                HybridProtection::attempt(node, storage)
+            } else {
+                None
+            };
+            fast.unwrap_or_else(|| HybridProtection::fallback(node, storage))
         })
     }
     unsafe fn wait_for_readers(&self, old: *const T::Base, storage: &AtomicPtr<T::Base>) {
-        self.fallback.wait_for_readers(old, storage);
-        Debt::pay_all::<T>(old);
+        // The pay_all may need to provide fresh replacement values if someone else is loading from
+        // this particular storage. We do so by the exact same way, by `load` ‒ it's OK, a writer
+        // does not hold a slot and the reader doesn't recurse back into writer, so we won't run
+        // out of slots.
+        let replacement = || self.load(storage).into_inner();
+        Debt::pay_all::<T, _>(old, storage as *const _ as usize, replacement);
     }
 }
 
-impl<T: RefCnt, L: LockStorage> CaS<T> for HybridStrategy<GenLockStrategy<L>> {
+impl<T: RefCnt, Cfg: Config> CaS<T> for HybridStrategy<Cfg> {
     unsafe fn compare_and_swap<C: crate::as_raw::AsRaw<T::Base>>(
         &self,
         storage: &AtomicPtr<T::Base>,
         current: C,
         new: T,
     ) -> Self::Protected {
-        let cur_ptr = current.as_raw();
-        let new = T::into_ptr(new);
-
-        // As noted above, this method has either semantics of load or of store. We don't know
-        // which ones upfront, so we need to implement safety measures for both.
-        let gen = GenLock::new(&self.fallback.0);
-
-        let swap_result =
-            storage.compare_exchange(cur_ptr, new, Ordering::SeqCst, Ordering::SeqCst);
-        let (previous_ptr, swapped) = match swap_result {
-            Ok(previous) => (previous, true),
-            Err(previous) => (previous, false),
-        };
-
-        // Drop it here, because:
-        // * We can't drop it before the compare_and_swap ‒ in such case, it could get recycled,
-        //   put into the pointer by another thread with a different value and create a fake
-        //   success (ABA).
-        // * We drop it before waiting for readers, because it could have been a Guard with a
-        //   generation lock. In such case, the caller doesn't have it any more and can't check if
-        //   it succeeded, but that's OK.
-        drop(current);
-
-        let debt = if swapped {
-            // New went in, previous out, but their ref counts are correct. So nothing to do here.
-            None
-        } else {
-            // Previous is a new copy of what is inside (and it stays there as well), so bump its
-            // ref count. New is thrown away so dec its ref count (but do it outside of the
-            // gen-lock).
-            //
-            // We try to do that by registering a debt and only if that fails by actually bumping
-            // the ref.
-            let debt = Debt::new(previous_ptr as usize);
-            if debt.is_none() {
-                let previous = T::from_ptr(previous_ptr);
-                T::inc(&previous);
-                T::into_ptr(previous);
+        loop {
+            let old = <Self as InnerStrategy<T>>::load(self, storage);
+            // Observation of their inequality is enough to make a verdict
+            if old.as_ptr() != current.as_raw() {
+                return old;
+            }
+            // If they are still equal, put the new one in.
+            let new_raw = T::as_ptr(&new);
+            if storage
+                .compare_exchange_weak(current.as_raw(), new_raw, SeqCst, Relaxed)
+                .is_ok()
+            {
+                // We successfully put the new value in. The ref count went in there too.
+                T::into_ptr(new);
+                <Self as InnerStrategy<T>>::wait_for_readers(self, old.as_ptr(), storage);
+                // We just got one ref count out of the storage and we have one in old. We don't
+                // need two.
+                T::dec(old.as_ptr());
+                return old;
             }
-            debt
-        };
-
-        drop(gen);
-
-        if swapped {
-            // We swapped. Before releasing the (possibly only) ref count of previous to user, wait
-            // for all readers to make sure there are no more untracked copies of it.
-            //
-            // Why is rustc confused about self.wait_for_readers???
-            InnerStrategy::<T>::wait_for_readers(self, previous_ptr, storage);
-        } else {
-            // We didn't swap, so new is black-holed.
-            T::dec(new);
         }
-
-        HybridProtection::new(previous_ptr, debt)
     }
 }
diff --git a/src/strategy/mod.rs b/src/strategy/mod.rs
index cb92b07..2a25a04 100644
--- a/src/strategy/mod.rs
+++ b/src/strategy/mod.rs
@@ -17,7 +17,6 @@
 //! Currently, we have these strategies:
 //!
 //! * [`DefaultStrategy`] (this one is used implicitly)
-//! * [`IndependentStrategy`]
 //! * [`RwLock<()>`][std::sync::RwLock]
 //!
 //! # Testing
@@ -34,59 +33,64 @@
 //!
 //! *This is not meant to be used in production code*.
 //!
-//! # Experimental strategies
-//!
-//! There are some more strategies inside the [`experimental`] module. Note that these **are not**
-//! subject to the API stability guarantees and can be changed, renamed or removed at any time.
-//!
-//! They are available only with the `experimental-strategies` feature.
-//!
 //! [`ArcSwap`]: crate::ArcSwap
 //! [`load`]: crate::ArcSwapAny::load
 
 use std::borrow::Borrow;
 use std::sync::atomic::AtomicPtr;
 
-use crate::gen_lock::{Global, PrivateUnsharded};
 use crate::ref_cnt::RefCnt;
 
-#[cfg(feature = "experimental-strategies")]
-pub mod experimental;
-mod gen_lock;
-mod helping;
 mod hybrid;
 mod rw_lock;
+// Do not use from outside of the crate.
+#[cfg(feature = "internal-test-strategies")]
+#[doc(hidden)]
+pub mod test_strategies;
 
-use self::gen_lock::GenLockStrategy;
-use self::hybrid::HybridStrategy;
+use self::hybrid::{DefaultConfig, HybridStrategy};
 
 /// The default strategy.
 ///
 /// It is used by the type aliases [`ArcSwap`][crate::ArcSwap] and
 /// [`ArcSwapOption`][crate::ArcSwapOption]. Only the other strategies need to be used explicitly.
 ///
-/// It is optimized for read heavy situations. The readers are wait-free (with the exception of the
-/// first [`load`] in each thread, which is merely lock-free), writers are not lock-free. The
-/// reclamation is tight ‒ the resource is released as soon as possible.
+/// # Performance characteristics
+///
+/// * It is optimized for read-heavy situations, with possibly many concurrent read accesses from
+///   multiple threads. Readers don't contend each other at all.
+/// * Readers are wait-free (with the exception of at most once in `usize::MAX / 4` accesses, which
+///   is only lock-free).
+/// * Writers are lock-free.
+/// * Reclamation is exact ‒ the resource is released as soon as possible (works like RAII, not
+///   like a traditional garbage collector; can contain non-`'static` data).
+///
+/// Each thread has a limited number of fast slots (currently 8, but the exact number is not
+/// guaranteed). If it holds at most that many [`Guard`]s at once, acquiring them is fast. Once
+/// these slots are used up (by holding to these many [`Guard`]s), acquiring more of them will be
+/// slightly slower, but still wait-free.
 ///
-/// Each thread has a limited number of "fast" slots. If a thread holds less than this number,
-/// loading is fast and does not suffer from contention (unlike using [`RwLock`][std::sync::RwLock]
-/// or even updating reference counts on the [`Arc`][std::sync::Arc]). In other words, no matter
-/// how many threads concurrently read, they should not be affecting performance of each other in
-/// any way.
+/// If you expect to hold a lot of "handles" to the data around, or hold onto it for a long time,
+/// you may want to prefer the [`load_full`][crate::ArcSwapAny::load_full] method.
 ///
-/// If the slots run out (the thread holds too many [`Guard`][crate::Guard], the loading becomes
-/// slower (because the reference counts need to be updated).
+/// The speed of the fast slots is in the ballpark of locking an *uncontented* mutex. The advantage
+/// over the mutex is the stability of speed in the face of contention from other threads ‒ while
+/// the performance of mutex goes rapidly down, the slowdown of running out of held slots or heavy
+/// concurrent writer thread in the area of single-digit multiples.
 ///
-/// Currently, the implementation is a hybrid between stripped-down hazard pointers and one-sided
-/// spin lock. The hazard pointers are the primary fast path. The spin locks are shared between all
-/// instances, therefore the fallbacks may influence other instances.
+/// The ballpark benchmark figures (my older computer) are around these, but you're welcome to run
+/// the benchmarks in the git repository or write your own.
 ///
-/// However, the actual implementation can change in future versions for something else with
-/// similar or better properties.
+/// * Load (both uncontented and contented by other loads): ~30ns
+/// * `load_full`: ~50ns uncontented, goes up a bit with other `load_full` in other threads on the
+///   same `Arc` value (~80-100ns).
+/// * Loads after running out of the slots ‒ about 10-20ns slower than `load_full`.
+/// * Stores: Dependent on number of threads, but generally low microseconds.
+/// * Loads with heavy concurrent writer (to the same `ArcSwap`): ~250ns.
 ///
 /// [`load`]: crate::ArcSwapAny::load
-pub type DefaultStrategy = HybridStrategy<GenLockStrategy<Global>>;
+/// [`Guard`]: crate::Guard
+pub type DefaultStrategy = HybridStrategy<DefaultConfig>;
 
 /// Strategy for isolating instances.
 ///
@@ -102,7 +106,9 @@ pub type DefaultStrategy = HybridStrategy<GenLockStrategy<Global>>;
 ///
 /// This too may be changed for something else (but with at least as good guarantees, primarily
 /// that other instances won't get influenced by the "torture").
-pub type IndependentStrategy = HybridStrategy<GenLockStrategy<PrivateUnsharded>>;
+// Testing if the DefaultStrategy is good enough to replace it fully and then deprecate.
+#[doc(hidden)]
+pub type IndependentStrategy = DefaultStrategy;
 
 // TODO: When we are ready to un-seal, should these traits become unsafe?
 
diff --git a/src/weak.rs b/src/weak.rs
index 27a8a36..23df26d 100644
--- a/src/weak.rs
+++ b/src/weak.rs
@@ -1,3 +1,4 @@
+use std::ptr;
 use std::rc::Weak as RcWeak;
 use std::sync::Weak;
 
@@ -6,75 +7,111 @@ use crate::RefCnt;
 unsafe impl<T> RefCnt for Weak<T> {
     type Base = T;
     fn as_ptr(me: &Self) -> *mut T {
-        Weak::as_ptr(me) as *mut T
+        if Weak::ptr_eq(&Weak::new(), me) {
+            ptr::null_mut()
+        } else {
+            Weak::as_ptr(me) as *mut T
+        }
     }
     fn into_ptr(me: Self) -> *mut T {
-        Weak::into_raw(me) as *mut T
+        if Weak::ptr_eq(&Weak::new(), &me) {
+            ptr::null_mut()
+        } else {
+            Weak::into_raw(me) as *mut T
+        }
     }
     unsafe fn from_ptr(ptr: *const T) -> Self {
-        Weak::from_raw(ptr)
+        if ptr.is_null() {
+            Weak::new()
+        } else {
+            Weak::from_raw(ptr)
+        }
     }
 }
 
 unsafe impl<T> RefCnt for RcWeak<T> {
     type Base = T;
     fn as_ptr(me: &Self) -> *mut T {
-        RcWeak::as_ptr(me) as *mut T
+        if RcWeak::ptr_eq(&RcWeak::new(), me) {
+            ptr::null_mut()
+        } else {
+            RcWeak::as_ptr(me) as *mut T
+        }
     }
     fn into_ptr(me: Self) -> *mut T {
-        RcWeak::into_raw(me) as *mut T
+        if RcWeak::ptr_eq(&RcWeak::new(), &me) {
+            ptr::null_mut()
+        } else {
+            RcWeak::into_raw(me) as *mut T
+        }
     }
     unsafe fn from_ptr(ptr: *const T) -> Self {
-        RcWeak::from_raw(ptr)
+        if ptr.is_null() {
+            RcWeak::new()
+        } else {
+            RcWeak::from_raw(ptr)
+        }
     }
 }
 
-#[cfg(test)]
-mod tests {
-    use std::sync::{Arc, Weak};
+macro_rules! t {
+    ($name: ident, $strategy: ty) => {
+        #[cfg(test)]
+        mod $name {
+            use std::sync::{Arc, Weak};
 
-    use crate::ArcSwapWeak;
+            use crate::ArcSwapAny;
 
-    // Convert to weak, push it through the shared and pull it out again.
-    #[test]
-    fn there_and_back() {
-        let data = Arc::new("Hello");
-        let shared = ArcSwapWeak::new(Arc::downgrade(&data));
-        assert_eq!(1, Arc::strong_count(&data));
-        assert_eq!(1, Arc::weak_count(&data));
-        let weak = shared.load();
-        assert_eq!("Hello", *weak.upgrade().unwrap());
-        assert!(Arc::ptr_eq(&data, &weak.upgrade().unwrap()));
-    }
+            #[allow(deprecated)] // We use "deprecated" testing strategies in here.
+            type ArcSwapWeak<T> = ArcSwapAny<Weak<T>, $strategy>;
 
-    // Replace a weak pointer with a NULL one
-    #[test]
-    // Miri-bug in std, see https://github.com/rust-lang/rust/issues/80365
-    #[cfg_attr(miri, ignore)]
-    fn reset() {
-        let data = Arc::new("Hello");
-        let shared = ArcSwapWeak::new(Arc::downgrade(&data));
-        assert_eq!(1, Arc::strong_count(&data));
-        assert_eq!(1, Arc::weak_count(&data));
+            // Convert to weak, push it through the shared and pull it out again.
+            #[test]
+            fn there_and_back() {
+                let data = Arc::new("Hello");
+                let shared = ArcSwapWeak::new(Arc::downgrade(&data));
+                assert_eq!(1, Arc::strong_count(&data));
+                assert_eq!(1, Arc::weak_count(&data));
+                let weak = shared.load();
+                assert_eq!("Hello", *weak.upgrade().unwrap());
+                assert!(Arc::ptr_eq(&data, &weak.upgrade().unwrap()));
+            }
 
-        // An empty weak (eg. NULL)
-        shared.store(Weak::new());
-        assert_eq!(1, Arc::strong_count(&data));
-        assert_eq!(0, Arc::weak_count(&data));
+            // Replace a weak pointer with a NULL one
+            #[test]
+            fn reset() {
+                let data = Arc::new("Hello");
+                let shared = ArcSwapWeak::new(Arc::downgrade(&data));
+                assert_eq!(1, Arc::strong_count(&data));
+                assert_eq!(1, Arc::weak_count(&data));
 
-        let weak = shared.load();
-        assert!(weak.upgrade().is_none());
-    }
+                // An empty weak (eg. NULL)
+                shared.store(Weak::new());
+                assert_eq!(1, Arc::strong_count(&data));
+                assert_eq!(0, Arc::weak_count(&data));
 
-    // Destroy the underlying data while the weak is still stored inside. Should make it go
-    // NULL-ish
-    #[test]
-    fn destroy() {
-        let data = Arc::new("Hello");
-        let shared = ArcSwapWeak::new(Arc::downgrade(&data));
+                let weak = shared.load();
+                assert!(weak.upgrade().is_none());
+            }
 
-        drop(data);
-        let weak = shared.load();
-        assert!(weak.upgrade().is_none());
-    }
+            // Destroy the underlying data while the weak is still stored inside. Should make it go
+            // NULL-ish
+            #[test]
+            fn destroy() {
+                let data = Arc::new("Hello");
+                let shared = ArcSwapWeak::new(Arc::downgrade(&data));
+
+                drop(data);
+                let weak = shared.load();
+                assert!(weak.upgrade().is_none());
+            }
+        }
+    };
 }
+
+t!(tests_default, crate::DefaultStrategy);
+#[cfg(feature = "internal-test-strategies")]
+t!(
+    tests_full_slots,
+    crate::strategy::test_strategies::FillFastSlots
+);


Test patch:
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 680577e..22a5caa 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -45,7 +45,52 @@ jobs:
           RUST_VERSION: ${{ matrix.rust }}
           OS: ${{ matrix.os }}
           RUSTFLAGS: -D warnings
-        run: cargo test --release --all-features
+        run: cargo test --all-features
+
+  big-tests:
+    name: Run the big ignored tests
+    runs-on: ubuntu-latest
+    steps:
+      - name: checkout
+        uses: actions/checkout@v2
+
+      - name: Install Rust
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: stable
+          default: true
+          profile: minimal
+
+      - name: Restore cache
+        uses: Swatinem/rust-cache@v1
+
+      - name: Build & test
+        env:
+          RUSTFLAGS: -D warnings
+        run: cargo test --all-features --release -- --ignored
+
+  bits32:
+    name: 32bit tests
+    runs-on: ubuntu-latest
+    steps:
+      - name: checkout
+        uses: actions/checkout@v2
+
+      - name: Install Rust
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: stable
+          default: true
+          profile: minimal
+          target: x86_64-unknown-linux-musl
+
+      - name: Restore cache
+        uses: Swatinem/rust-cache@v1
+
+      - name: Build & test
+        env:
+          RUSTFLAGS: -D warnings
+        run: cargo test --all-features --target x86_64-unknown-linux-musl
 
   rustfmt:
     name: Check formatting
diff --git a/src/strategy/test_strategies.rs b/src/strategy/test_strategies.rs
new file mode 100644
index 0000000..76bffd2
--- /dev/null
+++ b/src/strategy/test_strategies.rs
@@ -0,0 +1,22 @@
+#![deprecated(note = "Only for internal testing. Do not use")]
+#![allow(deprecated)] // We need to allow ourselves the stuff we deprecate here.
+//! Some strategies for internal testing.
+//!
+//! # Warning
+//!
+//! They come with no guarantees of correctness, stability, performance or anything at all. *DO NOT
+//! USE*.
+
+use super::hybrid::{Config, HybridStrategy};
+
+/// Config for no fast slots.
+#[derive(Clone, Copy, Default)]
+pub struct NoFastSlots;
+
+impl Config for NoFastSlots {
+    const USE_FAST: bool = false;
+}
+
+/// A strategy that fills the slots with some crap to make sure we test the fallbacks too.
+#[deprecated(note = "Only for internal testing. Do not use")]
+pub type FillFastSlots = HybridStrategy<NoFastSlots>;
diff --git a/tests/random.rs b/tests/random.rs
index 50fa9bc..5d8d32c 100644
--- a/tests/random.rs
+++ b/tests/random.rs
@@ -3,7 +3,7 @@
 use std::mem;
 use std::sync::Arc;
 
-use arc_swap::{ArcSwap, ArcSwapAny, DefaultStrategy, IndependentStrategy};
+use arc_swap::{ArcSwapAny, DefaultStrategy, IndependentStrategy};
 use once_cell::sync::Lazy;
 use proptest::prelude::*;
 
@@ -59,6 +59,7 @@ macro_rules! t {
                 instructions in proptest::collection::vec(SelInstruction::random(), 1..SIZE),
             ) {
                 let mut bare = Arc::clone(&ARCS[0]);
+                #[allow(deprecated)] // We use "deprecated" testing strategies in here.
                 let a = ArcSwapAny::<_, $strategy>::from(Arc::clone(&ARCS[0]));
                 for ins in instructions {
                     match ins {
@@ -96,7 +97,8 @@ macro_rules! t {
                 ) {
                     use crate::OpsInstruction::*;
                     let mut m = 0;
-                    let a = ArcSwap::from_pointee(0usize);
+                    #[allow(deprecated)] // We use "deprecated" testing strategies in here.
+                    let a = ArcSwapAny::<_, $strategy>::new(Arc::new(0usize));
                     for ins in instructions {
                         match ins {
                             Store(v) => {
@@ -119,15 +121,5 @@ macro_rules! t {
 
 t!(@full => default, DefaultStrategy);
 t!(@full => independent, IndependentStrategy);
-#[cfg(feature = "experimental-strategies")]
-t!(
-    @full =>
-    simple_genlock,
-    arc_swap::strategy::experimental::SimpleGenLock
-);
-#[cfg(feature = "experimental-strategies")]
-t!(
-    @full =>
-    helping,
-    arc_swap::strategy::experimental::Helping
-);
+#[cfg(feature = "internal-test-strategies")]
+t!(@full => full_slots, arc_swap::strategy::test_strategies::FillFastSlots);
diff --git a/tests/stress.rs b/tests/stress.rs
index b0812a9..15e60fc 100644
--- a/tests/stress.rs
+++ b/tests/stress.rs
@@ -159,7 +159,7 @@ where
             let global_cnt = &global_cnt;
             let live_cnt = &live_cnt;
             scope.spawn(move |_| {
-                for _ in 0..iters {
+                for iter in 0..iters {
                     barr.wait();
                     // Create bunch of nodes and put them into the list.
                     for i in 0..node_cnt {
@@ -175,6 +175,15 @@ where
                             Arc::clone(&node)
                         });
                     }
+                    if barr.wait().is_leader() {
+                        let mut cnt = 0;
+                        let mut node = head.load_full();
+                        while let Some(n) = node.as_ref() {
+                            cnt += 1;
+                            node = n.next.clone();
+                        }
+                        assert_eq!(cnt, node_cnt * cpus);
+                    }
                     barr.wait();
                     // Keep removing items, count how many there are and that they increase in each
                     // thread's list.
@@ -191,6 +200,10 @@ where
                     if barr.wait().is_leader() {
                         assert_eq!(node_cnt * cpus, global_cnt.swap(0, Ordering::Relaxed));
                     }
+                    assert_eq!(
+                        (iters - iter - 1) * node_cnt * cpus,
+                        live_cnt.load(Ordering::Relaxed),
+                    );
                 }
             });
         }
@@ -249,37 +262,40 @@ macro_rules! t {
         mod $name {
             use super::*;
 
+            #[allow(deprecated)] // We use some "deprecated" testing strategies
+            type Strategy = $strategy;
+
             #[test]
             fn storm_link_list_small() {
-                storm_link_list::<$strategy>(ITER_SMALL, 5);
+                storm_link_list::<Strategy>(ITER_SMALL, 5);
             }
 
             #[test]
             #[ignore]
             fn storm_link_list_large() {
-                storm_link_list::<$strategy>(10_000, 50);
+                storm_link_list::<Strategy>(10_000, 50);
             }
 
             #[test]
             fn storm_unroll_small() {
-                storm_unroll::<$strategy>(ITER_SMALL, 5);
+                storm_unroll::<Strategy>(ITER_SMALL, 5);
             }
 
             #[test]
             #[ignore]
             fn storm_unroll_large() {
-                storm_unroll::<$strategy>(10_000, 50);
+                storm_unroll::<Strategy>(10_000, 50);
             }
 
             #[test]
             fn load_parallel_small() {
-                load_parallel::<$strategy>(ITER_MID);
+                load_parallel::<Strategy>(ITER_MID);
             }
 
             #[test]
             #[ignore]
             fn load_parallel_large() {
-                load_parallel::<$strategy>(100_000);
+                load_parallel::<Strategy>(100_000);
             }
         }
     };
@@ -287,10 +303,8 @@ macro_rules! t {
 
 t!(default, DefaultStrategy);
 t!(independent, IndependentStrategy);
-#[cfg(feature = "experimental-strategies")]
+#[cfg(feature = "internal-test-strategies")]
 t!(
-    simple_genlock,
-    arc_swap::strategy::experimental::SimpleGenLock
+    full_slots,
+    arc_swap::strategy::test_strategies::FillFastSlots
 );
-#[cfg(feature = "experimental-strategies")]
-t!(helping, arc_swap::strategy::experimental::Helping);