Skip to content

Commit b121ee4

Browse files
authored
remove allocation lock (#962)
1 parent e999ae9 commit b121ee4

File tree

2 files changed

+39
-38
lines changed

2 files changed

+39
-38
lines changed

src/table.rs

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -60,30 +60,35 @@ struct SlotVTable {
6060
/// [`Slot`] methods
6161
memos: SlotMemosFnRaw,
6262
memos_mut: SlotMemosMutFnRaw,
63+
/// The type name of what is stored as entries in data.
64+
type_name: fn() -> &'static str,
6365
/// A drop impl to call when the own page drops
64-
/// SAFETY: The caller is required to supply a correct data pointer to a `Box<PageDataEntry<T>>` and initialized length,
65-
/// and correct memo types.
66+
/// SAFETY: The caller is required to supply a valid pointer to a `Box<PageDataEntry<T>>`, and
67+
/// the correct initialized length and memo types.
6668
drop_impl: unsafe fn(data: *mut (), initialized: usize, memo_types: &MemoTableTypes),
6769
}
6870

6971
impl SlotVTable {
7072
const fn of<T: Slot>() -> &'static Self {
7173
const {
7274
&Self {
73-
drop_impl: |data, initialized, memo_types|
74-
// SAFETY: The caller is required to supply a correct data pointer and initialized length
75-
unsafe {
76-
let data = Box::from_raw(data.cast::<PageData<T>>());
75+
drop_impl: |data, initialized, memo_types| {
76+
// SAFETY: The caller is required to provide a valid data pointer.
77+
let data = unsafe { Box::from_raw(data.cast::<PageData<T>>()) };
7778
for i in 0..initialized {
7879
let item = data[i].get().cast::<T>();
79-
memo_types.attach_memos_mut((*item).memos_mut()).drop();
80-
ptr::drop_in_place(item);
80+
// SAFETY: The caller is required to provide a valid initialized length.
81+
unsafe {
82+
memo_types.attach_memos_mut((*item).memos_mut()).drop();
83+
ptr::drop_in_place(item);
84+
}
8185
}
8286
},
8387
layout: Layout::new::<T>(),
84-
// SAFETY: The signatures are compatible
88+
type_name: std::any::type_name::<T>,
89+
// SAFETY: The signatures are ABI-compatible.
8590
memos: unsafe { mem::transmute::<SlotMemosFn<T>, SlotMemosFnRaw>(T::memos) },
86-
// SAFETY: The signatures are compatible
91+
// SAFETY: The signatures are ABI-compatible.
8792
memos_mut: unsafe {
8893
mem::transmute::<SlotMemosMutFn<T>, SlotMemosMutFnRaw>(T::memos_mut)
8994
},
@@ -102,16 +107,6 @@ struct Page {
102107
/// Number of elements of `data` that are initialized.
103108
allocated: AtomicUsize,
104109

105-
/// The "allocation lock" is held when we allocate a new entry.
106-
///
107-
/// It ensures that we can load the index, initialize it, and then update the length atomically
108-
/// with respect to other allocations.
109-
///
110-
/// We could avoid it if we wanted, we'd just have to be a bit fancier in our reasoning
111-
/// (for example, the bounds check in `Page::get` no longer suffices to truly guarantee
112-
/// that the data is initialized).
113-
allocation_lock: Mutex<()>,
114-
115110
/// The potentially uninitialized data of this page. As we initialize new entries, we increment `allocated`.
116111
/// This is a box allocated `PageData<SlotType>`
117112
data: NonNull<()>,
@@ -121,9 +116,6 @@ struct Page {
121116
/// The type id of what is stored as entries in data.
122117
// FIXME: Move this into SlotVTable once const stable
123118
slot_type_id: TypeId,
124-
/// The type name of what is stored as entries in data.
125-
// FIXME: Move this into SlotVTable once const stable
126-
slot_type_name: &'static str,
127119

128120
memo_types: Arc<MemoTableTypes>,
129121
}
@@ -329,12 +321,17 @@ impl<'db, T: Slot> PageView<'db, T> {
329321
unsafe { slice::from_raw_parts(self.0.data.cast::<T>().as_ptr(), len) }
330322
}
331323

324+
/// Allocate a value in this page.
325+
///
326+
/// # Safety
327+
///
328+
/// The caller must be the unique writer to this page, i.e. `allocate` cannot be called
329+
/// concurrently by multiple threads. Concurrent readers however, are fine.
332330
#[inline]
333-
pub(crate) fn allocate<V>(&self, page: PageIndex, value: V) -> Result<(Id, &'db T), V>
331+
pub(crate) unsafe fn allocate<V>(&self, page: PageIndex, value: V) -> Result<(Id, &'db T), V>
334332
where
335333
V: FnOnce(Id) -> T,
336334
{
337-
let _guard = self.0.allocation_lock.lock();
338335
let index = self.0.allocated.load(Ordering::Acquire);
339336
if index >= PAGE_LEN {
340337
return Err(value);
@@ -347,15 +344,14 @@ impl<'db, T: Slot> PageView<'db, T> {
347344
// SAFETY: `index` is also guaranteed to be in bounds as per the check above.
348345
let entry = unsafe { &*data.as_ptr().add(index) };
349346

350-
// SAFETY: We acquired the allocation lock, so we have unique access to the UnsafeCell
351-
// interior
347+
// SAFETY: The caller guarantees we are the unique writer, and readers will not attempt to
348+
// access this index until we have updated the length.
352349
unsafe { (*entry.get()).write(value(id)) };
353350

354351
// SAFETY: We just initialized the value above.
355352
let value = unsafe { (*entry.get()).assume_init_ref() };
356353

357-
// Update the length (this must be done after initialization as otherwise an uninitialized
358-
// read could occur!)
354+
// Update the length now that we have initialized the value.
359355
self.0.allocated.store(index + 1, Ordering::Release);
360356

361357
Ok((id, value))
@@ -383,14 +379,12 @@ impl Page {
383379
};
384380

385381
Self {
382+
ingredient,
383+
memo_types,
386384
slot_vtable: SlotVTable::of::<T>(),
387385
slot_type_id: TypeId::of::<T>(),
388-
slot_type_name: std::any::type_name::<T>(),
389-
ingredient,
390-
allocated: Default::default(),
391-
allocation_lock: Default::default(),
386+
allocated: AtomicUsize::new(0),
392387
data: NonNull::from(Box::leak(data)).cast::<()>(),
393-
memo_types,
394388
}
395389
}
396390

@@ -439,7 +433,7 @@ impl Page {
439433
fn type_assert_failed<T: 'static>(page: &Page) -> ! {
440434
panic!(
441435
"page has slot type `{:?}` but `{:?}` was expected",
442-
page.slot_type_name,
436+
(page.slot_vtable.type_name)(),
443437
std::any::type_name::<T>(),
444438
)
445439
}

src/zalsa_local.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ impl ZalsaLocal {
7070
// Fast-path, we already have an unfilled page available.
7171
if let Some(&page) = most_recent_pages.get(&ingredient) {
7272
let page_ref = zalsa.table().page::<T>(page);
73-
match page_ref.allocate(page, value) {
73+
74+
// SAFETY: `ZalsaLocal` is `!Sync`, and we only insert a page into `most_recent_pages`
75+
// if it was allocated by our thread, so we are the unique writer.
76+
match unsafe { page_ref.allocate(page, value) } {
7477
Ok((id, value)) => return (id, value),
7578
Err(v) => value = v,
7679
}
@@ -108,11 +111,15 @@ impl ZalsaLocal {
108111
loop {
109112
// Try to allocate an entry on that page
110113
let page_ref = zalsa.table().page::<T>(page);
111-
match page_ref.allocate(page, value) {
114+
115+
// SAFETY: `ZalsaLocal` is `!Sync`, and we only insert a page into `most_recent_pages`
116+
// if it was allocated by our thread, so we are the unique writer.
117+
match unsafe { page_ref.allocate(page, value) } {
112118
// If successful, return
113119
Ok((id, value)) => return (id, value),
114120

115-
// Otherwise, create a new page and try again
121+
// Otherwise, create a new page and try again.
122+
//
116123
// Note that we could try fetching a page again, but as we just filled one up
117124
// it is unlikely that there is a non-full one available.
118125
Err(v) => {

0 commit comments

Comments
 (0)