gst-plugins-rs/gst-plugin-threadshare/src/runtime/task.rs
François Laignel 116cf9bd3c threadshare/*src: rework pause/flush_start/flush_stop
This commit fixes several issues with the `Ts*Src` elements.

The pause functions used cancel_task which breaks the Task loop at await
points. For some elements, this implies making sure no item is being lost.
Moreover, cancelling the Task also cancels downstream processing, which
makes it difficult to ensure elements can handle all cases.

This commit reimplements Task::pause which allows completing the running
loop iteration before pausing the loop.

See https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/277#note_439529

In the Paused state, incoming items were rejected by TsAppSrc and DataQueue.

See https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/277#note_438455

- FlushStart must engage items rejection and cancel the Task.
- FlushStop must purge the internal stream & accept items again.

If the task was cancelled, `push_prelude` could set `need_initial_events`
to `true` when the events weren't actually pushed yet.

TsAppSrc used to renew its internal channel which could cause Buffer loss
when transitionning Playing -> Paused -> Playing.

See https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/issues/98
2020-03-21 18:46:03 +00:00

720 lines
24 KiB
Rust

// Copyright (C) 2019-2020 François Laignel <fengalin@free.fr>
// Copyright (C) 2020 Sebastian Dröge <sebastian@centricular.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the
// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
// Boston, MA 02110-1335, USA.
//! An execution loop to run asynchronous processing.
use futures::channel::oneshot;
use futures::future::{abortable, AbortHandle, Aborted};
use futures::prelude::*;
use gst::{gst_debug, gst_error, gst_log, gst_trace, gst_warning};
use std::fmt;
use std::sync::{Arc, Mutex};
use super::executor::{block_on, yield_now};
use super::{Context, JoinHandle, RUNTIME_CAT};
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub enum TaskState {
Cancelled,
Started,
Stopped,
Paused,
Pausing,
Preparing,
Unprepared,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TaskError {
ActiveTask,
}
impl fmt::Display for TaskError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
TaskError::ActiveTask => write!(f, "The task is still active"),
}
}
}
impl std::error::Error for TaskError {}
#[derive(Debug)]
struct TaskInner {
context: Option<Context>,
state: TaskState,
prepare_handle: Option<JoinHandle<Result<Result<(), gst::FlowError>, Aborted>>>,
prepare_abort_handle: Option<AbortHandle>,
abort_handle: Option<AbortHandle>,
loop_handle: Option<JoinHandle<Result<(), Aborted>>>,
resume_sender: Option<oneshot::Sender<()>>,
}
impl Default for TaskInner {
fn default() -> Self {
TaskInner {
context: None,
state: TaskState::Unprepared,
prepare_handle: None,
prepare_abort_handle: None,
abort_handle: None,
loop_handle: None,
resume_sender: None,
}
}
}
impl Drop for TaskInner {
fn drop(&mut self) {
if self.state != TaskState::Unprepared {
panic!("Missing call to `Task::unprepared`");
}
}
}
/// A `Task` operating on a `threadshare` [`Context`].
///
/// [`Context`]: ../executor/struct.Context.html
#[derive(Debug)]
pub struct Task(Arc<Mutex<TaskInner>>);
impl Default for Task {
fn default() -> Self {
Task(Arc::new(Mutex::new(TaskInner::default())))
}
}
impl Task {
pub fn prepare_with_func<F, Fut>(
&self,
context: Context,
prepare_func: F,
) -> Result<(), TaskError>
where
F: (FnOnce() -> Fut) + Send + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
gst_debug!(RUNTIME_CAT, "Preparing task");
let mut inner = self.0.lock().unwrap();
if inner.state != TaskState::Unprepared {
return Err(TaskError::ActiveTask);
}
// Spawn prepare function in the background
let task_weak = Arc::downgrade(&self.0);
let (prepare_fut, prepare_abort_handle) = abortable(async move {
gst_trace!(RUNTIME_CAT, "Calling task prepare function");
prepare_func().await;
gst_trace!(RUNTIME_CAT, "Task prepare function finished");
while Context::current_has_sub_tasks() {
Context::drain_sub_tasks().await?;
}
// Once the prepare function is finished we can forget the corresponding
// handles so that unprepare and friends don't have to block on it anymore
if let Some(task_inner) = task_weak.upgrade() {
let mut inner = task_inner.lock().unwrap();
inner.prepare_abort_handle = None;
inner.prepare_handle = None;
}
gst_trace!(RUNTIME_CAT, "Task fully prepared");
Ok(())
});
let prepare_handle = context.spawn(prepare_fut);
inner.prepare_handle = Some(prepare_handle);
inner.prepare_abort_handle = Some(prepare_abort_handle);
inner.context = Some(context);
inner.state = TaskState::Preparing;
gst_debug!(RUNTIME_CAT, "Task prepared");
Ok(())
}
pub fn prepare(&self, context: Context) -> Result<(), TaskError> {
gst_debug!(RUNTIME_CAT, "Preparing task");
let mut inner = self.0.lock().unwrap();
if inner.state != TaskState::Unprepared {
return Err(TaskError::ActiveTask);
}
inner.prepare_handle = None;
inner.prepare_abort_handle = None;
inner.context = Some(context);
inner.state = TaskState::Stopped;
gst_debug!(RUNTIME_CAT, "Task prepared");
Ok(())
}
pub fn unprepare(&self) -> Result<(), TaskError> {
let mut inner = self.0.lock().unwrap();
if inner.state != TaskState::Stopped {
gst_error!(
RUNTIME_CAT,
"Attempt to Unprepare a task in state {:?}",
inner.state
);
return Err(TaskError::ActiveTask);
}
gst_debug!(RUNTIME_CAT, "Unpreparing task");
// Abort any pending preparation
if let Some(abort_handle) = inner.prepare_abort_handle.take() {
abort_handle.abort();
}
let prepare_handle = inner.prepare_handle.take();
let context = inner.context.take().unwrap();
inner.state = TaskState::Unprepared;
drop(inner);
if let Some(prepare_handle) = prepare_handle {
if let Some((cur_context, cur_task_id)) = Context::current_task() {
if prepare_handle.is_current() {
// This would deadlock!
gst_warning!(
RUNTIME_CAT,
"Trying to stop task {:?} from itself, not waiting",
prepare_handle
);
} else if cur_context == context {
// This is ok: as we're on the same thread and the prepare function is aborted
// this means that it won't ever be called, and we're not inside it here
gst_debug!(
RUNTIME_CAT,
"Asynchronously waiting for task {:?} on the same context",
prepare_handle
);
let _ = Context::add_sub_task(async move {
let _ = prepare_handle.await;
Ok(())
});
} else {
// This is suboptimal but we can't really do this asynchronously as otherwise
// it might be started again before it's actually stopped.
gst_warning!(
RUNTIME_CAT,
"Synchronously waiting for task {:?} on task {:?} on context {}",
prepare_handle,
cur_task_id,
cur_context.name()
);
let _ = block_on(prepare_handle);
}
} else {
gst_debug!(
RUNTIME_CAT,
"Synchronously waiting for task {:?}",
prepare_handle
);
let _ = block_on(prepare_handle);
}
}
gst_debug!(RUNTIME_CAT, "Task unprepared");
Ok(())
}
pub fn state(&self) -> TaskState {
self.0.lock().unwrap().state
}
pub fn context(&self) -> Option<Context> {
self.0.lock().unwrap().context.as_ref().cloned()
}
/// `Starts` the `Task`.
///
/// The `Task` will loop on the provided @func.
/// The execution occurs on the `Task`'s context.
pub fn start<F, Fut>(&self, mut func: F)
where
F: (FnMut() -> Fut) + Send + 'static,
Fut: Future<Output = glib::Continue> + Send + 'static,
{
let inner_clone = Arc::clone(&self.0);
let mut inner = self.0.lock().unwrap();
match inner.state {
TaskState::Started => {
gst_log!(RUNTIME_CAT, "Task already Started");
return;
}
TaskState::Pausing => {
gst_debug!(RUNTIME_CAT, "Re-starting a Pausing task");
assert!(inner.resume_sender.is_none());
inner.state = TaskState::Started;
return;
}
TaskState::Paused => {
inner
.resume_sender
.take()
.expect("Task Paused but the resume_sender is already taken")
.send(())
.expect("Task Paused but the resume_receiver was dropped");
gst_log!(RUNTIME_CAT, "Resume requested");
return;
}
TaskState::Stopped | TaskState::Cancelled | TaskState::Preparing => (),
TaskState::Unprepared => panic!("Attempt to start an unprepared Task"),
}
gst_debug!(RUNTIME_CAT, "Starting Task");
let prepare_handle = inner.prepare_handle.take();
// If the task was only cancelled and not actually stopped yet then
// wait for that to happen as first thing in the new task.
let loop_handle = inner.loop_handle.take();
let (loop_fut, abort_handle) = abortable(async move {
let task_id = Context::current_task().unwrap().1;
if let Some(loop_handle) = loop_handle {
gst_trace!(
RUNTIME_CAT,
"Waiting for previous loop to finish before starting"
);
let _ = loop_handle.await;
}
// First await on the prepare function, if any
if let Some(prepare_handle) = prepare_handle {
gst_trace!(RUNTIME_CAT, "Waiting for prepare before starting");
let res = prepare_handle.await;
if res.is_err() {
gst_warning!(RUNTIME_CAT, "Preparing failed");
inner_clone.lock().unwrap().state = TaskState::Unprepared;
return;
}
inner_clone.lock().unwrap().state = TaskState::Stopped;
}
gst_trace!(RUNTIME_CAT, "Starting task loop");
// Then loop as long as we're actually running
loop {
let mut resume_receiver = {
let mut inner = inner_clone.lock().unwrap();
match inner.state {
TaskState::Started => None,
TaskState::Pausing => {
let (sender, receiver) = oneshot::channel();
inner.resume_sender = Some(sender);
inner.state = TaskState::Paused;
Some(receiver)
}
TaskState::Stopped | TaskState::Cancelled => {
gst_trace!(RUNTIME_CAT, "Stopping task loop");
break;
}
TaskState::Paused => {
unreachable!("The Paused state is controlled by the loop");
}
other => {
unreachable!("Task loop iteration in state {:?}", other);
}
}
};
if let Some(resume_receiver) = resume_receiver.take() {
gst_trace!(RUNTIME_CAT, "Task loop paused");
let _ = resume_receiver.await;
gst_trace!(RUNTIME_CAT, "Resuming task loop");
inner_clone.lock().unwrap().state = TaskState::Started;
}
if func().await == glib::Continue(false) {
let mut inner = inner_clone.lock().unwrap();
// Make sure to only reset the state if this is still the correct task
// and no new task was started in the meantime
if inner.state == TaskState::Started
&& inner
.loop_handle
.as_ref()
.map(|h| h.task_id() == task_id)
.unwrap_or(false)
{
gst_trace!(RUNTIME_CAT, "Exiting task loop");
inner.state = TaskState::Cancelled;
}
break;
}
// Make sure the loop can be aborted even if `func` never goes `Pending`.
yield_now().await;
}
// Once the loop function is finished we can forget the corresponding
// handles so that unprepare and friends don't have to block on it anymore
{
let mut inner = inner_clone.lock().unwrap();
// Make sure to only reset the state if this is still the correct task
// and no new task was started in the meantime
if inner
.loop_handle
.as_ref()
.map(|h| h.task_id() == task_id)
.unwrap_or(false)
{
inner.abort_handle = None;
inner.loop_handle = None;
inner.state = TaskState::Stopped;
}
}
gst_trace!(RUNTIME_CAT, "Task loop finished");
});
let loop_handle = inner
.context
.as_ref()
.expect("Context not set")
.spawn(loop_fut);
inner.abort_handle = Some(abort_handle);
inner.loop_handle = Some(loop_handle);
inner.state = TaskState::Started;
gst_debug!(RUNTIME_CAT, "Task Started");
}
/// Requests the `Task` loop to pause.
///
/// If an iteration is in progress, it will run to completion,
/// then no more iteration will be executed before `start` is called again.
pub fn pause(&self) {
let mut inner = self.0.lock().unwrap();
if inner.state != TaskState::Started {
gst_log!(RUNTIME_CAT, "Task not started");
return;
}
inner.state = TaskState::Pausing;
gst_debug!(RUNTIME_CAT, "Pause requested");
}
/// Cancels the `Task` so that it stops running as soon as possible.
pub fn cancel(&self) {
let mut inner = self.0.lock().unwrap();
if inner.state != TaskState::Started
&& inner.state != TaskState::Paused
&& inner.state != TaskState::Pausing
{
gst_log!(RUNTIME_CAT, "Task not Started nor Paused");
return;
}
gst_debug!(RUNTIME_CAT, "Cancelling Task");
// Abort any still running loop function
if let Some(abort_handle) = inner.abort_handle.take() {
abort_handle.abort();
}
inner.resume_sender = None;
inner.state = TaskState::Cancelled;
}
/// Stops the `Started` `Task` and wait for it to finish.
pub fn stop(&self) {
let mut inner = self.0.lock().unwrap();
if inner.state == TaskState::Stopped || inner.state == TaskState::Preparing {
gst_log!(RUNTIME_CAT, "Task loop already stopped");
return;
}
gst_debug!(RUNTIME_CAT, "Stopping Task");
inner.state = TaskState::Stopped;
// Abort any still running loop function
if let Some(abort_handle) = inner.abort_handle.take() {
abort_handle.abort();
}
// And now wait for it to actually stop
let loop_handle = inner.loop_handle.take();
inner.resume_sender = None;
let context = inner.context.as_ref().unwrap().clone();
drop(inner);
if let Some(loop_handle) = loop_handle {
if let Some((cur_context, cur_task_id)) = Context::current_task() {
if loop_handle.is_current() {
// This would deadlock!
gst_warning!(
RUNTIME_CAT,
"Trying to stop task {:?} from itself, not waiting",
loop_handle
);
} else if cur_context == context {
// This is ok: as we're on the same thread and the loop function is aborted
// this means that it won't ever be called, and we're not inside it here
gst_debug!(
RUNTIME_CAT,
"Asynchronously waiting for task {:?} on the same context",
loop_handle
);
let _ = Context::add_sub_task(async move {
let _ = loop_handle.await;
Ok(())
});
} else {
// This is suboptimal but we can't really do this asynchronously as otherwise
// it might be started again before it's actually stopped.
gst_warning!(
RUNTIME_CAT,
"Synchronously waiting for task {:?} on task {:?} on context {}",
loop_handle,
cur_task_id,
cur_context.name()
);
let _ = block_on(loop_handle);
}
} else {
gst_debug!(
RUNTIME_CAT,
"Synchronously waiting for task {:?}",
loop_handle
);
let _ = block_on(loop_handle);
}
}
gst_debug!(RUNTIME_CAT, "Task stopped");
}
}
#[cfg(test)]
mod tests {
use futures::channel::{mpsc, oneshot};
use futures::lock::Mutex;
use std::sync::Arc;
use crate::runtime::Context;
use super::*;
#[tokio::test]
async fn task() {
gst::init().unwrap();
let context = Context::acquire("task", 2).unwrap();
let task = Task::default();
task.prepare(context).unwrap();
let (mut sender, receiver) = mpsc::channel(0);
let receiver = Arc::new(Mutex::new(receiver));
gst_debug!(RUNTIME_CAT, "task test: starting");
task.start(move || {
let receiver = Arc::clone(&receiver);
async move {
gst_debug!(RUNTIME_CAT, "task test: awaiting receiver");
match receiver.lock().await.next().await {
Some(_) => {
gst_debug!(RUNTIME_CAT, "task test: item received");
glib::Continue(true)
}
None => {
gst_debug!(RUNTIME_CAT, "task test: channel complete");
glib::Continue(false)
}
}
}
});
gst_debug!(RUNTIME_CAT, "task test: sending item");
sender.send(()).await.unwrap();
gst_debug!(RUNTIME_CAT, "task test: item sent");
gst_debug!(RUNTIME_CAT, "task test: dropping sender");
drop(sender);
gst_debug!(RUNTIME_CAT, "task test: stopping");
task.stop();
gst_debug!(RUNTIME_CAT, "task test: stopped");
task.unprepare().unwrap();
gst_debug!(RUNTIME_CAT, "task test: unprepared");
}
#[tokio::test]
async fn task_with_prepare_func() {
gst::init().unwrap();
let context = Context::acquire("task_with_prepare_func", 2).unwrap();
let task = Task::default();
let (prepare_sender, prepare_receiver) = oneshot::channel();
task.prepare_with_func(context, move || async move {
prepare_sender.send(()).unwrap();
})
.unwrap();
let (mut sender, receiver) = mpsc::channel(0);
let receiver = Arc::new(Mutex::new(receiver));
let mut prepare_receiver = Some(prepare_receiver);
gst_debug!(RUNTIME_CAT, "task test: starting");
task.start(move || {
if let Some(mut prepare_receiver) = prepare_receiver.take() {
assert_eq!(prepare_receiver.try_recv().unwrap(), Some(()));
}
let receiver = Arc::clone(&receiver);
async move {
gst_debug!(RUNTIME_CAT, "task test: awaiting receiver");
match receiver.lock().await.next().await {
Some(_) => {
gst_debug!(RUNTIME_CAT, "task test: item received");
glib::Continue(true)
}
None => {
gst_debug!(RUNTIME_CAT, "task test: channel complete");
glib::Continue(false)
}
}
}
});
gst_debug!(RUNTIME_CAT, "task test: sending item");
sender.send(()).await.unwrap();
gst_debug!(RUNTIME_CAT, "task test: item sent");
gst_debug!(RUNTIME_CAT, "task test: dropping sender");
drop(sender);
gst_debug!(RUNTIME_CAT, "task test: stopping");
task.stop();
gst_debug!(RUNTIME_CAT, "task test: stopped");
task.unprepare().unwrap();
gst_debug!(RUNTIME_CAT, "task test: unprepared");
}
#[tokio::test]
async fn pause_start() {
use gst::gst_error;
gst::init().unwrap();
let context = Context::acquire("task_pause_start", 2).unwrap();
let task = Task::default();
task.prepare(context).unwrap();
let (iter_sender, mut iter_receiver) = mpsc::channel(0);
let iter_sender = Arc::new(Mutex::new(iter_sender));
let (mut complete_sender, complete_receiver) = mpsc::channel(0);
let complete_receiver = Arc::new(Mutex::new(complete_receiver));
gst_debug!(RUNTIME_CAT, "task_pause_start: starting");
task.start(move || {
let iter_sender = Arc::clone(&iter_sender);
let complete_receiver = Arc::clone(&complete_receiver);
async move {
gst_debug!(RUNTIME_CAT, "task_pause_start: entering iteration");
iter_sender.lock().await.send(()).await.unwrap();
gst_debug!(
RUNTIME_CAT,
"task_pause_start: iteration awaiting completion"
);
complete_receiver.lock().await.next().await.unwrap();
gst_debug!(RUNTIME_CAT, "task_pause_start: iteration complete");
glib::Continue(true)
}
});
gst_debug!(RUNTIME_CAT, "task_pause_start: awaiting 1st iteration");
iter_receiver.next().await.unwrap();
task.pause();
gst_debug!(
RUNTIME_CAT,
"task_pause_start: sending 1st iteration completion"
);
complete_sender.send(()).await.unwrap();
// Loop held on
iter_receiver.try_next().unwrap_err();
task.start(|| {
gst_error!(
RUNTIME_CAT,
"task_pause_start: reached start to resume closure"
);
future::pending()
});
gst_debug!(RUNTIME_CAT, "task_pause_start: awaiting 2d iteration");
iter_receiver.next().await.unwrap();
gst_debug!(
RUNTIME_CAT,
"task_pause_start: sending 2d iteration completion"
);
complete_sender.send(()).await.unwrap();
task.stop();
task.unprepare().unwrap();
}
}