Skip to content

Support retiring arbitrary Send values, not only pointers #48

Description

@Ricardo-Evans

Summary

Collector::retire and Guard::defer_retire currently take a *mut T and a reclaimer of type
unsafe fn(*mut T, &Collector). This works well when the retired object is a heap pointer, but
some resources that need delayed reclamation are not pointers.
Would it be possible to add APIs analogous to retire / defer_retire that accept any Send
value, with the reclaimer’s first argument updated accordingly?

Motivation

The current design is centered on allocating and freeing pointers. In some data structures the
thing that must be protected until all active guards drop is not a pointer.
A common example is a self-referential structure backed by an arena: nodes refer to each other
by arena index rather than *mut T. After an index is logically removed, it must not be reused
(or the slot must not be dropped) until no concurrent reader can still observe that index. Today
this is awkward to express with seize: you typically have to box the index (or a small handle)
just to get a pointer to retire, or invent an ad-hoc side channel.
The same pattern applies to other non-pointer resources: generation counters, slab keys, file
descriptors / handles, or small owned values that should be dropped only after a grace period.

Proposed API

Something along the lines of:

impl Collector {
    /// Retires `value`, running `reclaim` when no threads hold a reference to it.
    ///
    /// Like [`Collector::retire`], this is disconnected from any guards on the
    /// current thread, so `value` may be reclaimed immediately.
    pub unsafe fn retire_val<T: Send>(
        &self,
        value: T,
        reclaim: unsafe fn(T, &Collector),
    );
}

pub trait Guard {
    /// Like [Guard::defer_retire], but delays reclamation of an owned Send
    /// value until this guard is dropped.
    unsafe fn defer_retire_val<T: Send>(
        &self,
        value: T,
        reclaim: unsafe fn(T, &Collector),
    );
}

Names are bikesheddable (retire_val, retire_owned, defer, etc.). The important part is:

  • the retired payload is an owned T: Send, not *mut T;
  • reclaim receives that T (plus &Collector, matching the existing reclaimer convention);
  • safety/timing semantics stay aligned with the existing pointer APIs (retire may reclaim
    immediately; defer_retire_* waits until the current guard is dropped).
    Existing retire / defer_retire could remain as convenience wrappers around the pointer case
    (e.g. via reclaim::boxed).

Prior art

[crossbeam-epoch::Guard](https://docs.rs/crossbeam-epoch/latest/crossbeam_epoch/struct.Guard.h
tml) already supports this kind of use case:

  • defer / defer_unchecked schedule an arbitrary closure after the grace period;
  • defer_destroy is the pointer-shaped special case.
    A value-oriented retire API in seize would cover the same gap without forcing users to smuggle
    non-pointer state through *mut T.

Use case sketch

// Arena slot 42 was unlinked from the shared structure.
// Readers that entered before unlink may still hold index 42.
let idx: u32 = 42;

unsafe {
    guard.defer_retire_val(idx, |idx, _collector| {
        arena.free(idx);
    });
}

Without such an API, idx has to be boxed (or stored behind some other pointer) solely to
satisfy *mut T.

Open questions

  • Should T: Send be required, or should there be an unsafe variant without Send (as in
    crossbeam_epoch::Guard::defer_unchecked) for cases the type system cannot prove?
  • Is boxing T into the retirement batch an acceptable implementation, or would you prefer a
    closure-based defer like crossbeam?
  • Would you rather generalize the existing retire / defer_retire signatures, or add new
    methods and keep the pointer APIs as-is?
    Happy to help with a prototype if this direction seems reasonable.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions