forked from mirror/async-std
implement task locals
This commit is contained in:
parent
75ab7219df
commit
b96afc41dc
7 changed files with 173 additions and 200 deletions
|
@ -1,8 +1,6 @@
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
use kv_log_macro::trace;
|
use crate::task::Builder;
|
||||||
|
|
||||||
use crate::task::Task;
|
|
||||||
|
|
||||||
/// Spawns a task and blocks the current thread on its result.
|
/// Spawns a task and blocks the current thread on its result.
|
||||||
///
|
///
|
||||||
|
@ -31,33 +29,5 @@ pub fn block_on<F, T>(future: F) -> T
|
||||||
where
|
where
|
||||||
F: Future<Output = T>,
|
F: Future<Output = T>,
|
||||||
{
|
{
|
||||||
// Create a new task handle.
|
Builder::new().blocking(future)
|
||||||
let task = Task::new(None);
|
|
||||||
|
|
||||||
// Log this `block_on` operation.
|
|
||||||
trace!("block_on", {
|
|
||||||
task_id: task.id().0,
|
|
||||||
parent_task_id: Task::get_current(|t| t.id().0).unwrap_or(0),
|
|
||||||
});
|
|
||||||
|
|
||||||
let wrapped_future = async move {
|
|
||||||
// Drop task-locals on exit.
|
|
||||||
defer! {
|
|
||||||
Task::get_current(|t| unsafe { t.drop_locals() });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log completion on exit.
|
|
||||||
defer! {
|
|
||||||
trace!("completed", {
|
|
||||||
task_id: Task::get_current(|t| t.id().0),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
future.await
|
|
||||||
};
|
|
||||||
|
|
||||||
once_cell::sync::Lazy::force(&crate::rt::RUNTIME);
|
|
||||||
|
|
||||||
// Run the future as a task.
|
|
||||||
unsafe { Task::set_current(&task, || smol::block_on(wrapped_future)) }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use kv_log_macro::trace;
|
use kv_log_macro::trace;
|
||||||
|
|
||||||
use crate::io;
|
use crate::io;
|
||||||
use crate::task::{JoinHandle, Task};
|
use crate::task::{JoinHandle, Task, TaskLocalsWrapper};
|
||||||
|
|
||||||
/// Task builder that configures the settings of a new task.
|
/// Task builder that configures the settings of a new task.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
@ -25,41 +28,83 @@ impl Builder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build<F, T>(self, future: F) -> SupportTaskLocals<F>
|
||||||
|
where
|
||||||
|
F: Future<Output = T>,
|
||||||
|
{
|
||||||
|
let name = self.name.map(Arc::new);
|
||||||
|
|
||||||
|
// Create a new task handle.
|
||||||
|
let task = Task::new(name);
|
||||||
|
|
||||||
|
once_cell::sync::Lazy::force(&crate::rt::RUNTIME);
|
||||||
|
|
||||||
|
let tag = TaskLocalsWrapper::new(task.clone());
|
||||||
|
|
||||||
|
// FIXME: do not require all futures to be boxed.
|
||||||
|
SupportTaskLocals {
|
||||||
|
tag,
|
||||||
|
future: Box::pin(future),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Spawns a task with the configured settings.
|
/// Spawns a task with the configured settings.
|
||||||
pub fn spawn<F, T>(self, future: F) -> io::Result<JoinHandle<T>>
|
pub fn spawn<F, T>(self, future: F) -> io::Result<JoinHandle<T>>
|
||||||
where
|
where
|
||||||
F: Future<Output = T> + Send + 'static,
|
F: Future<Output = T> + Send + 'static,
|
||||||
T: Send + 'static,
|
T: Send + 'static,
|
||||||
{
|
{
|
||||||
// Create a new task handle.
|
let wrapped = self.build(future);
|
||||||
let task = Task::new(self.name);
|
|
||||||
|
|
||||||
// Log this `spawn` operation.
|
// Log this `spawn` operation.
|
||||||
trace!("spawn", {
|
trace!("spawn", {
|
||||||
task_id: task.id().0,
|
task_id: wrapped.tag.id().0,
|
||||||
parent_task_id: Task::get_current(|t| t.id().0).unwrap_or(0),
|
parent_task_id: TaskLocalsWrapper::get_current(|t| t.id().0).unwrap_or(0),
|
||||||
});
|
});
|
||||||
|
|
||||||
let wrapped_future = async move {
|
let task = wrapped.tag.task().clone();
|
||||||
// Drop task-locals on exit.
|
let smol_task = smol::Task::spawn(wrapped).detach();
|
||||||
defer! {
|
|
||||||
Task::get_current(|t| unsafe { t.drop_locals() });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log completion on exit.
|
|
||||||
defer! {
|
|
||||||
trace!("completed", {
|
|
||||||
task_id: Task::get_current(|t| t.id().0),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
future.await
|
|
||||||
};
|
|
||||||
|
|
||||||
once_cell::sync::Lazy::force(&crate::rt::RUNTIME);
|
|
||||||
|
|
||||||
// FIXME: figure out how to set the current task.
|
|
||||||
|
|
||||||
let smol_task = smol::Task::spawn(wrapped_future).detach();
|
|
||||||
Ok(JoinHandle::new(smol_task, task))
|
Ok(JoinHandle::new(smol_task, task))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Spawns a task with the configured settings, blocking on its execution.
|
||||||
|
pub fn blocking<F, T>(self, future: F) -> T
|
||||||
|
where
|
||||||
|
F: Future<Output = T>,
|
||||||
|
{
|
||||||
|
let wrapped = self.build(future);
|
||||||
|
|
||||||
|
// Log this `block_on` operation.
|
||||||
|
trace!("block_on", {
|
||||||
|
task_id: wrapped.tag.id().0,
|
||||||
|
parent_task_id: TaskLocalsWrapper::get_current(|t| t.id().0).unwrap_or(0),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run the future as a task.
|
||||||
|
unsafe { TaskLocalsWrapper::set_current(&wrapped.tag, || smol::block_on(wrapped)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper to add support for task locals.
|
||||||
|
struct SupportTaskLocals<F> {
|
||||||
|
tag: TaskLocalsWrapper,
|
||||||
|
future: Pin<Box<F>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> Drop for SupportTaskLocals<F> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Log completion on exit.
|
||||||
|
trace!("completed", {
|
||||||
|
task_id: self.tag.id().0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: Future> Future for SupportTaskLocals<F> {
|
||||||
|
type Output = F::Output;
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
unsafe { TaskLocalsWrapper::set_current(&self.tag, || Pin::new(&mut self.future).poll(cx)) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::task::Task;
|
use crate::task::{Task, TaskLocalsWrapper};
|
||||||
|
|
||||||
/// Returns a handle to the current task.
|
/// Returns a handle to the current task.
|
||||||
///
|
///
|
||||||
|
@ -23,6 +23,6 @@ use crate::task::Task;
|
||||||
/// # })
|
/// # })
|
||||||
/// ```
|
/// ```
|
||||||
pub fn current() -> Task {
|
pub fn current() -> Task {
|
||||||
Task::get_current(|t| t.clone())
|
TaskLocalsWrapper::get_current(|t| t.task().clone())
|
||||||
.expect("`task::current()` called outside the context of a task")
|
.expect("`task::current()` called outside the context of a task")
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,6 +142,7 @@ cfg_default! {
|
||||||
pub use task_local::{AccessError, LocalKey};
|
pub use task_local::{AccessError, LocalKey};
|
||||||
|
|
||||||
pub(crate) use task_local::LocalsMap;
|
pub(crate) use task_local::LocalsMap;
|
||||||
|
pub(crate) use task_locals_wrapper::TaskLocalsWrapper;
|
||||||
|
|
||||||
mod block_on;
|
mod block_on;
|
||||||
mod builder;
|
mod builder;
|
||||||
|
@ -153,6 +154,7 @@ cfg_default! {
|
||||||
mod task;
|
mod task;
|
||||||
mod task_id;
|
mod task_id;
|
||||||
mod task_local;
|
mod task_local;
|
||||||
|
mod task_locals_wrapper;
|
||||||
|
|
||||||
#[cfg(any(feature = "unstable", test))]
|
#[cfg(any(feature = "unstable", test))]
|
||||||
pub use spawn_blocking::spawn_blocking;
|
pub use spawn_blocking::spawn_blocking;
|
||||||
|
|
150
src/task/task.rs
150
src/task/task.rs
|
@ -1,74 +1,32 @@
|
||||||
use std::cell::Cell;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::mem::ManuallyDrop;
|
|
||||||
use std::ptr;
|
|
||||||
use std::sync::atomic::{AtomicPtr, Ordering};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::task::{LocalsMap, TaskId};
|
use crate::task::TaskId;
|
||||||
use crate::utils::abort_on_panic;
|
|
||||||
|
|
||||||
thread_local! {
|
/// A handle to a task.
|
||||||
/// A pointer to the currently running task.
|
#[derive(Clone)]
|
||||||
static CURRENT: Cell<*const Task> = Cell::new(ptr::null_mut());
|
pub struct Task {
|
||||||
}
|
|
||||||
|
|
||||||
/// The inner representation of a task handle.
|
|
||||||
struct Inner {
|
|
||||||
/// The task ID.
|
/// The task ID.
|
||||||
id: TaskId,
|
id: TaskId,
|
||||||
|
|
||||||
/// The optional task name.
|
/// The optional task name.
|
||||||
name: Option<Box<str>>,
|
name: Option<Arc<String>>,
|
||||||
|
|
||||||
/// The map holding task-local values.
|
|
||||||
locals: LocalsMap,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Inner {
|
|
||||||
#[inline]
|
|
||||||
fn new(name: Option<String>) -> Inner {
|
|
||||||
Inner {
|
|
||||||
id: TaskId::generate(),
|
|
||||||
name: name.map(String::into_boxed_str),
|
|
||||||
locals: LocalsMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A handle to a task.
|
|
||||||
pub struct Task {
|
|
||||||
/// The inner representation.
|
|
||||||
///
|
|
||||||
/// This pointer is lazily initialized on first use. In most cases, the inner representation is
|
|
||||||
/// never touched and therefore we don't allocate it unless it's really needed.
|
|
||||||
inner: AtomicPtr<Inner>,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for Task {}
|
|
||||||
unsafe impl Sync for Task {}
|
|
||||||
|
|
||||||
impl Task {
|
impl Task {
|
||||||
/// Creates a new task handle.
|
/// Creates a new task handle.
|
||||||
///
|
|
||||||
/// If the task is unnamed, the inner representation of the task will be lazily allocated on
|
|
||||||
/// demand.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn new(name: Option<String>) -> Task {
|
pub(crate) fn new(name: Option<Arc<String>>) -> Task {
|
||||||
let inner = match name {
|
Task {
|
||||||
None => AtomicPtr::default(),
|
id: TaskId::generate(),
|
||||||
Some(name) => {
|
name,
|
||||||
let raw = Arc::into_raw(Arc::new(Inner::new(Some(name))));
|
|
||||||
AtomicPtr::new(raw as *mut Inner)
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
Task { inner }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the task's unique identifier.
|
/// Gets the task's unique identifier.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn id(&self) -> TaskId {
|
pub fn id(&self) -> TaskId {
|
||||||
self.inner().id
|
self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the name of this task.
|
/// Returns the name of this task.
|
||||||
|
@ -77,93 +35,7 @@ impl Task {
|
||||||
///
|
///
|
||||||
/// [`Builder::name`]: struct.Builder.html#method.name
|
/// [`Builder::name`]: struct.Builder.html#method.name
|
||||||
pub fn name(&self) -> Option<&str> {
|
pub fn name(&self) -> Option<&str> {
|
||||||
self.inner().name.as_ref().map(|s| &**s)
|
self.name.as_ref().map(|s| s.as_str())
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the map holding task-local values.
|
|
||||||
pub(crate) fn locals(&self) -> &LocalsMap {
|
|
||||||
&self.inner().locals
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Drops all task-local values.
|
|
||||||
///
|
|
||||||
/// This method is only safe to call at the end of the task.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) unsafe fn drop_locals(&self) {
|
|
||||||
let raw = self.inner.load(Ordering::Acquire);
|
|
||||||
if let Some(inner) = raw.as_mut() {
|
|
||||||
// Abort the process if dropping task-locals panics.
|
|
||||||
abort_on_panic(|| {
|
|
||||||
inner.locals.clear();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the inner representation, initializing it on first use.
|
|
||||||
fn inner(&self) -> &Inner {
|
|
||||||
loop {
|
|
||||||
let raw = self.inner.load(Ordering::Acquire);
|
|
||||||
if !raw.is_null() {
|
|
||||||
return unsafe { &*raw };
|
|
||||||
}
|
|
||||||
|
|
||||||
let new = Arc::into_raw(Arc::new(Inner::new(None))) as *mut Inner;
|
|
||||||
if self.inner.compare_and_swap(raw, new, Ordering::AcqRel) != raw {
|
|
||||||
unsafe {
|
|
||||||
drop(Arc::from_raw(new));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a reference to the current task.
|
|
||||||
pub(crate) unsafe fn set_current<F, R>(task: *const Task, f: F) -> R
|
|
||||||
where
|
|
||||||
F: FnOnce() -> R,
|
|
||||||
{
|
|
||||||
CURRENT.with(|current| {
|
|
||||||
let old_task = current.replace(task);
|
|
||||||
defer! {
|
|
||||||
current.set(old_task);
|
|
||||||
}
|
|
||||||
f()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets a reference to the current task.
|
|
||||||
pub(crate) fn get_current<F, R>(f: F) -> Option<R>
|
|
||||||
where
|
|
||||||
F: FnOnce(&Task) -> R,
|
|
||||||
{
|
|
||||||
let res = CURRENT.try_with(|current| unsafe { current.get().as_ref().map(f) });
|
|
||||||
match res {
|
|
||||||
Ok(Some(val)) => Some(val),
|
|
||||||
Ok(None) | Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Task {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
// Deallocate the inner representation if it was initialized.
|
|
||||||
let raw = *self.inner.get_mut();
|
|
||||||
if !raw.is_null() {
|
|
||||||
unsafe {
|
|
||||||
drop(Arc::from_raw(raw));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for Task {
|
|
||||||
fn clone(&self) -> Task {
|
|
||||||
// We need to make sure the inner representation is initialized now so that this instance
|
|
||||||
// and the clone have raw pointers that point to the same `Arc<Inner>`.
|
|
||||||
let arc = unsafe { ManuallyDrop::new(Arc::from_raw(self.inner())) };
|
|
||||||
let raw = Arc::into_raw(Arc::clone(&arc));
|
|
||||||
Task {
|
|
||||||
inner: AtomicPtr::new(raw as *mut Inner),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::atomic::{AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
|
||||||
use crate::task::Task;
|
use crate::task::TaskLocalsWrapper;
|
||||||
|
|
||||||
/// The key for accessing a task-local value.
|
/// The key for accessing a task-local value.
|
||||||
///
|
///
|
||||||
|
@ -98,7 +98,7 @@ impl<T: Send + 'static> LocalKey<T> {
|
||||||
where
|
where
|
||||||
F: FnOnce(&T) -> R,
|
F: FnOnce(&T) -> R,
|
||||||
{
|
{
|
||||||
Task::get_current(|task| unsafe {
|
TaskLocalsWrapper::get_current(|task| unsafe {
|
||||||
// Prepare the numeric key, initialization function, and the map of task-locals.
|
// Prepare the numeric key, initialization function, and the map of task-locals.
|
||||||
let key = self.key();
|
let key = self.key();
|
||||||
let init = || Box::new((self.__init)()) as Box<dyn Send>;
|
let init = || Box::new((self.__init)()) as Box<dyn Send>;
|
||||||
|
|
84
src/task/task_locals_wrapper.rs
Normal file
84
src/task/task_locals_wrapper.rs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
use std::cell::Cell;
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
use crate::task::{LocalsMap, Task, TaskId};
|
||||||
|
use crate::utils::abort_on_panic;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
/// A pointer to the currently running task.
|
||||||
|
static CURRENT: Cell<*const TaskLocalsWrapper> = Cell::new(ptr::null_mut());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A wrapper to store task local data.
|
||||||
|
pub(crate) struct TaskLocalsWrapper {
|
||||||
|
/// The actual task details.
|
||||||
|
task: Task,
|
||||||
|
|
||||||
|
/// The map holding task-local values.
|
||||||
|
locals: LocalsMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TaskLocalsWrapper {
|
||||||
|
/// Creates a new task handle.
|
||||||
|
///
|
||||||
|
/// If the task is unnamed, the inner representation of the task will be lazily allocated on
|
||||||
|
/// demand.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn new(task: Task) -> Self {
|
||||||
|
Self {
|
||||||
|
task,
|
||||||
|
locals: LocalsMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the task's unique identifier.
|
||||||
|
#[inline]
|
||||||
|
pub fn id(&self) -> TaskId {
|
||||||
|
self.task.id()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the inner `Task`.
|
||||||
|
pub(crate) fn task(&self) -> &Task {
|
||||||
|
&self.task
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the map holding task-local values.
|
||||||
|
pub(crate) fn locals(&self) -> &LocalsMap {
|
||||||
|
&self.locals
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a reference to the current task.
|
||||||
|
pub(crate) unsafe fn set_current<F, R>(task: *const TaskLocalsWrapper, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce() -> R,
|
||||||
|
{
|
||||||
|
CURRENT.with(|current| {
|
||||||
|
let old_task = current.replace(task);
|
||||||
|
defer! {
|
||||||
|
current.set(old_task);
|
||||||
|
}
|
||||||
|
f()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a reference to the current task.
|
||||||
|
pub(crate) fn get_current<F, R>(f: F) -> Option<R>
|
||||||
|
where
|
||||||
|
F: FnOnce(&TaskLocalsWrapper) -> R,
|
||||||
|
{
|
||||||
|
let res = CURRENT.try_with(|current| unsafe { current.get().as_ref().map(f) });
|
||||||
|
match res {
|
||||||
|
Ok(Some(val)) => Some(val),
|
||||||
|
Ok(None) | Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TaskLocalsWrapper {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Abort the process if dropping task-locals panics.
|
||||||
|
abort_on_panic(|| {
|
||||||
|
unsafe { self.locals.clear() };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue