mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-07 00:00:08 +02:00
cleanup: review and cleanup unsafe code
I did some things the other day that I don't like the next morning. Cleaning those up in this commit. Also fixes a flaky test I added.
This commit is contained in:
@@ -475,6 +475,10 @@ impl Handle {
|
|||||||
unsafe { self.spawn_local_named_inner(future, meta, user_data) }
|
unsafe { self.spawn_local_named_inner(future, meta, user_data) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This must only be called in `LocalRuntime` if the runtime has been verified to be owned
|
||||||
|
/// by the current thread.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
unsafe fn spawn_local_named_inner<F>(
|
unsafe fn spawn_local_named_inner<F>(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -475,20 +475,20 @@ impl Handle {
|
|||||||
F::Output: Send + 'static,
|
F::Output: Send + 'static,
|
||||||
{
|
{
|
||||||
#[cfg(tokio_unstable)]
|
#[cfg(tokio_unstable)]
|
||||||
let parent = task::current_task_meta();
|
let (handle, notified) = task::with_current_task_meta(|parent| {
|
||||||
#[cfg(tokio_unstable)]
|
me.shared.owned.bind_with_spawn_hook(
|
||||||
let (handle, notified) = me.shared.owned.bind_with_spawn_hook(
|
future,
|
||||||
future,
|
me.clone(),
|
||||||
me.clone(),
|
id,
|
||||||
id,
|
spawned_at,
|
||||||
spawned_at,
|
user_data,
|
||||||
user_data,
|
|task| {
|
||||||
|task| {
|
// Safety: the task is freshly allocated and not published yet.
|
||||||
// Safety: the task is freshly allocated and not published yet.
|
let mut meta = unsafe { task.task_meta() };
|
||||||
let mut meta = unsafe { task.task_meta() };
|
me.task_hooks.spawn(&mut meta, parent);
|
||||||
me.task_hooks.spawn(&mut meta, parent);
|
},
|
||||||
},
|
)
|
||||||
);
|
});
|
||||||
#[cfg(not(tokio_unstable))]
|
#[cfg(not(tokio_unstable))]
|
||||||
let (handle, notified) = me.shared.owned.bind(future, me.clone(), id, spawned_at);
|
let (handle, notified) = me.shared.owned.bind(future, me.clone(), id, spawned_at);
|
||||||
|
|
||||||
@@ -524,26 +524,25 @@ impl Handle {
|
|||||||
F: crate::future::Future + 'static,
|
F: crate::future::Future + 'static,
|
||||||
F::Output: 'static,
|
F::Output: 'static,
|
||||||
{
|
{
|
||||||
// Safety: the caller guarantees that this is only called on a `LocalRuntime`.
|
|
||||||
#[cfg(tokio_unstable)]
|
#[cfg(tokio_unstable)]
|
||||||
let parent = task::current_task_meta();
|
let (handle, notified) = task::with_current_task_meta(|parent| {
|
||||||
#[cfg(tokio_unstable)]
|
let before_bind = |task: &Task<Arc<Handle>>| {
|
||||||
let before_bind = |task: &Task<Arc<Handle>>| {
|
// Safety: the task is freshly allocated and not published yet.
|
||||||
// Safety: the task is freshly allocated and not published yet.
|
let mut meta = unsafe { task.task_meta() };
|
||||||
let mut meta = unsafe { task.task_meta() };
|
me.task_hooks.spawn(&mut meta, parent);
|
||||||
me.task_hooks.spawn(&mut meta, parent);
|
};
|
||||||
};
|
// Safety: the caller guarantees that this is only called on a `LocalRuntime`.
|
||||||
#[cfg(tokio_unstable)]
|
unsafe {
|
||||||
let (handle, notified) = unsafe {
|
me.shared.owned.bind_local_with_spawn_hook(
|
||||||
me.shared.owned.bind_local_with_spawn_hook(
|
future,
|
||||||
future,
|
me.clone(),
|
||||||
me.clone(),
|
id,
|
||||||
id,
|
spawned_at,
|
||||||
spawned_at,
|
user_data,
|
||||||
user_data,
|
before_bind,
|
||||||
before_bind,
|
)
|
||||||
)
|
}
|
||||||
};
|
});
|
||||||
#[cfg(not(tokio_unstable))]
|
#[cfg(not(tokio_unstable))]
|
||||||
let (handle, notified) = unsafe {
|
let (handle, notified) = unsafe {
|
||||||
me.shared
|
me.shared
|
||||||
|
|||||||
@@ -98,20 +98,20 @@ impl Handle {
|
|||||||
T::Output: Send + 'static,
|
T::Output: Send + 'static,
|
||||||
{
|
{
|
||||||
#[cfg(tokio_unstable)]
|
#[cfg(tokio_unstable)]
|
||||||
let parent = task::current_task_meta();
|
let (handle, notified) = task::with_current_task_meta(|parent| {
|
||||||
#[cfg(tokio_unstable)]
|
me.shared.owned.bind_with_spawn_hook(
|
||||||
let (handle, notified) = me.shared.owned.bind_with_spawn_hook(
|
future,
|
||||||
future,
|
me.clone(),
|
||||||
me.clone(),
|
id,
|
||||||
id,
|
spawned_at,
|
||||||
spawned_at,
|
user_data,
|
||||||
user_data,
|
|task| {
|
||||||
|task| {
|
// Safety: the task is freshly allocated and not published yet.
|
||||||
// Safety: the task is freshly allocated and not published yet.
|
let mut meta = unsafe { task.task_meta() };
|
||||||
let mut meta = unsafe { task.task_meta() };
|
me.task_hooks.spawn(&mut meta, parent);
|
||||||
me.task_hooks.spawn(&mut meta, parent);
|
},
|
||||||
},
|
)
|
||||||
);
|
});
|
||||||
#[cfg(not(tokio_unstable))]
|
#[cfg(not(tokio_unstable))]
|
||||||
let (handle, notified) = me.shared.owned.bind(future, me.clone(), id, spawned_at);
|
let (handle, notified) = me.shared.owned.bind(future, me.clone(), id, spawned_at);
|
||||||
|
|
||||||
|
|||||||
@@ -364,7 +364,11 @@ impl<T: Future, S: Schedule> Core<T, S> {
|
|||||||
///
|
///
|
||||||
/// `self` must also be pinned. This is handled by storing the task on the
|
/// `self` must also be pinned. This is handled by storing the task on the
|
||||||
/// heap.
|
/// heap.
|
||||||
pub(super) fn poll(
|
///
|
||||||
|
/// When `tokio_unstable` is enabled, `header` must point to the header for
|
||||||
|
/// this exact task allocation, and the allocation must remain live until
|
||||||
|
/// this function returns.
|
||||||
|
pub(super) unsafe fn poll(
|
||||||
&self,
|
&self,
|
||||||
#[cfg(tokio_unstable)] header: NonNull<Header>,
|
#[cfg(tokio_unstable)] header: NonNull<Header>,
|
||||||
mut cx: Context<'_>,
|
mut cx: Context<'_>,
|
||||||
|
|||||||
@@ -222,8 +222,9 @@ where
|
|||||||
|
|
||||||
#[cfg(tokio_unstable)]
|
#[cfg(tokio_unstable)]
|
||||||
{
|
{
|
||||||
// Safety: the task is in the RUNNING state, which excludes
|
// Safety: the task is in the RUNNING state, so shutdown
|
||||||
// concurrent shutdown and termination metadata access.
|
// cannot take ownership of the task contents and termination
|
||||||
|
// cannot access hook data concurrently.
|
||||||
let mut task_meta = unsafe { self.task_meta() };
|
let mut task_meta = unsafe { self.task_meta() };
|
||||||
let res = panic::catch_unwind(panic::AssertUnwindSafe(|| {
|
let res = panic::catch_unwind(panic::AssertUnwindSafe(|| {
|
||||||
self.core()
|
self.core()
|
||||||
@@ -232,7 +233,9 @@ where
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
if let Err(panic) = res {
|
if let Err(panic) = res {
|
||||||
poll_hook_panic(self.core(), panic);
|
// Safety: the task is still in the RUNNING state, so we
|
||||||
|
// have exclusive access to the future/output storage.
|
||||||
|
unsafe { poll_hook_panic(self.core(), panic) };
|
||||||
return PollFuture::Complete;
|
return PollFuture::Complete;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,18 +248,23 @@ where
|
|||||||
let header_ptr = self.header_ptr();
|
let header_ptr = self.header_ptr();
|
||||||
let waker_ref = waker_ref::<S>(&header_ptr);
|
let waker_ref = waker_ref::<S>(&header_ptr);
|
||||||
let cx = Context::from_waker(&waker_ref);
|
let cx = Context::from_waker(&waker_ref);
|
||||||
let res = poll_future(
|
// Safety: `transition_to_running` succeeded, so this thread has
|
||||||
self.core(),
|
// exclusive access to the future/output storage. The header pointer
|
||||||
#[cfg(tokio_unstable)]
|
// comes from this harness and remains live while the task is running.
|
||||||
header_ptr,
|
let res = unsafe {
|
||||||
cx,
|
poll_future(
|
||||||
);
|
self.core(),
|
||||||
|
#[cfg(tokio_unstable)]
|
||||||
|
header_ptr,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(tokio_unstable)]
|
#[cfg(tokio_unstable)]
|
||||||
{
|
{
|
||||||
// Safety: the task is still in the RUNNING state, which
|
// Safety: the task is still in the RUNNING state, so
|
||||||
// excludes concurrent shutdown and termination metadata
|
// shutdown cannot take ownership of the task contents and
|
||||||
// access.
|
// termination cannot access hook data concurrently.
|
||||||
let mut task_meta = unsafe { self.task_meta() };
|
let mut task_meta = unsafe { self.task_meta() };
|
||||||
let hook_res = panic::catch_unwind(panic::AssertUnwindSafe(|| {
|
let hook_res = panic::catch_unwind(panic::AssertUnwindSafe(|| {
|
||||||
self.core()
|
self.core()
|
||||||
@@ -265,7 +273,9 @@ where
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
if let Err(panic) = hook_res {
|
if let Err(panic) = hook_res {
|
||||||
poll_hook_panic(self.core(), panic);
|
// Safety: the task is still in the RUNNING state, so we
|
||||||
|
// have exclusive access to the future/output storage.
|
||||||
|
unsafe { poll_hook_panic(self.core(), panic) };
|
||||||
return PollFuture::Complete;
|
return PollFuture::Complete;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -578,8 +588,14 @@ fn panic_result_to_join_error(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert a poll hook panic into the task output.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The caller must have exclusive access to the task's future/output storage,
|
||||||
|
/// such as by holding the task in the RUNNING state.
|
||||||
#[cfg(tokio_unstable)]
|
#[cfg(tokio_unstable)]
|
||||||
fn poll_hook_panic<T: Future, S: Schedule>(
|
unsafe fn poll_hook_panic<T: Future, S: Schedule>(
|
||||||
core: &Core<T, S>,
|
core: &Core<T, S>,
|
||||||
hook_panic: Box<dyn Any + Send + 'static>,
|
hook_panic: Box<dyn Any + Send + 'static>,
|
||||||
) {
|
) {
|
||||||
@@ -602,7 +618,15 @@ fn poll_hook_panic<T: Future, S: Schedule>(
|
|||||||
|
|
||||||
/// Polls the future. If the future completes, the output is written to the
|
/// Polls the future. If the future completes, the output is written to the
|
||||||
/// stage field.
|
/// stage field.
|
||||||
fn poll_future<T: Future, S: Schedule>(
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The caller must satisfy the mutual-exclusion requirements of `Core::poll`.
|
||||||
|
///
|
||||||
|
/// When `tokio_unstable` is enabled, `header` must point to the header for this
|
||||||
|
/// exact task allocation, and the allocation must remain live until this
|
||||||
|
/// function returns.
|
||||||
|
unsafe fn poll_future<T: Future, S: Schedule>(
|
||||||
core: &Core<T, S>,
|
core: &Core<T, S>,
|
||||||
#[cfg(tokio_unstable)] header: NonNull<Header>,
|
#[cfg(tokio_unstable)] header: NonNull<Header>,
|
||||||
cx: Context<'_>,
|
cx: Context<'_>,
|
||||||
@@ -620,11 +644,15 @@ fn poll_future<T: Future, S: Schedule>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let guard = Guard { core };
|
let guard = Guard { core };
|
||||||
let res = guard.core.poll(
|
// Safety: the caller guarantees the mutual-exclusion requirements of
|
||||||
#[cfg(tokio_unstable)]
|
// `Core::poll` and that `header` identifies this live task allocation.
|
||||||
header,
|
let res = unsafe {
|
||||||
cx,
|
guard.core.poll(
|
||||||
);
|
#[cfg(tokio_unstable)]
|
||||||
|
header,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
};
|
||||||
mem::forget(guard);
|
mem::forget(guard);
|
||||||
res
|
res
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -669,13 +669,18 @@ impl SpawnLocation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(tokio_unstable)]
|
#[cfg(tokio_unstable)]
|
||||||
pub(crate) fn current_task_meta<'meta>() -> Option<crate::runtime::TaskMetaRef<'meta>> {
|
pub(crate) fn with_current_task_meta<R>(
|
||||||
let ptr = crate::runtime::context::current_task()?;
|
f: impl for<'meta> FnOnce(Option<crate::runtime::TaskMetaRef<'meta>>) -> R,
|
||||||
|
) -> R {
|
||||||
|
let Some(ptr) = crate::runtime::context::current_task() else {
|
||||||
|
return f(None);
|
||||||
|
};
|
||||||
|
|
||||||
// Safety: the context stores this pointer only while the referenced task is
|
// Safety: the context stores this pointer only while the referenced task is
|
||||||
// being polled, so the allocation is alive for the duration of this call.
|
// being polled, so the allocation is alive for this synchronous call.
|
||||||
let raw = unsafe { RawTask::from_raw(ptr.cast()) };
|
let raw = unsafe { RawTask::from_raw(ptr.cast()) };
|
||||||
// Safety: parent metadata is exposed read-only during synchronous spawn
|
// Safety: parent metadata is exposed read-only during this synchronous call
|
||||||
// hook invocation while no mutable parent hook metadata is live.
|
// while no mutable parent hook metadata is live. The closure-bound lifetime
|
||||||
Some(unsafe { raw.task_meta_ref() })
|
// prevents references exposed through the metadata from escaping.
|
||||||
|
f(Some(unsafe { raw.task_meta_ref() }))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -278,8 +278,8 @@ impl RawTask {
|
|||||||
/// The task allocation must be live, and the returned metadata must have
|
/// The task allocation must be live, and the returned metadata must have
|
||||||
/// exclusive access to hook data for as long as it can expose mutable references.
|
/// exclusive access to hook data for as long as it can expose mutable references.
|
||||||
pub(crate) unsafe fn task_meta<'meta>(&self) -> crate::runtime::TaskMeta<'meta> {
|
pub(crate) unsafe fn task_meta<'meta>(&self) -> crate::runtime::TaskMeta<'meta> {
|
||||||
// Safety: `self` holds a live task reference, and callers use the
|
// Safety: the caller guarantees that the task allocation is live and that
|
||||||
// metadata only for the current hook invocation.
|
// the returned metadata has exclusive access to hook data.
|
||||||
unsafe {
|
unsafe {
|
||||||
crate::runtime::TaskMeta::new(
|
crate::runtime::TaskMeta::new(
|
||||||
Header::get_id(self.ptr),
|
Header::get_id(self.ptr),
|
||||||
@@ -295,8 +295,8 @@ impl RawTask {
|
|||||||
/// The task allocation must be live, and hook data must not be mutated while
|
/// The task allocation must be live, and hook data must not be mutated while
|
||||||
/// references exposed through the returned metadata are live.
|
/// references exposed through the returned metadata are live.
|
||||||
pub(crate) unsafe fn task_meta_ref<'meta>(&self) -> crate::runtime::TaskMetaRef<'meta> {
|
pub(crate) unsafe fn task_meta_ref<'meta>(&self) -> crate::runtime::TaskMetaRef<'meta> {
|
||||||
// Safety: `self` holds a live task reference, and this only exposes
|
// Safety: the caller guarantees that the task allocation is live and that
|
||||||
// shared access to task data.
|
// hook data is not mutated while exposed references are live.
|
||||||
unsafe {
|
unsafe {
|
||||||
crate::runtime::TaskMetaRef::new(
|
crate::runtime::TaskMetaRef::new(
|
||||||
Header::get_id(self.ptr),
|
Header::get_id(self.ptr),
|
||||||
|
|||||||
@@ -215,8 +215,11 @@ pub struct TaskMetaRef<'a> {
|
|||||||
impl<'a> TaskMetaRef<'a> {
|
impl<'a> TaskMetaRef<'a> {
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// If `user_data` is present, it must point to live task storage for the
|
/// If `user_data` is present, it must point to live task storage for the duration
|
||||||
/// duration of any references exposed through this metadata value.
|
/// of any references exposed through this metadata value.
|
||||||
|
///
|
||||||
|
/// While any references exposed through this metadata value are live, the task
|
||||||
|
/// data must not be mutably accessed, replaced, cleared, or dropped.
|
||||||
#[cfg(tokio_unstable)]
|
#[cfg(tokio_unstable)]
|
||||||
pub(crate) unsafe fn new(
|
pub(crate) unsafe fn new(
|
||||||
id: super::task::Id,
|
id: super::task::Id,
|
||||||
@@ -251,7 +254,8 @@ impl<'a> TaskMetaRef<'a> {
|
|||||||
let user_data = self.user_data?;
|
let user_data = self.user_data?;
|
||||||
|
|
||||||
// Safety: `TaskMetaRef` is only constructed while the task allocation is
|
// Safety: `TaskMetaRef` is only constructed while the task allocation is
|
||||||
// known to be alive, and it does not expose mutation.
|
// known to be alive. Its constructor requires that the data is not mutated
|
||||||
|
// while exposed references are live.
|
||||||
unsafe { user_data.as_ref().as_ref()?.downcast_ref::<T>() }
|
unsafe { user_data.as_ref().as_ref()?.downcast_ref::<T>() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -549,13 +549,12 @@ fn task_builder_data_is_visible_to_hooks() {
|
|||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
#[test]
|
#[test]
|
||||||
fn task_builder_data_is_not_dropped_for_spawn_blocking() {
|
fn task_builder_data_is_not_dropped_for_spawn_blocking() {
|
||||||
let terminated = Arc::new(Mutex::new(Vec::new()));
|
let (terminated_tx, terminated_rx) = std::sync::mpsc::channel();
|
||||||
let terminated2 = Arc::clone(&terminated);
|
|
||||||
|
|
||||||
let runtime = Builder::new_current_thread()
|
let runtime = Builder::new_current_thread()
|
||||||
.on_task_terminate(move |meta| {
|
.on_task_terminate(move |meta| {
|
||||||
if let Some(value) = meta.take_data::<usize>() {
|
if let Some(value) = meta.take_data::<usize>() {
|
||||||
terminated2.lock().unwrap().push(*value);
|
terminated_tx.send(*value).unwrap();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
@@ -570,7 +569,10 @@ fn task_builder_data_is_not_dropped_for_spawn_blocking() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(*terminated.lock().unwrap(), vec![7]);
|
let terminated = terminated_rx
|
||||||
|
.recv_timeout(Duration::from_secs(5))
|
||||||
|
.expect("spawn_blocking task terminate hook did not receive task data");
|
||||||
|
assert_eq!(terminated, 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mk_spawn_location_hook(
|
fn mk_spawn_location_hook(
|
||||||
|
|||||||
Reference in New Issue
Block a user