Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace the half-lock with helping strategy #50

Merged
merged 19 commits into from
Jan 3, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Little cleanups + tweaks of docs
  • Loading branch information
vorner committed Jan 3, 2021
commit c6f8da3e9b20b56af4e55f64407d6886656de062
3 changes: 2 additions & 1 deletion src/debt/fast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ impl Slots {
// 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 :-(
slot.0.swap(ptr, SeqCst);
let old = slot.0.swap(ptr, SeqCst);
debug_assert_eq!(Debt::NONE, old);
local.offset.set(i + 1);
return Some(&self.0[i]);
}
Expand Down
54 changes: 30 additions & 24 deletions src/debt/helping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,10 @@ use std::sync::atomic::{AtomicPtr, AtomicUsize};
use super::Debt;
use crate::RefCnt;

pub(crate) const REPLACEMENT_TAG: usize = 0b01;
pub(crate) const GEN_TAG: usize = 0b10;
pub(crate) const TAG_MASK: usize = 0b11;
pub(crate) const IDLE: usize = 0;
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)]
Expand All @@ -133,10 +133,36 @@ 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>,
}

Expand Down Expand Up @@ -306,23 +332,3 @@ impl Slots {
}
}
}

#[cfg(test)]
mod tests {
use std::mem;
use std::sync::Arc;

use super::*;

/// 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);
}
}
20 changes: 15 additions & 5 deletions src/debt/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const NODE_COOLDOWN: usize = 2;
/// The head of the debt linked list.
static LIST_HEAD: AtomicPtr<Node> = AtomicPtr::new(ptr::null_mut());

pub(crate) struct NodeReservation<'a>(&'a Node);
pub struct NodeReservation<'a>(&'a Node);

impl Drop for NodeReservation<'_> {
fn drop(&mut self) {
Expand Down Expand Up @@ -126,7 +126,7 @@ impl Node {
}

/// Mark this node that a writer is currently playing with it.
pub(super) fn reserve_writer(&self) -> NodeReservation {
pub fn reserve_writer(&self) -> NodeReservation {
self.active_writers.fetch_add(1, Acquire);
NodeReservation(self)
}
Expand Down Expand Up @@ -235,16 +235,16 @@ impl LocalNode {
///
/// 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.
#[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);
Expand All @@ -258,6 +258,12 @@ impl LocalNode {
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,
Expand All @@ -272,6 +278,10 @@ impl LocalNode {
.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,
Expand Down