From 710712746ac53f4581d6a2a66a0a789214a28ea3 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 29 Jun 2022 10:54:02 +0000 Subject: [PATCH 1/8] task: various small improvements to `LocalKey` --- tokio/src/task/task_local.rs | 295 +++++++++++++++++++++++++---------- 1 file changed, 211 insertions(+), 84 deletions(-) diff --git a/tokio/src/task/task_local.rs b/tokio/src/task/task_local.rs index 70fa967df5e..ffc8f0a1d47 100644 --- a/tokio/src/task/task_local.rs +++ b/tokio/src/task/task_local.rs @@ -1,4 +1,3 @@ -use pin_project_lite::pin_project; use std::cell::RefCell; use std::error::Error; use std::future::Future; @@ -79,7 +78,7 @@ macro_rules! __task_local_inner { /// A key for task-local data. /// -/// This type is generated by the `task_local!` macro. +/// This type is generated by the [`task_local!`] macro. /// /// Unlike [`std::thread::LocalKey`], `tokio::task::LocalKey` will /// _not_ lazily initialize the value on first access. Instead, the @@ -107,7 +106,9 @@ macro_rules! __task_local_inner { /// }).await; /// # } /// ``` +/// /// [`std::thread::LocalKey`]: struct@std::thread::LocalKey +/// [`task_local!`]: ../macro.task_local.html #[cfg_attr(docsrs, doc(cfg(feature = "rt")))] pub struct LocalKey { #[doc(hidden)] @@ -119,6 +120,11 @@ impl LocalKey { /// /// On completion of `scope`, the task-local will be dropped. /// + /// ### Panics + /// + /// If you poll the returned future inside a call to [`with`] or + /// [`try_with`] on the same `LocalKey`, then the call to `poll` will panic. + /// /// ### Examples /// /// ``` @@ -132,6 +138,9 @@ impl LocalKey { /// }).await; /// # } /// ``` + /// + /// [`with`]: fn@Self::with + /// [`try_with`]: fn@Self::try_with pub fn scope(&'static self, value: T, f: F) -> TaskLocalFuture where F: Future, @@ -139,7 +148,7 @@ impl LocalKey { TaskLocalFuture { local: self, slot: Some(value), - future: f, + future: Some(f), _pinned: PhantomPinned, } } @@ -148,6 +157,11 @@ impl LocalKey { /// /// On completion of `scope`, the task-local will be dropped. /// + /// ### Panics + /// + /// This method panics if called inside a call to [`with`] or [`try_with`] + /// on the same `LocalKey`. + /// /// ### Examples /// /// ``` @@ -161,34 +175,85 @@ impl LocalKey { /// }); /// # } /// ``` + /// + /// [`with`]: fn@Self::with + /// [`try_with`]: fn@Self::try_with + #[track_caller] pub fn sync_scope(&'static self, value: T, f: F) -> R where F: FnOnce() -> R, { - let scope = TaskLocalFuture { - local: self, - slot: Some(value), - future: (), - _pinned: PhantomPinned, - }; - crate::pin!(scope); - scope.with_task(|_| f()) + let mut value = Some(value); + match self.scope_inner(&mut value, f) { + Ok(res) => res, + Err(ScopeInnerErr::BorrowError) => { + panic!("sync_scope called while Task Local Storage is borrowed") + } + Err(ScopeInnerErr::AccessError) => { + panic!("cannot access a Task Local Storage value during or after destruction") + } + } + } + + fn scope_inner(&'static self, slot: &mut Option, f: F) -> Result + where + F: FnOnce() -> R, + { + struct Guard<'a, T: 'static> { + local: &'static LocalKey, + slot: &'a mut Option, + } + + impl<'a, T: 'static> Drop for Guard<'a, T> { + fn drop(&mut self) { + // This should not panic. + // + // We know that the RefCell was not borrowed before the call to + // `scope_inner`, so the only way for this to panic is if the + // closure has created but not destroyed a RefCell guard. + // However, we never give user-code access to the guards, so + // there's no way for user-code to forget to destroy a guard. + // + // The call to `with` also should not panic, since the + // thread-local wasn't destroyed when we first called + // `scope_inner`, and it shouldn't have gotten destroyed since + // then. + self.local.inner.with(|inner| { + let mut ref_mut = inner.borrow_mut(); + std::mem::swap(self.slot, &mut *ref_mut); + }); + } + } + + self.inner.try_with(|inner| { + inner + .try_borrow_mut() + .map(|mut ref_mut| std::mem::swap(slot, &mut *ref_mut)) + })??; + + let guard = Guard { local: self, slot }; + + let res = f(); + + drop(guard); + + Ok(res) } /// Accesses the current task-local and runs the provided closure. /// /// # Panics /// - /// This function will panic if not called within the context - /// of a future containing a task-local with the corresponding key. + /// This function will panic if the task local doesn't have a value set. + #[track_caller] pub fn with(&'static self, f: F) -> R where F: FnOnce(&T) -> R, { - self.try_with(f).expect( - "cannot access a Task Local Storage value \ - without setting it via `LocalKey::set`", - ) + match self.try_with(f) { + Ok(res) => res, + Err(_) => panic!("cannot access a Task Local Storage value without setting it first"), + } } /// Accesses the current task-local and runs the provided closure. @@ -200,19 +265,31 @@ impl LocalKey { where F: FnOnce(&T) -> R, { - self.inner.with(|v| { - if let Some(val) = v.borrow().as_ref() { - Ok(f(val)) - } else { - Err(AccessError { _private: () }) - } - }) + // If called after the thread-local storing the task-local is destroyed, + // then we are outside of a closure where the task-local is set. + // + // Therefore, it is correct to return an AccessError if `try_with` + // returns an error. + let try_with_res = self.inner.try_with(|v| { + // This call to `borrow` cannot panic because no user-defined code + // runs while a `borrow_mut` call is active. + v.borrow().as_ref().map(f) + }); + + match try_with_res { + Ok(Some(res)) => Ok(res), + Ok(None) | Err(_) => Err(AccessError { _private: () }), + } } } impl LocalKey { /// Returns a copy of the task-local value /// if the task-local value implements `Copy`. + /// + /// # Panics + /// + /// This function will panic if the task local doesn't have a value set. pub fn get(&'static self) -> T { self.with(|v| *v) } @@ -224,76 +301,109 @@ impl fmt::Debug for LocalKey { } } -pin_project! { - /// A future that sets a value `T` of a task local for the future `F` during - /// its execution. - /// - /// The value of the task-local must be `'static` and will be dropped on the - /// completion of the future. - /// - /// Created by the function [`LocalKey::scope`](self::LocalKey::scope). - /// - /// ### Examples - /// - /// ``` - /// # async fn dox() { - /// tokio::task_local! { - /// static NUMBER: u32; - /// } - /// - /// NUMBER.scope(1, async move { - /// println!("task local value: {}", NUMBER.get()); - /// }).await; - /// # } - /// ``` - pub struct TaskLocalFuture - where - T: 'static - { - local: &'static LocalKey, - slot: Option, - #[pin] - future: F, - #[pin] - _pinned: PhantomPinned, - } +/// A future that sets a value `T` of a task local for the future `F` during +/// its execution. +/// +/// The value of the task-local must be `'static` and will be dropped on the +/// completion of the future. +/// +/// Created by the function [`LocalKey::scope`](self::LocalKey::scope). +/// +/// ### Examples +/// +/// ``` +/// # async fn dox() { +/// tokio::task_local! { +/// static NUMBER: u32; +/// } +/// +/// NUMBER.scope(1, async move { +/// println!("task local value: {}", NUMBER.get()); +/// }).await; +/// # } +/// ``` +// Doesn't use pin_project due to custom Drop. +pub struct TaskLocalFuture +where + T: 'static, +{ + local: &'static LocalKey, + slot: Option, + future: Option, + _pinned: PhantomPinned, } -impl TaskLocalFuture { - fn with_task) -> R, R>(self: Pin<&mut Self>, f: F2) -> R { - struct Guard<'a, T: 'static> { - local: &'static LocalKey, - slot: &'a mut Option, - prev: Option, - } - - impl Drop for Guard<'_, T> { - fn drop(&mut self) { - let value = self.local.inner.with(|c| c.replace(self.prev.take())); - *self.slot = value; - } - } +impl Future for TaskLocalFuture { + type Output = F::Output; - let project = self.project(); - let val = project.slot.take(); + #[track_caller] + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // safety: The TaskLocalFuture struct is `!Unpin` so there is no way to + // move `self.future` from now on. + let this = unsafe { Pin::into_inner_unchecked(self) }; + let mut future_opt = unsafe { Pin::new_unchecked(&mut this.future) }; - let prev = project.local.inner.with(|c| c.replace(val)); + let res = + this.local + .scope_inner(&mut this.slot, || match future_opt.as_mut().as_pin_mut() { + Some(fut) => { + let res = fut.poll(cx); + if res.is_ready() { + future_opt.set(None); + } + Some(res) + } + None => None, + }); - let _guard = Guard { - prev, - slot: project.slot, - local: *project.local, - }; + match res { + Ok(Some(res)) => res, + Ok(None) => panic!("TaskLocalFuture polled after completion"), + Err(ScopeInnerErr::BorrowError) => { + panic!("TaskLocalFuture::poll called while task local is borrowed") + } + Err(ScopeInnerErr::AccessError) => { + panic!("cannot access a Task Local Storage value during or after destruction") + } + } + } +} - f(project.future) +impl Drop for TaskLocalFuture { + fn drop(&mut self) { + if std::mem::needs_drop::() && self.future.is_some() { + // Drop the future while the task-local is set, if possible. Otherwise + // the future is dropped normally when the `Option` field drops. + let future = &mut self.future; + let _ = self.local.scope_inner(&mut self.slot, || { + *future = None; + }); + } } } -impl Future for TaskLocalFuture { - type Output = F::Output; +impl fmt::Debug for TaskLocalFuture +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// Format the Option without Some. + struct TransparentOption<'a, T> { + value: &'a Option, + } + impl<'a, T: fmt::Debug> fmt::Debug for TransparentOption<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.value.as_ref() { + Some(value) => value.fmt(f), + // Hitting the None branch should not be possible. + None => f.pad(""), + } + } + } - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.with_task(|f| f.poll(cx)) + f.debug_struct("TaskLocalFuture") + .field("value", &TransparentOption { value: &self.slot }) + .finish() } } @@ -316,3 +426,20 @@ impl fmt::Display for AccessError { } impl Error for AccessError {} + +enum ScopeInnerErr { + BorrowError, + AccessError, +} + +impl From for ScopeInnerErr { + fn from(_: std::cell::BorrowMutError) -> Self { + Self::BorrowError + } +} + +impl From for ScopeInnerErr { + fn from(_: std::thread::AccessError) -> Self { + Self::AccessError + } +} From cf8633be550090903b9156764d1aebdde95be115 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 29 Jun 2022 13:39:45 +0000 Subject: [PATCH 2/8] Add test Co-authored-by: Kitsu --- tokio/tests/task_local.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tokio/tests/task_local.rs b/tokio/tests/task_local.rs index 811d63ea0f8..78a2f7180ee 100644 --- a/tokio/tests/task_local.rs +++ b/tokio/tests/task_local.rs @@ -31,3 +31,34 @@ async fn local() { j2.await.unwrap(); j3.await.unwrap(); } + +tokio::task_local! { + static KEY: u32; +} + +struct Guard(u32); +impl Drop for Guard { + fn drop(&mut self) { + assert_eq!(KEY.try_with(|x| *x), Ok(self.0)); + } +} + +#[tokio::test] +async fn task_local_available_on_drop() { + let (tx, rx) = tokio::sync::oneshot::channel(); + + let h = tokio::spawn(KEY.scope(42, async move { + let _g = Guard(42); + let _ = tx.send(()); + std::future::pending::<()>().await; + })); + + rx.await.unwrap(); + + h.abort(); + + let err = h.await.unwrap_err(); + if let Ok(panic) = err.try_into_panic() { + std::panic::resume_unwind(panic); + } +} From c5f39cab8e0ac308165136e24c2c564cd2832631 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Thu, 30 Jun 2022 08:39:20 +0000 Subject: [PATCH 3/8] Address review --- tokio/src/task/task_local.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tokio/src/task/task_local.rs b/tokio/src/task/task_local.rs index ffc8f0a1d47..00b3d5ab88a 100644 --- a/tokio/src/task/task_local.rs +++ b/tokio/src/task/task_local.rs @@ -4,7 +4,7 @@ use std::future::Future; use std::marker::PhantomPinned; use std::pin::Pin; use std::task::{Context, Poll}; -use std::{fmt, thread}; +use std::{fmt, mem, thread}; /// Declares a new task-local key of type [`tokio::task::LocalKey`]. /// @@ -187,10 +187,10 @@ impl LocalKey { match self.scope_inner(&mut value, f) { Ok(res) => res, Err(ScopeInnerErr::BorrowError) => { - panic!("sync_scope called while Task Local Storage is borrowed") + panic!("`LocalKey::sync_scope` called while task-local storage is borrowed") } Err(ScopeInnerErr::AccessError) => { - panic!("cannot access a Task Local Storage value during or after destruction") + panic!("cannot access a task-local storage value during or after destruction") } } } @@ -220,7 +220,7 @@ impl LocalKey { // then. self.local.inner.with(|inner| { let mut ref_mut = inner.borrow_mut(); - std::mem::swap(self.slot, &mut *ref_mut); + mem::swap(self.slot, &mut *ref_mut); }); } } @@ -228,7 +228,7 @@ impl LocalKey { self.inner.try_with(|inner| { inner .try_borrow_mut() - .map(|mut ref_mut| std::mem::swap(slot, &mut *ref_mut)) + .map(|mut ref_mut| mem::swap(slot, &mut *ref_mut)) })??; let guard = Guard { local: self, slot }; @@ -252,7 +252,7 @@ impl LocalKey { { match self.try_with(f) { Ok(res) => res, - Err(_) => panic!("cannot access a Task Local Storage value without setting it first"), + Err(_) => panic!("cannot access a task-local storage value without setting it first"), } } @@ -358,12 +358,12 @@ impl Future for TaskLocalFuture { match res { Ok(Some(res)) => res, - Ok(None) => panic!("TaskLocalFuture polled after completion"), + Ok(None) => panic!("`TaskLocalFuture` polled after completion"), Err(ScopeInnerErr::BorrowError) => { - panic!("TaskLocalFuture::poll called while task local is borrowed") + panic!("`TaskLocalFuture::poll` called while task-local storage is borrowed") } Err(ScopeInnerErr::AccessError) => { - panic!("cannot access a Task Local Storage value during or after destruction") + panic!("cannot access a task-local storage value during or after destruction") } } } @@ -371,7 +371,7 @@ impl Future for TaskLocalFuture { impl Drop for TaskLocalFuture { fn drop(&mut self) { - if std::mem::needs_drop::() && self.future.is_some() { + if mem::needs_drop::() && self.future.is_some() { // Drop the future while the task-local is set, if possible. Otherwise // the future is dropped normally when the `Option` field drops. let future = &mut self.future; From 3e3cc9153238123fd77c699ebca6333084968d23 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Thu, 30 Jun 2022 08:49:56 +0000 Subject: [PATCH 4/8] Combine shared panic messages --- tokio/src/task/task_local.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/tokio/src/task/task_local.rs b/tokio/src/task/task_local.rs index 00b3d5ab88a..6c8def8e5ee 100644 --- a/tokio/src/task/task_local.rs +++ b/tokio/src/task/task_local.rs @@ -186,12 +186,7 @@ impl LocalKey { let mut value = Some(value); match self.scope_inner(&mut value, f) { Ok(res) => res, - Err(ScopeInnerErr::BorrowError) => { - panic!("`LocalKey::sync_scope` called while task-local storage is borrowed") - } - Err(ScopeInnerErr::AccessError) => { - panic!("cannot access a task-local storage value during or after destruction") - } + Err(err) => std::panic::panic_any(err.as_str()), } } @@ -359,12 +354,7 @@ impl Future for TaskLocalFuture { match res { Ok(Some(res)) => res, Ok(None) => panic!("`TaskLocalFuture` polled after completion"), - Err(ScopeInnerErr::BorrowError) => { - panic!("`TaskLocalFuture::poll` called while task-local storage is borrowed") - } - Err(ScopeInnerErr::AccessError) => { - panic!("cannot access a task-local storage value during or after destruction") - } + Err(err) => std::panic::panic_any(err.as_str()), } } } @@ -432,6 +422,15 @@ enum ScopeInnerErr { AccessError, } +impl ScopeInnerErr { + fn as_str(self) -> &'static str { + match self { + Self::BorrowError => "cannot enter a task-local scope while the task-local storage is borrowed", + Self::AccessError => "cannot enter a task-local scope during or after destruction of the underlying thread-local", + } + } +} + impl From for ScopeInnerErr { fn from(_: std::cell::BorrowMutError) -> Self { Self::BorrowError From 5f9ab00964869b0ed36f9d1cb97116208c7b2bf6 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Thu, 30 Jun 2022 08:53:44 +0000 Subject: [PATCH 5/8] clippy --- tokio/src/task/task_local.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tokio/src/task/task_local.rs b/tokio/src/task/task_local.rs index 6c8def8e5ee..c90893739c2 100644 --- a/tokio/src/task/task_local.rs +++ b/tokio/src/task/task_local.rs @@ -423,7 +423,7 @@ enum ScopeInnerErr { } impl ScopeInnerErr { - fn as_str(self) -> &'static str { + fn as_str(&self) -> &'static str { match self { Self::BorrowError => "cannot enter a task-local scope while the task-local storage is borrowed", Self::AccessError => "cannot enter a task-local scope during or after destruction of the underlying thread-local", From 1e94d2174b6b6cde2fef61843c270a3f690861ad Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Thu, 30 Jun 2022 09:05:52 +0000 Subject: [PATCH 6/8] Fix panic silliness --- tokio/src/task/task_local.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tokio/src/task/task_local.rs b/tokio/src/task/task_local.rs index c90893739c2..e92b05fdc98 100644 --- a/tokio/src/task/task_local.rs +++ b/tokio/src/task/task_local.rs @@ -186,7 +186,7 @@ impl LocalKey { let mut value = Some(value); match self.scope_inner(&mut value, f) { Ok(res) => res, - Err(err) => std::panic::panic_any(err.as_str()), + Err(err) => err.panic(), } } @@ -354,7 +354,7 @@ impl Future for TaskLocalFuture { match res { Ok(Some(res)) => res, Ok(None) => panic!("`TaskLocalFuture` polled after completion"), - Err(err) => std::panic::panic_any(err.as_str()), + Err(err) => err.panic(), } } } @@ -423,10 +423,10 @@ enum ScopeInnerErr { } impl ScopeInnerErr { - fn as_str(&self) -> &'static str { + fn panic(&self) -> ! { match self { - Self::BorrowError => "cannot enter a task-local scope while the task-local storage is borrowed", - Self::AccessError => "cannot enter a task-local scope during or after destruction of the underlying thread-local", + Self::BorrowError => panic!("cannot enter a task-local scope while the task-local storage is borrowed"), + Self::AccessError => panic!("cannot enter a task-local scope during or after destruction of the underlying thread-local"), } } } From 3ae1c211884567568b52d1ac2f0a974532c2bc43 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Thu, 30 Jun 2022 09:22:15 +0000 Subject: [PATCH 7/8] Add another test --- tokio/tests/task_local.rs | 102 +++++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 24 deletions(-) diff --git a/tokio/tests/task_local.rs b/tokio/tests/task_local.rs index 78a2f7180ee..3b175964150 100644 --- a/tokio/tests/task_local.rs +++ b/tokio/tests/task_local.rs @@ -1,12 +1,16 @@ #![cfg(feature = "full")] - -tokio::task_local! { - static REQ_ID: u32; - pub static FOO: bool; -} +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; +use tokio::sync::oneshot; #[tokio::test(flavor = "multi_thread")] async fn local() { + tokio::task_local! { + static REQ_ID: u32; + pub static FOO: bool; + } + let j1 = tokio::spawn(REQ_ID.scope(1, async move { assert_eq!(REQ_ID.get(), 1); assert_eq!(REQ_ID.get(), 1); @@ -32,33 +36,83 @@ async fn local() { j3.await.unwrap(); } -tokio::task_local! { - static KEY: u32; -} +#[tokio::test] +async fn task_local_available_on_abort() { + tokio::task_local! { + static KEY: u32; + } -struct Guard(u32); -impl Drop for Guard { - fn drop(&mut self) { - assert_eq!(KEY.try_with(|x| *x), Ok(self.0)); + struct MyFuture { + tx_poll: Option>, + tx_drop: Option>, } -} + impl Future for MyFuture { + type Output = (); -#[tokio::test] -async fn task_local_available_on_drop() { - let (tx, rx) = tokio::sync::oneshot::channel(); + fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> { + if let Some(tx_poll) = self.tx_poll.take() { + tx_poll.send(()); + } + Poll::Pending + } + } + impl Drop for MyFuture { + fn drop(&mut self) { + let _ = self.tx_drop.take().unwrap().send(KEY.get()); + } + } - let h = tokio::spawn(KEY.scope(42, async move { - let _g = Guard(42); - let _ = tx.send(()); - std::future::pending::<()>().await; - })); + let (tx_drop, rx_drop) = oneshot::channel(); + let (tx_poll, rx_poll) = oneshot::channel(); - rx.await.unwrap(); + let h = tokio::spawn(KEY.scope( + 42, + MyFuture { + tx_poll: Some(tx_poll), + tx_drop: Some(tx_drop), + }, + )); + rx_poll.await.unwrap(); h.abort(); + assert_eq!(rx_drop.await.unwrap(), 42); let err = h.await.unwrap_err(); - if let Ok(panic) = err.try_into_panic() { - std::panic::resume_unwind(panic); + if !err.is_cancelled() { + if let Ok(panic) = err.try_into_panic() { + std::panic::resume_unwind(panic); + } else { + panic!(); + } } } + +#[tokio::test] +async fn task_local_available_on_completion_drop() { + tokio::task_local! { + static KEY: u32; + } + + struct MyFuture { + tx: Option>, + } + impl Future for MyFuture { + type Output = (); + + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> { + Poll::Ready(()) + } + } + impl Drop for MyFuture { + fn drop(&mut self) { + let _ = self.tx.take().unwrap().send(KEY.get()); + } + } + + let (tx, rx) = oneshot::channel(); + + let h = tokio::spawn(KEY.scope(42, MyFuture { tx: Some(tx) })); + + assert_eq!(rx.await.unwrap(), 42); + h.await.unwrap(); +} From 6abfc8d08c5a73cbab3760b505f88ac139f51059 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Thu, 30 Jun 2022 09:31:53 +0000 Subject: [PATCH 8/8] Fix warning --- tokio/tests/task_local.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tokio/tests/task_local.rs b/tokio/tests/task_local.rs index 3b175964150..4e33f29be43 100644 --- a/tokio/tests/task_local.rs +++ b/tokio/tests/task_local.rs @@ -51,7 +51,7 @@ async fn task_local_available_on_abort() { fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> { if let Some(tx_poll) = self.tx_poll.take() { - tx_poll.send(()); + let _ = tx_poll.send(()); } Poll::Pending }