Skip to main content

quick_cache/
sync_placeholder.rs

1use std::{
2    future::Future,
3    hash::{BuildHasher, Hash},
4    hint::unreachable_unchecked,
5    marker::PhantomPinned,
6    mem, pin,
7    task::{self, Poll},
8    time::{Duration, Instant},
9};
10
11use crate::{
12    linked_slab::Token,
13    shard::CacheShard,
14    shim::{
15        rw_lock::{RwLock, RwLockWriteGuard},
16        sync::{
17            atomic::{AtomicBool, Ordering},
18            Arc,
19        },
20        thread, OnceLock,
21    },
22    Equivalent, Lifecycle, Weighter,
23};
24
25pub type SharedPlaceholder<Val> = Arc<Placeholder<Val>>;
26
27impl<Val> crate::shard::SharedPlaceholder for SharedPlaceholder<Val> {
28    fn new(hash: u64, idx: Token) -> Self {
29        Arc::new(Placeholder {
30            hash,
31            idx,
32            value: OnceLock::new(),
33            state: RwLock::new(State {
34                waiters: Default::default(),
35                loading: LoadingState::Loading,
36            }),
37        })
38    }
39
40    #[inline]
41    fn same_as(&self, other: &Self) -> bool {
42        Arc::ptr_eq(self, other)
43    }
44
45    #[inline]
46    fn hash(&self) -> u64 {
47        self.hash
48    }
49
50    #[inline]
51    fn idx(&self) -> Token {
52        self.idx
53    }
54}
55
56#[derive(Debug)]
57pub struct Placeholder<Val> {
58    hash: u64,
59    idx: Token,
60    state: RwLock<State>,
61    value: OnceLock<Val>,
62}
63
64impl<Val> Placeholder<Val> {
65    /// Returns the filled value, if any.
66    #[inline]
67    pub(crate) fn value(&self) -> Option<&Val> {
68        self.value.get()
69    }
70}
71
72#[derive(Debug)]
73pub struct State {
74    /// The waiters list
75    /// Adding to the list requires holding the outer shard lock to avoid races between
76    /// removing the orphan placeholder from the cache and adding a new waiter to it.
77    waiters: Vec<Waiter>,
78    loading: LoadingState,
79}
80
81#[derive(Debug)]
82enum LoadingState {
83    /// A guard was/will be created and the value might get filled
84    Loading,
85    /// A value was filled, no more waiters can be added
86    Inserted,
87}
88
89pub struct PlaceholderGuard<'a, Key, Val, We, B, L> {
90    shard: &'a RwLock<CacheShard<Key, Val, We, B, L, SharedPlaceholder<Val>>>,
91    shared: SharedPlaceholder<Val>,
92    inserted: bool,
93}
94
95#[cfg(test)]
96impl<'a, Key, Val, We, B, L> PlaceholderGuard<'a, Key, Val, We, B, L> {
97    pub fn shared(&self) -> &SharedPlaceholder<Val> {
98        &self.shared
99    }
100}
101
102#[derive(Debug)]
103enum Waiter {
104    Thread {
105        notified: *const AtomicBool,
106        thread: thread::Thread,
107    },
108    Task {
109        notified: *const AtomicBool,
110        waker: task::Waker,
111    },
112}
113
114// SAFETY: The AtomicBool is on the waiting thread's stack or pinned future
115// and the thread/task will remove itself from waiters before returning
116unsafe impl Send for Waiter {}
117unsafe impl Sync for Waiter {}
118
119impl Waiter {
120    #[inline]
121    fn notify(self) {
122        match self {
123            Waiter::Thread {
124                thread, notified, ..
125            } => {
126                // SAFETY: The AtomicBool is on the waiting thread's stack or pinned future
127                // and the thread/task will remove itself from waiters before returning
128                unsafe { notified.as_ref().unwrap().store(true, Ordering::Release) };
129                thread.unpark();
130            }
131            Waiter::Task { waker: t, notified } => {
132                unsafe { notified.as_ref().unwrap().store(true, Ordering::Release) };
133                t.wake();
134            }
135        }
136    }
137
138    #[inline]
139    fn is_waiter(&self, other: *const AtomicBool) -> bool {
140        matches!(self, Waiter::Task { notified, .. } | Waiter::Thread { notified, .. } if std::ptr::eq(*notified, other))
141    }
142}
143
144/// Result of [`Cache::get_value_or_guard`](crate::sync::Cache::get_value_or_guard).
145///
146/// See also [`Cache::get_value_or_guard_async`](crate::sync::Cache::get_value_or_guard_async)
147/// which returns `Result<Val, PlaceholderGuard>` instead.
148#[derive(Debug)]
149pub enum GuardResult<'a, Key, Val, We, B, L> {
150    /// The value was found in the cache.
151    Value(Val),
152    /// The key was absent; use the guard to insert a value.
153    Guard(PlaceholderGuard<'a, Key, Val, We, B, L>),
154    /// Timed out waiting for another loader's placeholder.
155    Timeout,
156}
157
158// Re-export from shard where it's defined.
159pub use crate::shard::EntryAction;
160
161/// Result of waiting for a placeholder or [`JoinFuture`].
162pub(crate) enum JoinResult<'a, Key, Val, We, B, L> {
163    /// Value is available — either found directly in the cache (`None`) or
164    /// inside the shared placeholder (`Some`).
165    Filled(Option<SharedPlaceholder<Val>>),
166    /// Got the guard — caller should load the value.
167    Guard(PlaceholderGuard<'a, Key, Val, We, B, L>),
168    /// Timed out waiting (sync paths only).
169    Timeout,
170}
171
172/// Result of an [`entry`](crate::sync::Cache::entry) or
173/// [`entry_async`](crate::sync::Cache::entry_async) operation.
174#[derive(Debug)]
175pub enum EntryResult<'a, Key, Val, We, B, L, T> {
176    /// The key existed and the callback returned [`EntryAction::Retain`].
177    /// Contains the value `T` returned by the callback.
178    Retained(T),
179    /// The key existed and the callback returned [`EntryAction::Remove`].
180    /// Contains the removed key and value.
181    Removed(Key, Val),
182    /// The key existed and the callback returned [`EntryAction::ReplaceWithGuard`].
183    /// Contains a [`PlaceholderGuard`] for re-insertion and the old value.
184    Replaced(PlaceholderGuard<'a, Key, Val, We, B, L>, Val),
185    /// The key was absent. Contains a [`PlaceholderGuard`] for inserting a new value.
186    Vacant(PlaceholderGuard<'a, Key, Val, We, B, L>),
187    /// Timed out waiting for another loader's placeholder.
188    ///
189    /// Only returned by [`Cache::entry`](crate::sync::Cache::entry),
190    /// which accepts a `timeout` parameter. For the async variant, use an external
191    /// timeout mechanism (e.g. `tokio::time::timeout`).
192    Timeout,
193}
194
195impl<'a, Key, Val, We, B, L> PlaceholderGuard<'a, Key, Val, We, B, L> {
196    #[inline]
197    pub fn start_loading(
198        shard: &'a RwLock<CacheShard<Key, Val, We, B, L, SharedPlaceholder<Val>>>,
199        shared: SharedPlaceholder<Val>,
200    ) -> Self {
201        debug_assert!(matches!(
202            shared.state.write().loading,
203            LoadingState::Loading
204        ));
205        PlaceholderGuard {
206            shard,
207            shared,
208            inserted: false,
209        }
210    }
211
212    // Check the state of the placeholder, returning the value if it was loaded
213    // or a guard if the caller got the guard.
214    #[inline]
215    fn handle_notification(
216        shard: &'a RwLock<CacheShard<Key, Val, We, B, L, SharedPlaceholder<Val>>>,
217        shared: SharedPlaceholder<Val>,
218    ) -> Result<SharedPlaceholder<Val>, PlaceholderGuard<'a, Key, Val, We, B, L>> {
219        // Check if the value was loaded, and if it wasn't it means we got the
220        // guard and need to start loading the value.
221        if shared.value().is_some() {
222            Ok(shared)
223        } else {
224            Err(PlaceholderGuard::start_loading(shard, shared))
225        }
226    }
227
228    // Join the waiters list or return the value if it was already loaded
229    #[inline]
230    fn join_waiters(
231        // we require the shard lock to be held to add a new waiter
232        _locked_shard: RwLockWriteGuard<'a, CacheShard<Key, Val, We, B, L, SharedPlaceholder<Val>>>,
233        shared: &SharedPlaceholder<Val>,
234        // a function that returns a waiter if it should be added
235        waiter_new: impl FnOnce() -> Option<Waiter>,
236    ) -> bool {
237        let mut state = shared.state.write();
238        // _locked_shard could be released here, it would be sufficient to synchronize with the holder
239        // of the guard trying to remove the placeholder from the cache. But if this placeholder is hot,
240        // anyone waiting on the shard will immediately hit the state lock. Since the cache is sharded
241        // we consider the latter more likely. So we keep the shard lock until we are done with the state.
242        match state.loading {
243            LoadingState::Loading => {
244                if let Some(waiter) = waiter_new() {
245                    state.waiters.push(waiter);
246                }
247                false
248            }
249            LoadingState::Inserted => true,
250        }
251    }
252}
253
254impl<
255        'a,
256        Key: Eq + Hash,
257        Val: Clone,
258        We: Weighter<Key, Val>,
259        B: BuildHasher,
260        L: Lifecycle<Key, Val>,
261    > PlaceholderGuard<'a, Key, Val, We, B, L>
262{
263    pub fn join<Q>(
264        shard: &'a RwLock<CacheShard<Key, Val, We, B, L, SharedPlaceholder<Val>>>,
265        hash: u64,
266        key: &Q,
267        timeout: Option<Duration>,
268    ) -> GuardResult<'a, Key, Val, We, B, L>
269    where
270        Q: Hash + Equivalent<Key> + ToOwned<Owned = Key> + ?Sized,
271    {
272        let mut shard_guard = shard.write();
273        let shared = match shard_guard.get_or_placeholder(hash, key) {
274            Ok((_, v)) => return GuardResult::Value(v.clone()),
275            Err((shared, true)) => {
276                return GuardResult::Guard(Self::start_loading(shard, shared));
277            }
278            Err((shared, false)) => shared,
279        };
280        let mut deadline = timeout.map(Ok);
281        match Self::wait_for_placeholder(shard, shard_guard, shared, deadline.as_mut()) {
282            JoinResult::Filled(shared) => unsafe {
283                // SAFETY: Filled means the value was set by the loader.
284                GuardResult::Value(shared.unwrap_unchecked().value().unwrap_unchecked().clone())
285            },
286            JoinResult::Guard(g) => GuardResult::Guard(g),
287            JoinResult::Timeout => GuardResult::Timeout,
288        }
289    }
290
291    /// Waits for an existing placeholder to be filled by another thread.
292    ///
293    /// Registers the current thread as a waiter (consuming the shard guard to avoid
294    /// races with placeholder removal), then parks until notified or timeout.
295    ///
296    /// `deadline` is `None` for no timeout, or `Some(&mut Ok(duration))` on the first
297    /// call. On first use the duration is converted in-place to `Err(instant)` so that
298    /// callers that retry (e.g. `entry`) preserve the original deadline across calls.
299    pub(crate) fn wait_for_placeholder(
300        shard: &'a RwLock<CacheShard<Key, Val, We, B, L, SharedPlaceholder<Val>>>,
301        shard_guard: RwLockWriteGuard<'a, CacheShard<Key, Val, We, B, L, SharedPlaceholder<Val>>>,
302        shared: SharedPlaceholder<Val>,
303        deadline: Option<&mut Result<Duration, Instant>>,
304    ) -> JoinResult<'a, Key, Val, We, B, L> {
305        let notified = pin::pin!(AtomicBool::new(false));
306        let mut parked_thread = None;
307        let already_filled = Self::join_waiters(shard_guard, &shared, || {
308            // Skip registering a waiter if the timeout is zero.
309            // An already-elapsed Err(instant) deadline is not checked here;
310            // the loop below handles it and join_timeout cleans up the waiter.
311            if matches!(deadline.as_deref(), Some(Ok(d)) if d.is_zero()) {
312                None
313            } else {
314                let thread = thread::current();
315                parked_thread = Some(thread.id());
316                Some(Waiter::Thread {
317                    thread,
318                    notified: &*notified as *const AtomicBool,
319                })
320            }
321        });
322        if already_filled {
323            return JoinResult::Filled(Some(shared));
324        }
325
326        // Lazily convert the duration to a deadline on first call;
327        // subsequent retries from entry() reuse the same deadline.
328        let deadline = deadline.and_then(|d| match *d {
329            Ok(dur) => match Instant::now().checked_add(dur) {
330                Some(instant) => {
331                    *d = Err(instant);
332                    Some(instant)
333                }
334                None => None, // overflow → treat as no timeout (wait forever)
335            },
336            Err(instant) => Some(instant),
337        });
338        loop {
339            if let Some(instant) = deadline {
340                let remaining = instant.saturating_duration_since(Instant::now());
341                if remaining.is_zero() {
342                    return Self::join_timeout(shard, shared, parked_thread, &notified);
343                }
344                #[cfg(not(fuzzing))]
345                thread::park_timeout(remaining);
346            } else {
347                #[cfg(not(fuzzing))]
348                thread::park();
349            }
350            if notified.load(Ordering::Acquire) {
351                return match Self::handle_notification(shard, shared) {
352                    Ok(shared) => JoinResult::Filled(Some(shared)),
353                    Err(g) => JoinResult::Guard(g),
354                };
355            }
356        }
357    }
358
359    #[cold]
360    fn join_timeout(
361        shard: &'a RwLock<CacheShard<Key, Val, We, B, L, Arc<Placeholder<Val>>>>,
362        shared: Arc<Placeholder<Val>>,
363        // when timeout is zero, the thread may have not been added to the waiters list
364        parked_thread: Option<thread::ThreadId>,
365        notified: &AtomicBool,
366    ) -> JoinResult<'a, Key, Val, We, B, L> {
367        let mut state = shared.state.write();
368        match state.loading {
369            LoadingState::Loading if notified.load(Ordering::Acquire) => {
370                drop(state); // Drop state guard to avoid a deadlock with start_loading
371                JoinResult::Guard(PlaceholderGuard::start_loading(shard, shared))
372            }
373            LoadingState::Loading => {
374                if parked_thread.is_some() {
375                    // Remove ourselves from the waiters list
376                    let waiter_idx = state
377                        .waiters
378                        .iter()
379                        .position(|w| w.is_waiter(notified as _));
380                    if let Some(idx) = waiter_idx {
381                        state.waiters.swap_remove(idx);
382                    } else {
383                        unsafe { unreachable_unchecked() };
384                    }
385                }
386                JoinResult::Timeout
387            }
388            LoadingState::Inserted => {
389                drop(state);
390                JoinResult::Filled(Some(shared))
391            }
392        }
393    }
394}
395
396impl<
397        Key: Eq + Hash,
398        Val: Clone,
399        We: Weighter<Key, Val>,
400        B: BuildHasher,
401        L: Lifecycle<Key, Val>,
402    > PlaceholderGuard<'_, Key, Val, We, B, L>
403{
404    /// Inserts the value into the placeholder
405    ///
406    /// Returns Err if the placeholder isn't in the cache anymore.
407    /// A placeholder can be removed as a result of a `remove` call
408    /// or a non-placeholder `insert` with the same key.
409    pub fn insert(self, value: Val) -> Result<(), Val> {
410        let mut lcs = Default::default();
411        self.insert_with_lifecycle(value, &mut lcs)
412    }
413
414    /// Inserts the value into the placeholder, recording any evicted items into the given
415    /// lifecycle request state.
416    ///
417    /// Returns Err if the placeholder isn't in the cache anymore.
418    /// A placeholder can be removed as a result of a `remove` call
419    /// or a non-placeholder `insert` with the same key.
420    ///
421    /// `lcs` is a [`Lifecycle::RequestState`]; construct one with `Default::default()`.
422    /// Evicted items are released when `lcs` is dropped.
423    pub fn insert_with_lifecycle(
424        mut self,
425        value: Val,
426        lcs: &mut L::RequestState,
427    ) -> Result<(), Val> {
428        unsafe { self.shared.value.set(value.clone()).unwrap_unchecked() };
429        let referenced;
430        {
431            // Whoever is already waiting will get notified and hit the fast-path
432            // as they will see the value set. Anyone that races trying to add themselves
433            // to the waiters list will wait on the state lock.
434            let mut state = self.shared.state.write();
435            state.loading = LoadingState::Inserted;
436            referenced = !state.waiters.is_empty();
437            for w in state.waiters.drain(..) {
438                w.notify();
439            }
440        }
441
442        // Set flag to disable drop_uninserted_slow, it has no work to do:
443        //   - waiters have already been drained
444        //   - no waiters can be added because we set LoadingState::Inserted
445        //   - the placeholder will be removed here, if it still exists
446        self.inserted = true;
447
448        self.shard
449            .write()
450            .replace_placeholder(lcs, &self.shared, referenced, value)?;
451        Ok(())
452    }
453}
454
455impl<Key, Val, We, B, L> PlaceholderGuard<'_, Key, Val, We, B, L> {
456    #[cold]
457    fn drop_uninserted_slow(&mut self) {
458        // Fast path: check if there are other waiters without the shard lock
459        // This may or may not be common, but the assumption is that the shard lock is hot
460        // and should be avoided if possible.
461        {
462            let mut state = self.shared.state.write();
463            debug_assert!(matches!(state.loading, LoadingState::Loading));
464            if let Some(waiter) = state.waiters.pop() {
465                waiter.notify();
466                return;
467            }
468        }
469
470        // Slow path: acquire shard lock and re-check
471        // By acquiring the shard lock we synchronize with any other threads that might be
472        // trying to add themselves to the waiters list.
473        let mut shard_guard = self.shard.write();
474        let mut state = self.shared.state.write();
475        debug_assert!(matches!(state.loading, LoadingState::Loading));
476        if let Some(waiter) = state.waiters.pop() {
477            drop(shard_guard);
478            waiter.notify();
479        } else {
480            shard_guard.remove_placeholder(&self.shared);
481        }
482    }
483}
484
485impl<Key, Val, We, B, L> Drop for PlaceholderGuard<'_, Key, Val, We, B, L> {
486    #[inline]
487    fn drop(&mut self) {
488        if !self.inserted {
489            self.drop_uninserted_slow();
490        }
491    }
492}
493impl<Key, Val, We, B, L> std::fmt::Debug for PlaceholderGuard<'_, Key, Val, We, B, L> {
494    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
495        f.debug_struct("PlaceholderGuard").finish_non_exhaustive()
496    }
497}
498
499/// Future that checks for an existing placeholder and waits for it to be filled.
500///
501/// The shard lock is acquired as a local variable inside `poll`, never stored
502/// in the future state, so the future remains `Send`.
503///
504/// # Pin safety
505///
506/// This future is `!Unpin` because `poll` registers `&self.notified` as a raw
507/// pointer in the placeholder's waiter list. `Pin` guarantees the future won't
508/// be moved after the first poll, keeping that pointer valid. The pointer is
509/// cleaned up in `drop_pending_waiter` before the struct is destroyed.
510pub(crate) struct JoinFuture<'a, 'b, Q: ?Sized, Key, Val, We, B, L> {
511    shard: &'a RwLock<CacheShard<Key, Val, We, B, L, SharedPlaceholder<Val>>>,
512    hash: u64,
513    key: &'b Q,
514    state: JoinFutureState<Val>,
515    notified: AtomicBool,
516    _pin: PhantomPinned,
517}
518
519enum JoinFutureState<Val> {
520    Created,
521    Pending {
522        shared: SharedPlaceholder<Val>,
523        waker: task::Waker,
524    },
525    Done,
526}
527
528impl<'a, 'b, Q: ?Sized, Key, Val, We, B, L> JoinFuture<'a, 'b, Q, Key, Val, We, B, L> {
529    pub(crate) fn new(
530        shard: &'a RwLock<CacheShard<Key, Val, We, B, L, SharedPlaceholder<Val>>>,
531        hash: u64,
532        key: &'b Q,
533    ) -> Self {
534        Self {
535            shard,
536            hash,
537            key,
538            state: JoinFutureState::Created,
539            notified: Default::default(),
540            _pin: PhantomPinned,
541        }
542    }
543}
544
545impl<Q: ?Sized, Key, Val, We, B, L> JoinFuture<'_, '_, Q, Key, Val, We, B, L> {
546    #[cold]
547    fn drop_pending_waiter(&mut self) {
548        let JoinFutureState::Pending { shared, .. } =
549            mem::replace(&mut self.state, JoinFutureState::Done)
550        else {
551            unsafe { unreachable_unchecked() }
552        };
553        let mut state = shared.state.write();
554        match state.loading {
555            LoadingState::Loading if self.notified.load(Ordering::Acquire) => {
556                // The write guard was abandoned elsewhere, this future was notified but didn't get polled.
557                // So we get and drop the guard here to handle the side effects.
558                drop(state); // Drop state guard to avoid a deadlock with start_loading
559                let _ = PlaceholderGuard::start_loading(self.shard, shared);
560            }
561            LoadingState::Loading => {
562                // Remove ourselves from the waiters list
563                let waiter_idx = state
564                    .waiters
565                    .iter()
566                    .position(|w| w.is_waiter(&self.notified as _));
567                if let Some(idx) = waiter_idx {
568                    state.waiters.swap_remove(idx);
569                } else {
570                    // We didn't find ourselves in the waiters list!?
571                    unsafe { unreachable_unchecked() }
572                }
573            }
574            LoadingState::Inserted => (), // Notified but didn't get polled - nothing to do
575        }
576    }
577}
578
579impl<Q: ?Sized, Key, Val, We, B, L> Drop for JoinFuture<'_, '_, Q, Key, Val, We, B, L> {
580    #[inline]
581    fn drop(&mut self) {
582        if matches!(self.state, JoinFutureState::Pending { .. }) {
583            self.drop_pending_waiter();
584        }
585    }
586}
587
588impl<
589        'a,
590        Key: Eq + Hash,
591        Q: Hash + Equivalent<Key> + ToOwned<Owned = Key> + ?Sized,
592        Val,
593        We: Weighter<Key, Val>,
594        B: BuildHasher,
595        L: Lifecycle<Key, Val>,
596    > Future for JoinFuture<'a, '_, Q, Key, Val, We, B, L>
597{
598    type Output = JoinResult<'a, Key, Val, We, B, L>;
599
600    fn poll(self: pin::Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
601        // SAFETY: We never move the struct out of the Pin — only read/write individual
602        // fields. The `notified` field's address (registered in the waiter list) stays
603        // stable because Pin guarantees the future won't be moved.
604        let this = unsafe { self.get_unchecked_mut() };
605        let shard = this.shard;
606        match &mut this.state {
607            JoinFutureState::Created => {
608                let mut shard_guard = shard.write();
609                match shard_guard.get_or_placeholder(this.hash, this.key) {
610                    Ok(_) => {
611                        this.state = JoinFutureState::Done;
612                        Poll::Ready(JoinResult::Filled(None))
613                    }
614                    Err((shared, true)) => {
615                        this.state = JoinFutureState::Done;
616                        drop(shard_guard);
617                        Poll::Ready(JoinResult::Guard(PlaceholderGuard::start_loading(
618                            shard, shared,
619                        )))
620                    }
621                    Err((shared, false)) => {
622                        // Register as waiter while holding shard lock — prevents
623                        // race with drop_uninserted_slow removing the placeholder.
624                        let mut waker = None;
625                        let already_filled =
626                            PlaceholderGuard::join_waiters(shard_guard, &shared, || {
627                                let waker_ = cx.waker().clone();
628                                waker = Some(waker_.clone());
629                                Some(Waiter::Task {
630                                    waker: waker_,
631                                    notified: &this.notified as *const AtomicBool,
632                                })
633                            });
634                        if already_filled {
635                            this.state = JoinFutureState::Done;
636                            Poll::Ready(JoinResult::Filled(Some(shared)))
637                        } else {
638                            this.state = JoinFutureState::Pending {
639                                shared,
640                                waker: waker.unwrap(),
641                            };
642                            Poll::Pending
643                        }
644                    }
645                }
646            }
647            JoinFutureState::Pending { waker, shared } => {
648                if !this.notified.load(Ordering::Acquire) {
649                    let new_waker = cx.waker();
650                    if waker.will_wake(new_waker) {
651                        return Poll::Pending;
652                    }
653                    let mut state = shared.state.write();
654                    // Re-check after acquiring the lock — a concurrent insert
655                    // may have drained the waiters list in the meantime.
656                    if !this.notified.load(Ordering::Acquire) {
657                        let w = unsafe {
658                            state
659                                .waiters
660                                .iter_mut()
661                                .find(|w| w.is_waiter(&this.notified as _))
662                                .unwrap_unchecked()
663                        };
664                        *waker = new_waker.clone();
665                        *w = Waiter::Task {
666                            waker: new_waker.clone(),
667                            notified: &this.notified as *const AtomicBool,
668                        };
669                        return Poll::Pending;
670                    }
671                }
672                let JoinFutureState::Pending { shared, .. } =
673                    mem::replace(&mut this.state, JoinFutureState::Done)
674                else {
675                    unsafe { unreachable_unchecked() }
676                };
677                Poll::Ready(match PlaceholderGuard::handle_notification(shard, shared) {
678                    Ok(shared) => JoinResult::Filled(Some(shared)),
679                    Err(g) => JoinResult::Guard(g),
680                })
681            }
682            JoinFutureState::Done => panic!("Polled after ready"),
683        }
684    }
685}