gst-plugins-rs/generic/threadshare/src/runtime/executor/join.rs
François Laignel 6163589ac7 ts/executor: replace tokio with smol-like implementation
The threadshare executor was based on a modified version of tokio
which implemented the throttling strategy in the BasicScheduler.
Upstream tokio codebase has significantly diverged from what it
was when the throttling strategy was implemented making it hard
to follow. This means that we can hardly get updates from the
upstream project and when we cherry pick fixes, we can't reflect
the state of the project on our fork's version. As a consequence,
tools such as cargo-deny can't check for RUSTSEC fixes in our fork.

The smol ecosystem makes it quite easy to implement and maintain
a custom async executor. This MR imports the smol parts that
need modifications to comply with the threadshare model and implements
a throttling executor in place of the tokio fork.

Networking tokio specific types are replaced with Async wrappers
in the spirit of [smol-rs/async-io]. Note however that the Async
wrappers needed modifications in order to use the per thread
Reactor model. This means that higher level upstream networking
crates such as [async-net] can not be used with our Async
implementation.

Based on the example benchmark with ts-udpsrc, performances seem on par
with what we achieved using the tokio fork.

Fixes https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/118

Related to https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/604
2021-12-25 11:25:56 +00:00

103 lines
2.8 KiB
Rust

// Copyright (C) 2018-2020 Sebastian Dröge <sebastian@centricular.com>
// Copyright (C) 2019-2021 François Laignel <fengalin@free.fr>
//
// Take a look at the license at the top of the repository in the LICENSE file.
use futures::prelude::*;
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::task::Poll;
use super::context::Context;
use super::TaskId;
use super::{Handle, Scheduler};
#[derive(Debug)]
pub struct JoinError(TaskId);
impl fmt::Display for JoinError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{:?} was cancelled", self.0)
}
}
impl std::error::Error for JoinError {}
pub struct JoinHandle<T> {
task: Option<async_task::Task<T>>,
task_id: TaskId,
scheduler: Handle,
}
unsafe impl<T: Send> Send for JoinHandle<T> {}
unsafe impl<T: Send> Sync for JoinHandle<T> {}
impl<T> JoinHandle<T> {
pub(super) fn new(task_id: TaskId, task: async_task::Task<T>, scheduler: &Handle) -> Self {
JoinHandle {
task: Some(task),
task_id,
scheduler: scheduler.clone(),
}
}
pub fn is_current(&self) -> bool {
if let Some((cur_scheduler, task_id)) = Scheduler::current().zip(TaskId::current()) {
cur_scheduler == self.scheduler && task_id == self.task_id
} else {
false
}
}
pub fn context(&self) -> Context {
Context::from(self.scheduler.clone())
}
pub fn task_id(&self) -> TaskId {
self.task_id
}
pub fn cancel(mut self) {
let _ = self.task.take().map(|task| task.cancel());
}
}
impl<T> Future for JoinHandle<T> {
type Output = Result<T, JoinError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
if self.as_ref().is_current() {
panic!("Trying to join task {:?} from itself", self.as_ref());
}
if let Some(task) = self.as_mut().task.as_mut() {
// Unfortunately, we can't detect whether the task has panicked
// because the `async_task::Task` `Future` implementation
// `expect`s and we can't `panic::catch_unwind` here because of `&mut cx`.
// One solution for this would be to use our own `async_task` impl.
task.poll_unpin(cx).map(Ok)
} else {
Poll::Ready(Err(JoinError(self.task_id)))
}
}
}
impl<T> Drop for JoinHandle<T> {
fn drop(&mut self) {
if let Some(task) = self.task.take() {
task.detach();
}
}
}
impl<T> fmt::Debug for JoinHandle<T> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("JoinHandle")
.field("context", &self.scheduler.context_name())
.field("task_id", &self.task_id)
.finish()
}
}