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.
Summary
Collector::retireandGuard::defer_retirecurrently take a*mut Tand a reclaimer of typeunsafe fn(*mut T, &Collector). This works well when the retired object is a heap pointer, butsome resources that need delayed reclamation are not pointers.
Would it be possible to add APIs analogous to
retire/defer_retirethat accept anySendvalue, 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:
Names are bikesheddable (
retire_val,retire_owned,defer, etc.). The important part is:T: Send, not*mut T;reclaimreceives thatT(plus&Collector, matching the existing reclaimer convention);retiremay reclaimimmediately;
defer_retire_*waits until the current guard is dropped).Existing
retire/defer_retirecould 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.html) already supports this kind of use case:
defer/defer_uncheckedschedule an arbitrary closure after the grace period;defer_destroyis 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
Without such an API,
idxhas to be boxed (or stored behind some other pointer) solely tosatisfy
*mut T.Open questions
T: Sendbe required, or should there be anunsafevariant withoutSend(as incrossbeam_epoch::Guard::defer_unchecked) for cases the type system cannot prove?Tinto the retirement batch an acceptable implementation, or would you prefer aclosure-based
deferlike crossbeam?retire/defer_retiresignatures, or add newmethods and keep the pointer APIs as-is?
Happy to help with a prototype if this direction seems reasonable.