Skip to content

Commit ffe3a28

Browse files
committed
Add SAFETY comments and notes for unsafe blocks
1 parent 3065c37 commit ffe3a28

File tree

4 files changed

+129
-2
lines changed

4 files changed

+129
-2
lines changed

src/header.rs

+15-2
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,12 @@ impl<M> Header<M> {
6262
let state = self.state.fetch_or(NOTIFYING, Ordering::AcqRel);
6363

6464
// If the task was not notifying or registering an awaiter...
65+
// Note: The NOTIFYING and REGISTERING bits act as locks on the awaiter
66+
// UnsafeCell.
6567
if state & (NOTIFYING | REGISTERING) == 0 {
6668
// Take the waker out.
69+
// SAFETY: self.awaiter is tied to atomic ordering operations on
70+
// self.state.
6771
let waker = unsafe { (*self.awaiter.get()).take() };
6872

6973
// Unset the bit indicating that the task is notifying its awaiter.
@@ -93,8 +97,9 @@ impl<M> Header<M> {
9397

9498
loop {
9599
// There can't be two concurrent registrations because `Task` can only be polled
96-
// by a unique pinned reference.
97-
debug_assert!(state & REGISTERING == 0);
100+
// by a unique pinned reference. Enforcing this here instead of marking the
101+
// whole function unsafe.
102+
assert!(state & REGISTERING == 0);
98103

99104
// If we're in the notifying state at this moment, just wake and return without
100105
// registering.
@@ -119,6 +124,8 @@ impl<M> Header<M> {
119124
}
120125

121126
// Put the waker into the awaiter field.
127+
// SAFETY: We have set the state of the header to | REGISTERING so we have a lock on
128+
// self.awaiter and can write to it.
122129
unsafe {
123130
abort_on_panic(|| (*self.awaiter.get()) = Some(waker.clone()));
124131
}
@@ -130,6 +137,12 @@ impl<M> Header<M> {
130137
loop {
131138
// If there was a notification, take the waker out of the awaiter field.
132139
if state & NOTIFYING != 0 {
140+
// SAFETY: We have guaranteed that self.state is or'd with NOTIFYING, which
141+
// prevents everyone else from writing to self.awaiter. So we know that we won't
142+
// race with any writes from other threads.
143+
// We can't reach this branch on the first loop through, but we can if someone
144+
// notifies the task while we are in the middle of registering. Normally, they
145+
// would also take a waker, but they won't if we have the NOTIFYING bit set.
133146
if let Some(w) = unsafe { (*self.awaiter.get()).take() } {
134147
abort_on_panic(|| waker = Some(w));
135148
}

src/raw.rs

+75
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ impl<F, T, S, M> RawTask<F, T, S, M> {
123123
let offset_r = offset_union;
124124

125125
TaskLayout {
126+
// SAFETY: layout came from a Layout::extend call, which dynamically checks the
127+
// invariants for StdLayout and returns None if they are not met. The leap_unwrap!
128+
// would have panicked before this point.
126129
layout: unsafe { layout.into_std() },
127130
offset_s,
128131
offset_f,
@@ -163,10 +166,14 @@ where
163166
unsafe {
164167
// Allocate enough space for the entire task.
165168
let ptr = match NonNull::new(alloc::alloc::alloc(task_layout.layout) as *mut ()) {
169+
// SAFETY: task_layout.layout definitely has non-zero size because it's the layout
170+
// for a struct with a field that is AtomicUsize.
166171
None => abort(),
167172
Some(p) => p,
168173
};
169174

175+
// SAFETY: task_layout.layout has the correct layout for a C-style struct of Header
176+
// followed by S followed by union { F, T }.
170177
let raw = Self::from_ptr(ptr.as_ptr());
171178

172179
let crate::Builder {
@@ -176,6 +183,10 @@ where
176183
} = builder;
177184

178185
// Write the header as the first field of the task.
186+
// SAFETY: This write it OK because it's through a mutable pointer to a Header<M> that
187+
// is definitely properly aligned and points to enough memory for a Header<M>. We
188+
// didn't pass our pointer through any const references or other const-ifying
189+
// operations so the provenance is good.
179190
(raw.header as *mut Header<M>).write(Header {
180191
state: AtomicUsize::new(SCHEDULED | TASK | REFERENCE),
181192
awaiter: UnsafeCell::new(None),
@@ -195,12 +206,19 @@ where
195206
});
196207

197208
// Write the schedule function as the third field of the task.
209+
// SAFETY: raw.schedule is also non-null, properly aligned, valid for writes of size
210+
// size_of::<Schedule>().
198211
(raw.schedule as *mut S).write(schedule);
199212

200213
// Generate the future, now that the metadata has been pinned in place.
214+
// SAFETY: SAFETY: Dereferencing raw.header is OK because it's properly initialized
215+
// since we wrote to it.
201216
let future = abort_on_panic(|| future(&(*raw.header).metadata));
202217

203218
// Write the future as the fourth field of the task.
219+
// SAFETY: This write is OK because raw.future is non-null, properly-aligned, and valid
220+
// for writes of size F. Because we're not casting anything here we know it's the right
221+
// type.
204222
raw.future.write(future);
205223

206224
ptr
@@ -210,10 +228,15 @@ where
210228
/// Creates a `RawTask` from a raw task pointer.
211229
#[inline]
212230
pub(crate) fn from_ptr(ptr: *const ()) -> Self {
231+
// TODO: This function technically should be unsafe, since ptr must point to a region that
232+
// has a size and alignment matching task layout, since doing pointer arithmetic that
233+
// leaves the region or creating unaligned pointers is UB.
213234
let task_layout = Self::task_layout();
214235
let p = ptr as *const u8;
215236

216237
unsafe {
238+
// SAFETY: We're just picking apart the given pointer into its constituent fields.
239+
// These do correctly correspond to the fields as laid out in task_layout.
217240
Self {
218241
header: p as *const Header<M>,
219242
schedule: p.add(task_layout.offset_s) as *const S,
@@ -232,6 +255,8 @@ where
232255
unsafe fn wake(ptr: *const ()) {
233256
// This is just an optimization. If the schedule function has captured variables, then
234257
// we'll do less reference counting if we wake the waker by reference and then drop it.
258+
// TODO: Add safety docs here. What requirements does ptr have to meet? We can probably
259+
// assume that it has to be a pointer to a properly-allocated task.
235260
if mem::size_of::<S>() > 0 {
236261
Self::wake_by_ref(ptr);
237262
Self::drop_waker(ptr);
@@ -240,6 +265,8 @@ where
240265

241266
let raw = Self::from_ptr(ptr);
242267

268+
// SAFETY: This is just loading the state. Note that this does implicitly create an
269+
// &AtomicUsize, which is intentional.
243270
let mut state = (*raw.header).state.load(Ordering::Acquire);
244271

245272
loop {
@@ -296,6 +323,8 @@ where
296323

297324
/// Wakes a waker by reference.
298325
unsafe fn wake_by_ref(ptr: *const ()) {
326+
// TODO: Add safety docs, presumably ptr needs to be alive and point to a
327+
// correctly-allocated task.
299328
let raw = Self::from_ptr(ptr);
300329

301330
let mut state = (*raw.header).state.load(Ordering::Acquire);
@@ -346,6 +375,8 @@ where
346375
// because the schedule function cannot be destroyed while the waker is
347376
// still alive.
348377
let task = Runnable::from_raw(NonNull::new_unchecked(ptr as *mut ()));
378+
// SAFETY: The task is still alive, so we can call its schedule
379+
// function.
349380
(*raw.schedule).schedule(task, ScheduleInfo::new(false));
350381
}
351382

@@ -367,6 +398,8 @@ where
367398

368399
// If the reference count overflowed, abort.
369400
if state > isize::MAX as usize {
401+
// NOTE: isize::MAX definitely has more than 1 << 8 numbers between it and usize::MAX
402+
// so we're guaranteed to hit this.
370403
abort();
371404
}
372405

@@ -394,9 +427,17 @@ where
394427
(*raw.header)
395428
.state
396429
.store(SCHEDULED | CLOSED | REFERENCE, Ordering::Release);
430+
// SAFETY: ptr still points to a valid task even though its refcount has dropped
431+
// to zero.
432+
// NOTE: We should make sure that the executor is properly dropping scheduled tasks
433+
// with a refcount of zero.
397434
Self::schedule(ptr, ScheduleInfo::new(false));
398435
} else {
399436
// Otherwise, destroy the task right away.
437+
// NOTE: This isn't going to drop the output/result from the future. We have to
438+
// have already dealt with it, so whoever is calling drop_waker needs to be
439+
// checked. It looks like whoever sets the TASK bit to zero is affirming that they
440+
// have moved or dropped the output/result.
400441
Self::destroy(ptr);
401442
}
402443
}
@@ -416,6 +457,8 @@ where
416457
// If this was the last reference to the task and the `Task` has been dropped too,
417458
// then destroy the task.
418459
if new & !(REFERENCE - 1) == 0 && new & TASK == 0 {
460+
// SAFETY: This is safe as long as ptr obeys the same invariants we need everywhere
461+
// else.
419462
Self::destroy(ptr);
420463
}
421464
}
@@ -435,6 +478,8 @@ where
435478
}
436479

437480
let task = Runnable::from_raw(NonNull::new_unchecked(ptr as *mut ()));
481+
// NOTE: The schedule function has to drop tasks with a refcount of zero. That's not
482+
// happening in this function, so it has to be happening in the schedule member function.
438483
(*raw.schedule).schedule(task, info);
439484
}
440485

@@ -459,6 +504,9 @@ where
459504
///
460505
/// The schedule function will be dropped, and the task will then get deallocated.
461506
/// The task must be closed before this function is called.
507+
///
508+
/// NOTE: Whoever calls this function has to have already dealt with the return value of the
509+
/// future or its error if it failed. We are not going to drop it!
462510
#[inline]
463511
unsafe fn destroy(ptr: *const ()) {
464512
let raw = Self::from_ptr(ptr);
@@ -467,13 +515,18 @@ where
467515
// We need a safeguard against panics because destructors can panic.
468516
abort_on_panic(|| {
469517
// Drop the header along with the metadata.
518+
// SAFETY: This points to a valid Header<M> that we have permission to move out of and
519+
// drop.
470520
(raw.header as *mut Header<M>).drop_in_place();
471521

472522
// Drop the schedule function.
523+
// SAFETY: This points to a valid S that we have permission to move out of and drop.
473524
(raw.schedule as *mut S).drop_in_place();
474525
});
475526

476527
// Finally, deallocate the memory reserved by the task.
528+
// SAFETY: We know that ptr was allocated with layout task_layout.layout, so deallocating
529+
// it with the same layout is correct.
477530
alloc::alloc::dealloc(ptr as *mut u8, task_layout.layout);
478531
}
479532

@@ -482,9 +535,11 @@ where
482535
/// If polling its future panics, the task will be closed and the panic will be propagated into
483536
/// the caller.
484537
unsafe fn run(ptr: *const ()) -> bool {
538+
// SAFETY: As long as it's a pointer to a valid task, we can get the raw form of it.
485539
let raw = Self::from_ptr(ptr);
486540

487541
// Create a context from the raw task pointer and the vtable inside the its header.
542+
// SAFETY: The implementation of RAW_WAKER_VTABLE is correct.
488543
let waker = ManuallyDrop::new(Waker::from_raw(RawWaker::new(ptr, &Self::RAW_WAKER_VTABLE)));
489544
let cx = &mut Context::from_waker(&waker);
490545

@@ -507,6 +562,9 @@ where
507562
}
508563

509564
// Drop the task reference.
565+
// SAFETY: This pointer is definitely alive because we hold a reference to it.
566+
// TODO: But who holds the reference to it? The queue in general? The fact that
567+
// it's scheduled?
510568
Self::drop_ref(ptr);
511569

512570
// Notify the awaiter that the future has been dropped.
@@ -563,7 +621,10 @@ where
563621
match poll {
564622
Poll::Ready(out) => {
565623
// Replace the future with its output.
624+
// SAFETY: We have exclusive access to the task so we can drop the future for it.
566625
Self::drop_future(ptr);
626+
// SAFETY: raw.output definitely points to a valid memory location to hold the
627+
// Output type of the future.
567628
raw.output.write(out);
568629

569630
// The task is now completed.
@@ -593,10 +654,12 @@ where
593654
// Take the awaiter out.
594655
let mut awaiter = None;
595656
if state & AWAITER != 0 {
657+
// SAFETY: This is safe for the same reasons as we said earlier.
596658
awaiter = (*raw.header).take(None);
597659
}
598660

599661
// Drop the task reference.
662+
// SAFETY: We "own" the ref to this task and are allowed to drop it.
600663
Self::drop_ref(ptr);
601664

602665
// Notify the awaiter that the future has been dropped.
@@ -625,6 +688,9 @@ where
625688
if state & CLOSED != 0 && !future_dropped {
626689
// The thread that closed the task didn't drop the future because it was
627690
// running so now it's our responsibility to do so.
691+
// SAFETY: This is corroborated by header.rs where they state that closing
692+
// a task doesn't drop the future, it just marks it closed and puts it back
693+
// in the polling queue so a poller can drop it.
628694
Self::drop_future(ptr);
629695
future_dropped = true;
630696
}
@@ -648,6 +714,8 @@ where
648714
}
649715

650716
// Drop the task reference.
717+
// SAFETY: We're allowed to drop the ref as stated earlier. We
718+
// checked that it won't accidentally be double-dropped.
651719
Self::drop_ref(ptr);
652720

653721
// Notify the awaiter that the future has been dropped.
@@ -657,10 +725,13 @@ where
657725
} else if state & SCHEDULED != 0 {
658726
// The thread that woke the task up didn't reschedule it because
659727
// it was running so now it's our responsibility to do so.
728+
// SAFETY: ptr definitely points to a valid task that hasn't been
729+
// dropped. It has its SCHEDULED bit set.
660730
Self::schedule(ptr, ScheduleInfo::new(true));
661731
return true;
662732
} else {
663733
// Drop the task reference.
734+
// SAFETY: We're still allowed.
664735
Self::drop_ref(ptr);
665736
}
666737
break;
@@ -697,6 +768,7 @@ where
697768
if state & CLOSED != 0 {
698769
// The thread that closed the task didn't drop the future because it
699770
// was running so now it's our responsibility to do so.
771+
// SAFETY: If poll panicked then the thread didn't drop the future.
700772
RawTask::<F, T, S, M>::drop_future(ptr);
701773

702774
// Mark the task as not running and not scheduled.
@@ -711,6 +783,7 @@ where
711783
}
712784

713785
// Drop the task reference.
786+
// SAFETY: We still have permission to drop a ref.
714787
RawTask::<F, T, S, M>::drop_ref(ptr);
715788

716789
// Notify the awaiter that the future has been dropped.
@@ -729,6 +802,8 @@ where
729802
) {
730803
Ok(state) => {
731804
// Drop the future because the task is now closed.
805+
// SAFETY: This is effectively the same situation as earlier.
806+
// TODO: DRY this up by refactoring this.
732807
RawTask::<F, T, S, M>::drop_future(ptr);
733808

734809
// Take the awaiter out.

0 commit comments

Comments
 (0)