// Copyright (C) 2019-2022 François Laignel // Copyright (C) 2020 Sebastian Dröge // // 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. // // SPDX-License-Identifier: LGPL-2.1-or-later //! An execution loop to run asynchronous processing. use futures::channel::mpsc as async_mpsc; use futures::channel::oneshot; use futures::future::{self, BoxFuture}; use futures::prelude::*; use std::fmt; use std::ops::Deref; use std::pin::Pin; use std::sync::{Arc, Mutex, MutexGuard}; use std::task::Poll; use super::{Context, JoinHandle, RUNTIME_CAT}; #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] pub enum TaskState { Error, Flushing, Paused, PausedFlushing, Prepared, Preparing, Started, Stopped, Unprepared, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Trigger { Error, FlushStart, FlushStop, Pause, Prepare, Start, Stop, Unprepare, } /// Transition success details. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum TransitionOk { /// Transition completed successfully. Complete { origin: TaskState, target: TaskState, }, /// Not waiting for transition result. /// /// This is to prevent: /// - A deadlock when executing a transition action. /// - A potential infinite wait when pausing a running loop /// which could be awaiting for an `nominal` to complete. NotWaiting { trigger: Trigger, origin: TaskState }, /// Skipping triggering event due to current state. Skipped { trigger: Trigger, state: TaskState }, } /// TriggeringEvent error details. #[derive(Clone, Debug, Eq, PartialEq)] pub struct TransitionError { pub trigger: Trigger, pub state: TaskState, pub err_msg: gst::ErrorMessage, } impl fmt::Display for TransitionError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "{:?} from state {:?}: {:?}", self.trigger, self.state, self.err_msg ) } } impl std::error::Error for TransitionError {} impl From for gst::ErrorMessage { fn from(err: TransitionError) -> Self { err.err_msg } } /// Transition status. /// /// A state transition occurs as a result of a triggering event. /// The triggering event is asynchronously handled by a state machine /// running on a [`Context`]. #[must_use = "This `TransitionStatus` may be `Pending`. In most cases it should be awaited. See `await_maybe_on_context`"] pub enum TransitionStatus { /// Transition result is ready. Ready(Result), /// Transition is pending. Pending { trigger: Trigger, origin: TaskState, res_fut: Pin> + Send>>, }, } impl TransitionStatus { pub fn is_ready(&self) -> bool { matches!(self, TransitionStatus::Ready { .. }) } pub fn is_pending(&self) -> bool { matches!(self, TransitionStatus::Pending { .. }) } /// Converts the `TransitionStatus` into a `Result`. /// /// This function allows getting the `TransitionError` when /// the transition result is ready without `await`ing nor blocking. /// /// See also [`Self::await_maybe_on_context`]. // FIXME once stabilized, this could use https://github.com/rust-lang/rust/issues/84277 pub fn check(self) -> Result { match self { TransitionStatus::Ready(Err(err)) => Err(err), other => Ok(other), } } /// Awaits for this transition to complete, possibly while running on a [`Context`]. /// /// Notes: /// /// - When running in an `async` block within a running transition or /// task iteration, don't await for the transition as it would deadlock. /// Use [`Self::check`] to make sure the state transition is valid. /// - When running in an `async` block out of a running transition or /// task iteration, just `.await` normally. E.g.: /// /// ``` /// # use gstthreadshare::runtime::task::{Task, TransitionOk, TransitionError}; /// # async fn async_fn() -> Result { /// # let task = Task::default(); /// let flush_ok = task.flush_start().await?; /// # Ok(flush_ok) /// # } /// ``` /// /// This function makes sure the transition completes successfully or /// produces an error. It must be used in situations where we don't know /// whether we are running on a [`Context`] or not. This is the case for /// functions in [`PadSrc`] and [`PadSink`] as well as the synchronous /// functions transitively called from them. /// /// As an example, a `PadSrc::src_event` function which handles a /// `FlushStart` could call: /// /// ``` /// # fn src_event() -> bool { /// # let task = gstthreadshare::runtime::Task::default(); /// return task /// .flush_start() /// .await_maybe_on_context() /// .is_ok(); /// # } /// ``` /// /// If the transition is already complete, the result is returned immediately. /// /// If we are NOT running on a [`Context`], the transition result is awaited /// by blocking on current thread and the result is returned. /// /// If we are running on a [`Context`], the transition result is awaited /// in a sub task for current [`Context`]'s Scheduler task. As a consequence, /// the sub task will be awaited in usual [`Context::drain_sub_tasks`] /// rendezvous, ensuring some kind of synchronization. To avoid deadlocks, /// `Ok(TransitionOk::NotWaiting { .. })` is immediately returned. /// /// [`PadSrc`]: ../pad/struct.PadSrc.html /// [`PadSink`]: ../pad/struct.PadSink.html pub fn await_maybe_on_context(self) -> Result { use TransitionStatus::*; match self { Pending { trigger, origin, res_fut, } => { if let Some((ctx, task_id)) = Context::current_task() { gst::debug!( RUNTIME_CAT, "Awaiting for {:?} ack in a subtask on context {}", trigger, ctx.name() ); let _ = ctx.add_sub_task(task_id, async move { let res = res_fut.await; if res.is_ok() { gst::log!(RUNTIME_CAT, "Received ack {:?} for {:?}", res, trigger); } else { gst::error!(RUNTIME_CAT, "Received ack {:?} for {:?}", res, trigger); } Ok(()) }); Ok(TransitionOk::NotWaiting { trigger, origin }) } else { gst::debug!( RUNTIME_CAT, "Awaiting for {:?} ack on current thread", trigger, ); futures::executor::block_on(res_fut) } } Ready(res) => res, } } /// Awaits for this transition to complete by blocking current thread. /// /// This function blocks until the transition completes successfully or /// produces an error. /// /// In situations where we don't know whether we are running on a [`Context`] /// or not, use [`Self::await_maybe_on_context`] instead. /// /// # Panics /// /// Panics if current thread is a [`Context`] thread. pub fn block_on(self) -> Result { assert!(!Context::is_context_thread()); use TransitionStatus::*; match self { Pending { trigger, res_fut, .. } => { gst::debug!( RUNTIME_CAT, "Awaiting for {:?} ack on current thread", trigger, ); futures::executor::block_on(res_fut) } Ready(res) => res, } } } impl Future for TransitionStatus { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { use TransitionStatus::*; match &mut *self { Ready(res) => Poll::Ready(res.clone()), Pending { res_fut, .. } => match Pin::new(res_fut).poll(cx) { Poll::Pending => Poll::Pending, Poll::Ready(res) => { *self = Ready(res.clone()); Poll::Ready(res) } }, } } } impl From for TransitionStatus { fn from(ok: TransitionOk) -> Self { Self::Ready(Ok(ok)) } } impl From for TransitionStatus { fn from(err: TransitionError) -> Self { Self::Ready(Err(err)) } } // Explicit impl due to `res_fut` not implementing `Debug`. impl fmt::Debug for TransitionStatus { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use TransitionStatus::*; match self { Ready(res) => f.debug_tuple("Ready").field(res).finish(), Pending { trigger, origin, .. } => f .debug_struct("Pending") .field("trigger", trigger) .field("origin", origin) .finish(), } } } /// Implementation trait for `Task`s. /// /// Defines implementations for state transition actions and error handlers. pub trait TaskImpl: Send + 'static { type Item: Send + 'static; fn prepare(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { future::ok(()).boxed() } fn unprepare(&mut self) -> BoxFuture<'_, ()> { future::ready(()).boxed() } fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { future::ok(()).boxed() } /// Tries to retrieve the next item to process. /// /// With [`Self::handle_item`], this is one of the two `Task` loop /// functions. They are executed in a loop in the `Started` state. /// /// Function `try_next` is awaited at the beginning of each iteration, /// and can be cancelled at `await` point if a state transition is requested. /// /// If `Ok(item)` is returned, the iteration calls [`Self::handle_item`] /// with said `Item`. /// /// If `Err(..)` is returned, the iteration calls [`Self::handle_loop_error`]. fn try_next(&mut self) -> BoxFuture<'_, Result>; /// Does whatever needs to be done with the `item`. /// /// With [`Self::try_next`], this is one of the two `Task` loop /// functions. They are executed in a loop in the `Started` state. /// /// Function `handle_item` asynchronously processes an `item` previously /// retrieved by [`Self::try_next`]. Processing is guaranteed to run /// to completion even if a state transition is requested. /// /// If `Err(..)` is returned, the iteration calls [`Self::handle_loop_error`]. fn handle_item(&mut self, _item: Self::Item) -> BoxFuture<'_, Result<(), gst::FlowError>>; fn pause(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { future::ok(()).boxed() } fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { future::ok(()).boxed() } fn flush_stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { future::ok(()).boxed() } fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { future::ok(()).boxed() } /// Handles an error occuring during the execution of the `Task` loop. /// /// This include errors returned by [`Self::try_next`] & [`Self::handle_item`]. /// /// If the error is unrecoverable, implementations might use /// `gst::Element::post_error_message` and return `Trigger::Error`. /// /// Otherwise, handle the error and return the requested `Transition` to recover. /// /// Default behaviour depends on the `err`: /// /// - `FlowError::Flushing` -> `Trigger::FlushStart`. /// - `FlowError::Eos` -> `Trigger::Stop`. /// - Other `FlowError` -> `Trigger::Error`. fn handle_loop_error(&mut self, err: gst::FlowError) -> BoxFuture<'_, Trigger> { async move { match err { gst::FlowError::Flushing => { gst::debug!( RUNTIME_CAT, "Task loop returned Flushing. Posting FlushStart" ); Trigger::FlushStart } gst::FlowError::Eos => { gst::debug!(RUNTIME_CAT, "Task loop returned Eos. Posting Stop"); Trigger::Stop } other => { gst::error!(RUNTIME_CAT, "Task loop returned {:?}. Posting Error", other); Trigger::Error } } } .boxed() } /// Handles an error occuring during the execution of a transition action. /// /// This handler also catches errors returned by subtasks spawned by the transition action. /// /// If the error is unrecoverable, implementations might use `gst::Element::post_error_message` /// and return `Trigger::Error`. /// /// Otherwise, handle the error and return the recovering `Trigger`. /// /// Default is to `gst::error` log and return `Trigger::Error`. fn handle_action_error( &mut self, trigger: Trigger, state: TaskState, err: gst::ErrorMessage, ) -> BoxFuture<'_, Trigger> { async move { gst::error!( RUNTIME_CAT, "TaskImpl transition action error during {:?} from {:?}: {:?}. Posting Trigger::Error", trigger, state, err, ); Trigger::Error } .boxed() } } type AckSender = oneshot::Sender>; type AckReceiver = oneshot::Receiver>; struct TriggeringEvent { trigger: Trigger, ack_tx: AckSender, } impl TriggeringEvent { fn new(trigger: Trigger) -> (Self, AckReceiver) { let (ack_tx, ack_rx) = oneshot::channel(); let req = TriggeringEvent { trigger, ack_tx }; (req, ack_rx) } fn send_ack(self, res: Result) { let _ = self.ack_tx.send(res); } fn send_err_ack(self) { let res = Err(TransitionError { trigger: self.trigger, state: TaskState::Error, err_msg: gst::error_msg!( gst::CoreError::StateChange, [ "Triggering Event {:?} rejected due to a previous unrecoverable error", self.trigger, ] ), }); self.send_ack(res); } } impl fmt::Debug for TriggeringEvent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("TriggeringEvent") .field("trigger", &self.trigger) .finish() } } #[derive(Debug)] struct StateMachineHandle { join_handle: JoinHandle<()>, triggering_evt_tx: async_mpsc::Sender, context: Context, } impl StateMachineHandle { fn trigger(&mut self, trigger: Trigger) -> AckReceiver { let (triggering_evt, ack_rx) = TriggeringEvent::new(trigger); gst::log!(RUNTIME_CAT, "Pushing {:?}", triggering_evt); self.triggering_evt_tx.try_send(triggering_evt).unwrap(); self.context.unpark(); ack_rx } async fn join(self) { self.join_handle .await .expect("state machine shouldn't have been cancelled"); } } #[derive(Debug)] struct TaskInner { state: TaskState, state_machine_handle: Option, } impl Default for TaskInner { fn default() -> Self { TaskInner { state: TaskState::Unprepared, state_machine_handle: None, } } } impl TaskInner { fn switch_to_state(&mut self, target_state: TaskState, triggering_evt: TriggeringEvent) { let res = Ok(TransitionOk::Complete { origin: self.state, target: target_state, }); self.state = target_state; triggering_evt.send_ack(res); } fn switch_to_err(&mut self, triggering_evt: TriggeringEvent) { let res = Err(TransitionError { trigger: triggering_evt.trigger, state: self.state, err_msg: gst::error_msg!( gst::CoreError::StateChange, [ "Unrecoverable error for {:?} from state {:?}", triggering_evt, self.state, ] ), }); self.state = TaskState::Error; triggering_evt.send_ack(res); } fn skip_triggering_evt(&mut self, triggering_evt: TriggeringEvent) { let res = Ok(TransitionOk::Skipped { trigger: triggering_evt.trigger, state: self.state, }); triggering_evt.send_ack(res); } fn trigger(&mut self, trigger: Trigger) -> Result { self.state_machine_handle .as_mut() .map(|state_machine| state_machine.trigger(trigger)) .ok_or_else(|| { gst::warning!( RUNTIME_CAT, "Unable to send {:?}: no state machine", trigger ); TransitionError { trigger, state: TaskState::Unprepared, err_msg: gst::error_msg!( gst::ResourceError::NotFound, ["Unable to send {:?}: no state machine", trigger] ), } }) } } impl Drop for TaskInner { fn drop(&mut self) { if self.state != TaskState::Unprepared { // Don't panic here: in case another panic occurs, we would get // "panicked while panicking" which would prevents developers // from getting the initial panic message. gst::fixme!(RUNTIME_CAT, "Missing call to `Task::unprepare`"); } } } /// An RAII implementation of scoped locked `TaskState`. pub struct TaskStateGuard<'guard>(MutexGuard<'guard, TaskInner>); impl Deref for TaskStateGuard<'_> { type Target = TaskState; fn deref(&self) -> &Self::Target { &(self.0).state } } /// A `Task` operating on a `threadshare` [`Context`]. /// /// [`Context`]: ../executor/struct.Context.html #[derive(Debug, Clone)] pub struct Task(Arc>); impl Default for Task { fn default() -> Self { Task(Arc::new(Mutex::new(TaskInner::default()))) } } impl Task { pub fn state(&self) -> TaskState { self.0.lock().unwrap().state } pub fn lock_state(&self) -> TaskStateGuard<'_> { TaskStateGuard(self.0.lock().unwrap()) } pub fn prepare(&self, task_impl: impl TaskImpl, context: Context) -> TransitionStatus { let mut inner = self.0.lock().unwrap(); let origin = inner.state; match origin { TaskState::Unprepared => (), TaskState::Prepared | TaskState::Preparing => { gst::debug!(RUNTIME_CAT, "Task already {:?}", origin); return TransitionOk::Skipped { trigger: Trigger::Prepare, state: origin, } .into(); } state => { gst::warning!(RUNTIME_CAT, "Attempt to prepare Task in state {:?}", state); return TransitionError { trigger: Trigger::Prepare, state: inner.state, err_msg: gst::error_msg!( gst::CoreError::StateChange, ["Attempt to prepare Task in state {:?}", state] ), } .into(); } } assert!(inner.state_machine_handle.is_none()); inner.state = TaskState::Preparing; gst::log!(RUNTIME_CAT, "Spawning task state machine"); inner.state_machine_handle = Some(StateMachine::spawn( self.0.clone(), Box::new(task_impl), context, )); let ack_rx = match inner.trigger(Trigger::Prepare) { Ok(ack_rx) => ack_rx, Err(err) => return err.into(), }; drop(inner); TransitionStatus::Pending { trigger: Trigger::Prepare, origin: TaskState::Unprepared, res_fut: Box::pin(ack_rx.map(Result::unwrap)), } } pub fn unprepare(&self) -> TransitionStatus { let mut inner = self.0.lock().unwrap(); let origin = inner.state; let mut state_machine_handle = match origin { TaskState::Stopped | TaskState::Error | TaskState::Prepared | TaskState::Preparing | TaskState::Unprepared => match inner.state_machine_handle.take() { Some(state_machine_handle) => { gst::debug!(RUNTIME_CAT, "Unpreparing task"); state_machine_handle } None => { gst::debug!(RUNTIME_CAT, "Task already unpreparing"); return TransitionOk::Skipped { trigger: Trigger::Unprepare, state: origin, } .into(); } }, state => { gst::warning!( RUNTIME_CAT, "Attempt to unprepare Task in state {:?}", state ); return TransitionError { trigger: Trigger::Unprepare, state: inner.state, err_msg: gst::error_msg!( gst::CoreError::StateChange, ["Attempt to unprepare Task in state {:?}", state] ), } .into(); } }; let ack_rx = state_machine_handle.trigger(Trigger::Unprepare); drop(inner); let state_machine_end_fut = async { state_machine_handle.join().await; ack_rx.await.unwrap() }; TransitionStatus::Pending { trigger: Trigger::Unprepare, origin, res_fut: Box::pin(state_machine_end_fut), } } /// Starts the `Task`. /// /// The execution occurs on the `Task` context. pub fn start(&self) -> TransitionStatus { let mut inner = self.0.lock().unwrap(); if let TaskState::Started = inner.state { return TransitionOk::Skipped { trigger: Trigger::Start, state: TaskState::Started, } .into(); } let ack_rx = match inner.trigger(Trigger::Start) { Ok(ack_rx) => ack_rx, Err(err) => return err.into(), }; let origin = inner.state; drop(inner); TransitionStatus::Pending { trigger: Trigger::Start, origin, res_fut: Box::pin(ack_rx.map(Result::unwrap)), } } /// Requests the `Task` loop to pause. /// /// If an item handling is in progress, it will run to completion, /// then no iterations will be executed before `start` is called again. pub fn pause(&self) -> TransitionStatus { self.push_pending(Trigger::Pause) } pub fn flush_start(&self) -> TransitionStatus { self.push_pending(Trigger::FlushStart) } pub fn flush_stop(&self) -> TransitionStatus { self.push_pending(Trigger::FlushStop) } /// Stops the `Started` `Task` and wait for it to finish. pub fn stop(&self) -> TransitionStatus { self.push_pending(Trigger::Stop) } /// Pushes a [`Trigger`] and returns TransitionStatus::Pending. fn push_pending(&self, trigger: Trigger) -> TransitionStatus { let mut inner = self.0.lock().unwrap(); let ack_rx = match inner.trigger(trigger) { Ok(ack_rx) => ack_rx, Err(err) => return err.into(), }; let origin = inner.state; drop(inner); TransitionStatus::Pending { trigger, origin, res_fut: Box::pin(ack_rx.map(Result::unwrap)), } } } struct StateMachine { task_impl: Box>, triggering_evt_rx: async_mpsc::Receiver, pending_triggering_evt: Option, } macro_rules! exec_action { ($self:ident, $action:ident, $triggering_evt:expr, $origin:expr, $task_inner:expr) => {{ match $self.task_impl.$action().await { Ok(()) => Ok($triggering_evt), Err(err) => { // FIXME problem is that we loose the origin trigger in the // final TransitionStatus. let next_trigger = $self .task_impl .handle_action_error($triggering_evt.trigger, $origin, err) .await; // Convert triggering event according to the error handler's decision gst::trace!( RUNTIME_CAT, "TaskImpl transition action error: converting {:?} to {:?}", $triggering_evt.trigger, next_trigger, ); $triggering_evt.trigger = next_trigger; $self.pending_triggering_evt = Some($triggering_evt); Err(()) } } }}; } impl StateMachine { // Use dynamic dispatch for TaskImpl as it reduces memory usage compared to monomorphization // without inducing any significant performance penalties. fn spawn( task_inner: Arc>, task_impl: Box>, context: Context, ) -> StateMachineHandle { let (triggering_evt_tx, triggering_evt_rx) = async_mpsc::channel(4); let state_machine = StateMachine { task_impl, triggering_evt_rx, pending_triggering_evt: None, }; StateMachineHandle { join_handle: context.spawn_and_unpark(state_machine.run(task_inner)), triggering_evt_tx, context, } } async fn run(mut self, task_inner: Arc>) { let mut triggering_evt = self .triggering_evt_rx .next() .await .expect("triggering_evt_rx dropped"); if let Trigger::Prepare = triggering_evt.trigger { gst::trace!(RUNTIME_CAT, "Preparing task"); let res = exec_action!( self, prepare, triggering_evt, TaskState::Unprepared, &task_inner ); if let Ok(triggering_evt) = res { let mut task_inner = task_inner.lock().unwrap(); let res = Ok(TransitionOk::Complete { origin: TaskState::Unprepared, target: TaskState::Prepared, }); task_inner.state = TaskState::Prepared; triggering_evt.send_ack(res); gst::trace!(RUNTIME_CAT, "Task Prepared"); } } else { panic!("Unexpected initial trigger {:?}", triggering_evt.trigger); } loop { triggering_evt = match self.pending_triggering_evt.take() { Some(pending_triggering_evt) => pending_triggering_evt, None => self .triggering_evt_rx .next() .await .expect("triggering_evt_rx dropped"), }; gst::trace!(RUNTIME_CAT, "State machine popped {:?}", triggering_evt); match triggering_evt.trigger { Trigger::Error => { let mut task_inner = task_inner.lock().unwrap(); task_inner.switch_to_err(triggering_evt); gst::trace!(RUNTIME_CAT, "Switched to Error"); } Trigger::Start => { let origin = { let mut task_inner = task_inner.lock().unwrap(); let origin = task_inner.state; match origin { TaskState::Stopped | TaskState::Paused | TaskState::Prepared => (), TaskState::PausedFlushing => { task_inner.switch_to_state(TaskState::Flushing, triggering_evt); gst::trace!( RUNTIME_CAT, "Switched from PausedFlushing to Flushing" ); continue; } TaskState::Error => { triggering_evt.send_err_ack(); continue; } state => { task_inner.skip_triggering_evt(triggering_evt); gst::trace!(RUNTIME_CAT, "Skipped Start in state {:?}", state); continue; } } origin }; self.start(triggering_evt, origin, &task_inner).await; // next/pending triggering event handled in next iteration } Trigger::Pause => { let (origin, target) = { let mut task_inner = task_inner.lock().unwrap(); let origin = task_inner.state; match origin { TaskState::Started | TaskState::Stopped | TaskState::Prepared => { (origin, TaskState::Paused) } TaskState::Flushing => (origin, TaskState::PausedFlushing), TaskState::Error => { triggering_evt.send_err_ack(); continue; } state => { task_inner.skip_triggering_evt(triggering_evt); gst::trace!(RUNTIME_CAT, "Skipped Pause in state {:?}", state); continue; } } }; let res = exec_action!(self, pause, triggering_evt, origin, &task_inner); if let Ok(triggering_evt) = res { task_inner .lock() .unwrap() .switch_to_state(target, triggering_evt); gst::trace!(RUNTIME_CAT, "Task loop {:?}", target); } } Trigger::Stop => { let origin = { let mut task_inner = task_inner.lock().unwrap(); let origin = task_inner.state; match origin { TaskState::Started | TaskState::Paused | TaskState::PausedFlushing | TaskState::Flushing => origin, TaskState::Error => { triggering_evt.send_err_ack(); continue; } state => { task_inner.skip_triggering_evt(triggering_evt); gst::trace!(RUNTIME_CAT, "Skipped Stop in state {:?}", state); continue; } } }; let res = exec_action!(self, stop, triggering_evt, origin, &task_inner); if let Ok(triggering_evt) = res { task_inner .lock() .unwrap() .switch_to_state(TaskState::Stopped, triggering_evt); gst::trace!(RUNTIME_CAT, "Task loop Stopped"); } } Trigger::FlushStart => { let (origin, target) = { let mut task_inner = task_inner.lock().unwrap(); let origin = task_inner.state; match origin { TaskState::Started => (origin, TaskState::Flushing), TaskState::Paused => (origin, TaskState::PausedFlushing), TaskState::Error => { triggering_evt.send_err_ack(); continue; } state => { task_inner.skip_triggering_evt(triggering_evt); gst::trace!(RUNTIME_CAT, "Skipped FlushStart in state {:?}", state); continue; } } }; let res = exec_action!(self, flush_start, triggering_evt, origin, &task_inner); if let Ok(triggering_evt) = res { task_inner .lock() .unwrap() .switch_to_state(target, triggering_evt); gst::trace!(RUNTIME_CAT, "Task {:?}", target); } } Trigger::FlushStop => { let origin = task_inner.lock().unwrap().state; let is_paused = match origin { TaskState::Flushing => false, TaskState::PausedFlushing => true, TaskState::Error => { triggering_evt.send_err_ack(); continue; } state => { task_inner .lock() .unwrap() .skip_triggering_evt(triggering_evt); gst::trace!(RUNTIME_CAT, "Skipped FlushStop in state {:?}", state); continue; } }; let res = exec_action!(self, flush_stop, triggering_evt, origin, &task_inner); if let Ok(triggering_evt) = res { if is_paused { task_inner .lock() .unwrap() .switch_to_state(TaskState::Paused, triggering_evt); gst::trace!(RUNTIME_CAT, "Switched from PausedFlushing to Paused"); } else { self.start(triggering_evt, origin, &task_inner).await; // next/pending triggering event handled in next iteration } } } Trigger::Unprepare => { // Unprepare is not joined by an ack_rx but by joining the state machine handle self.task_impl.unprepare().await; task_inner .lock() .unwrap() .switch_to_state(TaskState::Unprepared, triggering_evt); break; } _ => unreachable!("State machine handler {:?}", triggering_evt), } } gst::trace!(RUNTIME_CAT, "Task state machine terminated"); } async fn start( &mut self, mut triggering_evt: TriggeringEvent, origin: TaskState, task_inner: &Arc>, ) { match exec_action!(self, start, triggering_evt, origin, &task_inner) { Ok(triggering_evt) => { let mut task_inner = task_inner.lock().unwrap(); task_inner.switch_to_state(TaskState::Started, triggering_evt); } Err(_) => { // error handled by exec_action return; } } match self.run_loop().await { Ok(()) => (), Err(err) => { let next_trigger = self.task_impl.handle_loop_error(err).await; let (triggering_evt, _) = TriggeringEvent::new(next_trigger); self.pending_triggering_evt = Some(triggering_evt); } } } async fn run_loop(&mut self) -> Result<(), gst::FlowError> { gst::trace!(RUNTIME_CAT, "Task loop started"); let mut item; loop { item = { // select_biased requires the selected futures to implement // `FusedFuture`. Because async trait functions are not stable, // we use `BoxFuture` for the `TaskImpl` function, including // `try_next`. Since we need to get a new `BoxFuture` at // each iteration, we can guarantee that the future is // always valid for use in `select_biased`. let mut try_next_fut = self.task_impl.try_next().fuse(); futures::select_biased! { triggering_evt = self.triggering_evt_rx.next() => { let triggering_evt = triggering_evt.expect("broken state machine channel"); gst::trace!( RUNTIME_CAT, "Task loop handing {:?} to state machine", triggering_evt, ); self.pending_triggering_evt = Some(triggering_evt); return Ok(()); } try_next_res = try_next_fut => try_next_res.map_err(|err| { gst::debug!(RUNTIME_CAT, "TaskImpl::try_next returned {:?}", err); err })?, } }; self.task_impl.handle_item(item).await.map_err(|err| { gst::debug!(RUNTIME_CAT, "TaskImpl::handle_item returned {:?}", err); err })?; } } } #[cfg(test)] mod tests { use futures::channel::{mpsc, oneshot}; use futures::executor::block_on; use futures::future::BoxFuture; use futures::prelude::*; use std::time::Duration; use super::{ Task, TaskImpl, TaskState::{self, *}, TransitionError, TransitionOk::*, TransitionStatus::*, Trigger::{self, *}, }; use crate::runtime::{Context, RUNTIME_CAT}; #[track_caller] fn stop_then_unprepare(task: Task) { task.stop().block_on().unwrap(); task.unprepare().block_on().unwrap(); } #[test] fn nominal() { gst::init().unwrap(); struct TaskTest { prepared_sender: mpsc::Sender<()>, started_sender: mpsc::Sender<()>, try_next_ready_sender: mpsc::Sender<()>, try_next_receiver: mpsc::Receiver<()>, handle_item_ready_sender: mpsc::Sender<()>, handle_item_sender: mpsc::Sender<()>, paused_sender: mpsc::Sender<()>, stopped_sender: mpsc::Sender<()>, unprepared_sender: mpsc::Sender<()>, } impl TaskImpl for TaskTest { type Item = (); fn prepare(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "nominal: prepared"); self.prepared_sender.send(()).await.unwrap(); Ok(()) } .boxed() } fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "nominal: started"); self.started_sender.send(()).await.unwrap(); Ok(()) } .boxed() } fn try_next(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { async move { gst::debug!(RUNTIME_CAT, "nominal: entering try_next"); self.try_next_ready_sender.send(()).await.unwrap(); gst::debug!(RUNTIME_CAT, "nominal: awaiting try_next"); self.try_next_receiver.next().await.unwrap(); Ok(()) } .boxed() } fn handle_item(&mut self, _item: ()) -> BoxFuture<'_, Result<(), gst::FlowError>> { async move { gst::debug!(RUNTIME_CAT, "nominal: entering handle_item"); self.handle_item_ready_sender.send(()).await.unwrap(); gst::debug!(RUNTIME_CAT, "nominal: locked in handle_item"); self.handle_item_sender.send(()).await.unwrap(); gst::debug!(RUNTIME_CAT, "nominal: leaving handle_item"); Ok(()) } .boxed() } fn pause(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "nominal: paused"); self.paused_sender.send(()).await.unwrap(); Ok(()) } .boxed() } fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "nominal: stopped"); self.stopped_sender.send(()).await.unwrap(); Ok(()) } .boxed() } fn unprepare(&mut self) -> BoxFuture<'_, ()> { async move { gst::debug!(RUNTIME_CAT, "nominal: unprepared"); self.unprepared_sender.send(()).await.unwrap(); } .boxed() } } let context = Context::acquire("nominal", Duration::from_millis(2)).unwrap(); let task = Task::default(); assert_eq!(task.state(), Unprepared); gst::debug!(RUNTIME_CAT, "nominal: preparing"); let (prepared_sender, mut prepared_receiver) = mpsc::channel(1); let (started_sender, mut started_receiver) = mpsc::channel(1); let (try_next_ready_sender, mut try_next_ready_receiver) = mpsc::channel(1); let (mut try_next_sender, try_next_receiver) = mpsc::channel(1); let (handle_item_ready_sender, mut handle_item_ready_receiver) = mpsc::channel(1); let (handle_item_sender, mut handle_item_receiver) = mpsc::channel(0); let (paused_sender, mut paused_receiver) = mpsc::channel(1); let (stopped_sender, mut stopped_receiver) = mpsc::channel(1); let (unprepared_sender, mut unprepared_receiver) = mpsc::channel(1); let prepare_status = task.prepare( TaskTest { prepared_sender, started_sender, try_next_ready_sender, try_next_receiver, handle_item_ready_sender, handle_item_sender, paused_sender, stopped_sender, unprepared_sender, }, context, ); assert!(prepare_status.is_pending()); match prepare_status { Pending { trigger: Prepare, origin: Unprepared, .. } => (), other => panic!("{other:?}"), }; gst::debug!(RUNTIME_CAT, "nominal: starting (async prepare)"); let start_status = task.start().check().unwrap(); block_on(prepared_receiver.next()).unwrap(); // also tests await_maybe_on_context assert_eq!( prepare_status.await_maybe_on_context().unwrap(), Complete { origin: Unprepared, target: Prepared, }, ); block_on(started_receiver.next()).unwrap(); assert_eq!( start_status.block_on().unwrap(), Complete { origin: Prepared, target: Started, } ); assert_eq!(task.state(), Started); // unlock task loop and keep looping block_on(try_next_ready_receiver.next()).unwrap(); block_on(try_next_sender.send(())).unwrap(); block_on(handle_item_ready_receiver.next()).unwrap(); block_on(handle_item_receiver.next()).unwrap(); gst::debug!(RUNTIME_CAT, "nominal: starting (redundant)"); // already started assert_eq!( task.start().block_on().unwrap(), Skipped { trigger: Start, state: Started, }, ); assert_eq!(task.state(), Started); // Attempt to prepare Task in state Started (also tests check) match task.unprepare().check().unwrap_err() { TransitionError { trigger: Unprepare, state: Started, .. } => (), other => panic!("{other:?}"), } gst::debug!(RUNTIME_CAT, "nominal: pause cancelling try_next"); block_on(try_next_ready_receiver.next()).unwrap(); let pause_status = task.pause().check().unwrap(); gst::debug!(RUNTIME_CAT, "nominal: awaiting pause ack"); block_on(paused_receiver.next()).unwrap(); assert_eq!( pause_status.block_on().unwrap(), Complete { origin: Started, target: Paused, }, ); // handle_item not reached assert!(handle_item_ready_receiver.try_next().is_err()); // try_next not reached again assert!(try_next_ready_receiver.try_next().is_err()); gst::debug!( RUNTIME_CAT, "nominal: starting (after pause cancelling try_next)" ); let start_receiver = task.start().check().unwrap(); block_on(started_receiver.next()).unwrap(); assert_eq!( start_receiver.block_on().unwrap(), Complete { origin: Paused, target: Started, }, ); assert_eq!(task.state(), Started); gst::debug!(RUNTIME_CAT, "nominal: pause // handle_item"); block_on(try_next_ready_receiver.next()).unwrap(); block_on(try_next_sender.send(())).unwrap(); // Make sure item is picked block_on(handle_item_ready_receiver.next()).unwrap(); gst::debug!(RUNTIME_CAT, "nominal: requesting to pause"); let pause_status = task.pause().check().unwrap(); gst::debug!(RUNTIME_CAT, "nominal: unlocking item handling"); block_on(handle_item_receiver.next()).unwrap(); gst::debug!(RUNTIME_CAT, "nominal: awaiting pause ack"); block_on(paused_receiver.next()).unwrap(); assert_eq!( pause_status.block_on().unwrap(), Complete { origin: Started, target: Paused, }, ); // try_next not reached again assert!(try_next_ready_receiver.try_next().is_err()); gst::debug!( RUNTIME_CAT, "nominal: starting (after pause // handle_item)" ); let start_receiver = task.start().check().unwrap(); block_on(started_receiver.next()).unwrap(); assert_eq!( start_receiver.block_on().unwrap(), Complete { origin: Paused, target: Started, }, ); assert_eq!(task.state(), Started); gst::debug!(RUNTIME_CAT, "nominal: stopping"); assert_eq!( task.stop().block_on().unwrap(), Complete { origin: Started, target: Stopped, }, ); assert_eq!(task.state(), Stopped); let _ = block_on(stopped_receiver.next()); // purge remaining iteration received before stop if any let _ = try_next_ready_receiver.try_next(); gst::debug!(RUNTIME_CAT, "nominal: starting (after stop)"); assert_eq!( task.start().block_on().unwrap(), Complete { origin: Stopped, target: Started, }, ); let _ = block_on(started_receiver.next()); gst::debug!(RUNTIME_CAT, "nominal: stopping"); assert_eq!( task.stop().block_on().unwrap(), Complete { origin: Started, target: Stopped, }, ); assert_eq!( task.unprepare().block_on().unwrap(), Complete { origin: Stopped, target: Unprepared, }, ); assert_eq!(task.state(), Unprepared); let _ = block_on(unprepared_receiver.next()); } #[test] fn prepare_error() { gst::init().unwrap(); struct TaskPrepareTest { prepare_error_sender: mpsc::Sender<()>, } impl TaskImpl for TaskPrepareTest { type Item = (); fn prepare(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "prepare_error: prepare returning an error"); Err(gst::error_msg!( gst::ResourceError::Failed, ["prepare_error: intentional error"] )) } .boxed() } fn handle_action_error( &mut self, trigger: Trigger, state: TaskState, err: gst::ErrorMessage, ) -> BoxFuture<'_, Trigger> { async move { gst::debug!( RUNTIME_CAT, "prepare_error: handling prepare error {:?}", err ); match (trigger, state) { (Prepare, Unprepared) => { self.prepare_error_sender.send(()).await.unwrap(); } other => unreachable!("{:?}", other), } Trigger::Error } .boxed() } fn try_next(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { unreachable!("prepare_error: try_next"); } fn handle_item(&mut self, _item: ()) -> BoxFuture<'_, Result<(), gst::FlowError>> { unreachable!("prepare_error: handle_item"); } } let context = Context::acquire("prepare_error", Duration::from_millis(2)).unwrap(); let task = Task::default(); assert_eq!(task.state(), Unprepared); let (prepare_error_sender, mut prepare_error_receiver) = mpsc::channel(1); let prepare_status = task.prepare( TaskPrepareTest { prepare_error_sender, }, context, ); gst::debug!( RUNTIME_CAT, "prepare_error: await action error notification" ); block_on(prepare_error_receiver.next()).unwrap(); match prepare_status.block_on().unwrap_err() { TransitionError { trigger: Trigger::Error, state: Preparing, .. } => (), other => panic!("{other:?}"), } // Wait for state machine to reach Error while TaskState::Error != task.state() { std::thread::sleep(Duration::from_millis(2)); } match task.start().block_on().unwrap_err() { TransitionError { trigger: Start, state: TaskState::Error, .. } => (), other => panic!("{other:?}"), } block_on(task.unprepare()).unwrap(); } #[test] fn prepare_start_ok() { // Hold the preparation function so that it completes after the start request is engaged gst::init().unwrap(); struct TaskPrepareTest { prepare_receiver: mpsc::Receiver<()>, } impl TaskImpl for TaskPrepareTest { type Item = (); fn prepare(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!( RUNTIME_CAT, "prepare_start_ok: preparation awaiting trigger" ); self.prepare_receiver.next().await.unwrap(); gst::debug!(RUNTIME_CAT, "prepare_start_ok: preparation complete Ok"); Ok(()) } .boxed() } fn handle_action_error( &mut self, _trigger: Trigger, _state: TaskState, _err: gst::ErrorMessage, ) -> BoxFuture<'_, Trigger> { unreachable!("prepare_start_ok: handle_prepare_error"); } fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "prepare_start_ok: started"); Ok(()) } .boxed() } fn try_next(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { future::pending::>().boxed() } fn handle_item(&mut self, _item: ()) -> BoxFuture<'_, Result<(), gst::FlowError>> { unreachable!("prepare_start_ok: handle_item"); } } let context = Context::acquire("prepare_start_ok", Duration::from_millis(2)).unwrap(); let task = Task::default(); let (mut prepare_sender, prepare_receiver) = mpsc::channel(1); let fut = task.prepare(TaskPrepareTest { prepare_receiver }, context); drop(fut); let start_ctx = Context::acquire("prepare_start_ok_requester", Duration::ZERO).unwrap(); let (ready_sender, ready_receiver) = oneshot::channel(); let start_handle = start_ctx.spawn(async move { assert_eq!(task.state(), Preparing); gst::debug!(RUNTIME_CAT, "prepare_start_ok: starting"); let start_status = task.start(); match start_status { Pending { trigger: Start, origin: Preparing, .. } => (), other => panic!("{other:?}"), } ready_sender.send(()).unwrap(); assert_eq!( start_status.await.unwrap(), Complete { origin: Prepared, target: Started, }, ); assert_eq!(task.state(), Started); let stop_status = task.stop(); match stop_status { Pending { trigger: Stop, origin: Started, .. } => (), other => panic!("{other:?}"), } assert_eq!( stop_status.await.unwrap(), Complete { origin: Started, target: Stopped, }, ); assert_eq!(task.state(), Stopped); let unprepare_status = task.unprepare(); match unprepare_status { Pending { trigger: Unprepare, origin: Stopped, .. } => (), other => panic!("{other:?}"), }; assert_eq!( unprepare_status.await.unwrap(), Complete { origin: Stopped, target: Unprepared, }, ); assert_eq!(task.state(), Unprepared); }); gst::debug!(RUNTIME_CAT, "prepare_start_ok: awaiting for start_ctx"); block_on(ready_receiver).unwrap(); gst::debug!(RUNTIME_CAT, "prepare_start_ok: triggering preparation"); block_on(prepare_sender.send(())).unwrap(); block_on(start_handle).unwrap(); } #[test] fn prepare_start_error() { // Hold the preparation function so that it completes after the start request is engaged gst::init().unwrap(); struct TaskPrepareTest { prepare_receiver: mpsc::Receiver<()>, prepare_error_sender: mpsc::Sender<()>, } impl TaskImpl for TaskPrepareTest { type Item = (); fn prepare(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!( RUNTIME_CAT, "prepare_start_error: preparation awaiting trigger" ); self.prepare_receiver.next().await.unwrap(); gst::debug!(RUNTIME_CAT, "prepare_start_error: preparation complete Err"); Err(gst::error_msg!( gst::ResourceError::Failed, ["prepare_start_error: intentional error"] )) } .boxed() } fn handle_action_error( &mut self, trigger: Trigger, state: TaskState, err: gst::ErrorMessage, ) -> BoxFuture<'_, Trigger> { async move { gst::debug!( RUNTIME_CAT, "prepare_start_error: handling prepare error {:?}", err ); match (trigger, state) { (Prepare, Unprepared) => { self.prepare_error_sender.send(()).await.unwrap(); } other => panic!("action error for {other:?}"), } Trigger::Error } .boxed() } fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { unreachable!("prepare_start_error: start"); } fn try_next(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { unreachable!("prepare_start_error: try_next"); } fn handle_item(&mut self, _item: ()) -> BoxFuture<'_, Result<(), gst::FlowError>> { unreachable!("prepare_start_error: handle_item"); } } let context = Context::acquire("prepare_start_error", Duration::from_millis(2)).unwrap(); let task = Task::default(); let (mut prepare_sender, prepare_receiver) = mpsc::channel(1); let (prepare_error_sender, mut prepare_error_receiver) = mpsc::channel(1); let prepare_status = task.prepare( TaskPrepareTest { prepare_receiver, prepare_error_sender, }, context, ); match prepare_status { Pending { trigger: Prepare, origin: Unprepared, .. } => (), other => panic!("{other:?}"), }; let start_ctx = Context::acquire("prepare_start_error_requester", Duration::ZERO).unwrap(); let (ready_sender, ready_receiver) = oneshot::channel(); let start_handle = start_ctx.spawn(async move { gst::debug!(RUNTIME_CAT, "prepare_start_error: starting (Err)"); let fut = task.start(); drop(fut); ready_sender.send(()).unwrap(); // FIXME we loose the origin Trigger (Start) // and only get the Trigger returned by handle_action_error // see also: comment in exec_action! match prepare_status.await { Err(TransitionError { trigger: Trigger::Error, state: Preparing, .. }) => (), other => panic!("{other:?}"), } let unprepare_status = task.unprepare(); match unprepare_status { Pending { trigger: Unprepare, origin: TaskState::Error, .. } => (), other => panic!("{other:?}"), }; assert_eq!( unprepare_status.await.unwrap(), Complete { origin: TaskState::Error, target: Unprepared, }, ); }); gst::debug!(RUNTIME_CAT, "prepare_start_error: awaiting for start_ctx"); block_on(ready_receiver).unwrap(); gst::debug!( RUNTIME_CAT, "prepare_start_error: triggering preparation (failure)" ); block_on(prepare_sender.send(())).unwrap(); gst::debug!( RUNTIME_CAT, "prepare_start_error: await prepare error notification" ); block_on(prepare_error_receiver.next()).unwrap(); block_on(start_handle).unwrap(); } #[test] fn item_error() { gst::init().unwrap(); struct TaskTest { try_next_receiver: mpsc::Receiver, } impl TaskImpl for TaskTest { type Item = gst::FlowError; fn try_next(&mut self) -> BoxFuture<'_, Result> { async move { gst::debug!(RUNTIME_CAT, "item_error: awaiting try_next"); Ok(self.try_next_receiver.next().await.unwrap()) } .boxed() } fn handle_item( &mut self, item: gst::FlowError, ) -> BoxFuture<'_, Result<(), gst::FlowError>> { async move { gst::debug!(RUNTIME_CAT, "item_error: handle_item received {:?}", item); Err(item) } .boxed() } } let context = Context::acquire("item_error", Duration::from_millis(2)).unwrap(); let task = Task::default(); gst::debug!(RUNTIME_CAT, "item_error: prepare and start"); let (mut try_next_sender, try_next_receiver) = mpsc::channel(1); task.prepare(TaskTest { try_next_receiver }, context) .block_on() .unwrap(); task.start().block_on().unwrap(); gst::debug!(RUNTIME_CAT, "item_error: req. handle_item to return Eos"); block_on(try_next_sender.send(gst::FlowError::Eos)).unwrap(); // Wait for state machine to reach Stopped while Stopped != task.state() { std::thread::sleep(Duration::from_millis(2)); } gst::debug!(RUNTIME_CAT, "item_error: starting (after stop)"); assert_eq!( task.start().block_on().unwrap(), Complete { origin: Stopped, target: Started, }, ); gst::debug!(RUNTIME_CAT, "item_error: req. handle_item to return Error"); block_on(try_next_sender.send(gst::FlowError::Error)).unwrap(); // Wait for state machine to reach Error while TaskState::Error != task.state() { std::thread::sleep(Duration::from_millis(2)); } gst::debug!(RUNTIME_CAT, "item_error: attempting to start (after Error)"); match task.start().block_on().unwrap_err() { TransitionError { trigger: Start, state: TaskState::Error, .. } => (), other => panic!("{other:?}"), } assert_eq!( task.unprepare().block_on().unwrap(), Complete { origin: TaskState::Error, target: Unprepared, }, ); } #[test] fn flush_regular_sync() { gst::init().unwrap(); struct TaskFlushTest { flush_start_sender: mpsc::Sender<()>, flush_stop_sender: mpsc::Sender<()>, } impl TaskImpl for TaskFlushTest { type Item = (); fn try_next(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { future::pending::>().boxed() } fn handle_item(&mut self, _item: ()) -> BoxFuture<'_, Result<(), gst::FlowError>> { unreachable!("flush_regular_sync: handle_item"); } fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "flush_regular_sync: started flushing"); self.flush_start_sender.send(()).await.unwrap(); Ok(()) } .boxed() } fn flush_stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "flush_regular_sync: stopped flushing"); self.flush_stop_sender.send(()).await.unwrap(); Ok(()) } .boxed() } } let context = Context::acquire("flush_regular_sync", Duration::from_millis(2)).unwrap(); let task = Task::default(); let (flush_start_sender, mut flush_start_receiver) = mpsc::channel(1); let (flush_stop_sender, mut flush_stop_receiver) = mpsc::channel(1); let fut = task.prepare( TaskFlushTest { flush_start_sender, flush_stop_sender, }, context, ); drop(fut); gst::debug!(RUNTIME_CAT, "flush_regular_sync: start"); block_on(task.start()).unwrap(); gst::debug!(RUNTIME_CAT, "flush_regular_sync: starting flush"); assert_eq!( task.flush_start().block_on().unwrap(), Complete { origin: Started, target: Flushing, }, ); assert_eq!(task.state(), Flushing); block_on(flush_start_receiver.next()).unwrap(); gst::debug!(RUNTIME_CAT, "flush_regular_sync: stopping flush"); assert_eq!( task.flush_stop().await_maybe_on_context().unwrap(), Complete { origin: Flushing, target: Started, }, ); assert_eq!(task.state(), Started); block_on(flush_stop_receiver.next()).unwrap(); let fut = task.pause(); drop(fut); stop_then_unprepare(task); } #[test] fn flush_regular_different_context() { // Purpose: make sure a flush sequence triggered from a Context doesn't block. gst::init().unwrap(); struct TaskFlushTest { flush_start_sender: mpsc::Sender<()>, flush_stop_sender: mpsc::Sender<()>, } impl TaskImpl for TaskFlushTest { type Item = (); fn try_next(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { future::pending::>().boxed() } fn handle_item(&mut self, _item: ()) -> BoxFuture<'_, Result<(), gst::FlowError>> { unreachable!("flush_regular_different_context: handle_item"); } fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!( RUNTIME_CAT, "flush_regular_different_context: started flushing" ); self.flush_start_sender.send(()).await.unwrap(); Ok(()) } .boxed() } fn flush_stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!( RUNTIME_CAT, "flush_regular_different_context: stopped flushing" ); self.flush_stop_sender.send(()).await.unwrap(); Ok(()) } .boxed() } } let context = Context::acquire("flush_regular_different_context", Duration::from_millis(2)).unwrap(); let task = Task::default(); let (flush_start_sender, mut flush_start_receiver) = mpsc::channel(1); let (flush_stop_sender, mut flush_stop_receiver) = mpsc::channel(1); let fut = task.prepare( TaskFlushTest { flush_start_sender, flush_stop_sender, }, context, ); drop(fut); gst::debug!(RUNTIME_CAT, "flush_regular_different_context: start"); task.start().block_on().unwrap(); let oob_context = Context::acquire( "flush_regular_different_context_oob", Duration::from_millis(2), ) .unwrap(); let task_clone = task.clone(); let flush_res_fut = oob_context.spawn(async move { let flush_start_status = task_clone.flush_start(); match flush_start_status { Pending { trigger: FlushStart, origin: Started, .. } => (), other => panic!("{other:?}"), }; assert_eq!( flush_start_status.await.unwrap(), Complete { origin: Started, target: Flushing, }, ); assert_eq!(task_clone.state(), Flushing); flush_start_receiver.next().await.unwrap(); let flush_stop_status = task_clone.flush_stop(); match flush_stop_status { Pending { trigger: FlushStop, origin: Flushing, .. } => (), other => panic!("{other:?}"), }; assert_eq!( flush_stop_status.await_maybe_on_context().unwrap(), NotWaiting { trigger: FlushStop, origin: Flushing, }, ); Context::drain_sub_tasks().await.unwrap(); assert_eq!(task_clone.state(), Started); }); block_on(flush_res_fut).unwrap(); block_on(flush_stop_receiver.next()).unwrap(); stop_then_unprepare(task); } #[test] fn flush_regular_same_context() { // Purpose: make sure a flush sequence triggered from the same Context doesn't block. gst::init().unwrap(); struct TaskFlushTest { flush_start_sender: mpsc::Sender<()>, flush_stop_sender: mpsc::Sender<()>, } impl TaskImpl for TaskFlushTest { type Item = (); fn try_next(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { future::pending::>().boxed() } fn handle_item(&mut self, _item: ()) -> BoxFuture<'_, Result<(), gst::FlowError>> { unreachable!("flush_regular_same_context: handle_item"); } fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "flush_regular_same_context: started flushing"); self.flush_start_sender.send(()).await.unwrap(); Ok(()) } .boxed() } fn flush_stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "flush_regular_same_context: stopped flushing"); self.flush_stop_sender.send(()).await.unwrap(); Ok(()) } .boxed() } } let context = Context::acquire("flush_regular_same_context", Duration::from_millis(2)).unwrap(); let task = Task::default(); let (flush_start_sender, mut flush_start_receiver) = mpsc::channel(1); let (flush_stop_sender, mut flush_stop_receiver) = mpsc::channel(1); let fut = task.prepare( TaskFlushTest { flush_start_sender, flush_stop_sender, }, context.clone(), ); drop(fut); block_on(task.start()).unwrap(); let task_clone = task.clone(); let flush_handle = context.spawn(async move { let flush_start_status = task_clone.flush_start(); match flush_start_status { Pending { trigger: FlushStart, origin: Started, .. } => (), other => panic!("{other:?}"), }; assert_eq!( flush_start_status.await.unwrap(), Complete { origin: Started, target: Flushing, }, ); assert_eq!(task_clone.state(), Flushing); flush_start_receiver.next().await.unwrap(); let flush_stop_status = task_clone.flush_stop(); match flush_stop_status { Pending { trigger: FlushStop, origin: Flushing, .. } => (), other => panic!("{other:?}"), }; assert_eq!( flush_stop_status.await.unwrap(), Complete { origin: Flushing, target: Started, }, ); assert_eq!(task_clone.state(), Started); }); block_on(flush_handle).unwrap(); block_on(flush_stop_receiver.next()).unwrap(); stop_then_unprepare(task); } #[test] fn flush_from_loop() { // Purpose: make sure a flush_start triggered from an iteration doesn't block. gst::init().unwrap(); struct TaskFlushTest { task: Task, flush_start_sender: mpsc::Sender<()>, } impl TaskImpl for TaskFlushTest { type Item = (); fn try_next(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { future::ok(()).boxed() } fn handle_item(&mut self, _item: ()) -> BoxFuture<'_, Result<(), gst::FlowError>> { async move { gst::debug!(RUNTIME_CAT, "flush_from_loop: flush_start from handle_item"); match self.task.flush_start() { Pending { trigger: FlushStart, origin: Started, .. } => (), other => panic!("{other:?}"), } Ok(()) } .boxed() } fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "flush_from_loop: started flushing"); self.flush_start_sender.send(()).await.unwrap(); Ok(()) } .boxed() } } let context = Context::acquire("flush_from_loop", Duration::from_millis(2)).unwrap(); let task = Task::default(); let (flush_start_sender, mut flush_start_receiver) = mpsc::channel(1); let fut = task.prepare( TaskFlushTest { task: task.clone(), flush_start_sender, }, context, ); drop(fut); let fut = task.start(); drop(fut); gst::debug!( RUNTIME_CAT, "flush_from_loop: awaiting flush_start notification" ); block_on(flush_start_receiver.next()).unwrap(); assert_eq!( task.stop().block_on().unwrap(), Complete { origin: Flushing, target: Stopped, }, ); task.unprepare().block_on().unwrap(); } #[test] fn pause_from_loop() { // Purpose: make sure a start triggered from an iteration doesn't block. // E.g. an auto pause cancellation after a delay. gst::init().unwrap(); struct TaskStartTest { task: Task, pause_sender: mpsc::Sender<()>, } impl TaskImpl for TaskStartTest { type Item = (); fn try_next(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { future::ok(()).boxed() } fn handle_item(&mut self, _item: ()) -> BoxFuture<'_, Result<(), gst::FlowError>> { async move { gst::debug!(RUNTIME_CAT, "pause_from_loop: entering handle_item"); crate::runtime::timer::delay_for(Duration::from_millis(50)).await; gst::debug!(RUNTIME_CAT, "pause_from_loop: pause from handle_item"); match self.task.pause() { Pending { trigger: Pause, origin: Started, .. } => (), other => panic!("{other:?}"), } Ok(()) } .boxed() } fn pause(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "pause_from_loop: entering pause action"); self.pause_sender.send(()).await.unwrap(); Ok(()) } .boxed() } } let context = Context::acquire("pause_from_loop", Duration::from_millis(2)).unwrap(); let task = Task::default(); let (pause_sender, mut pause_receiver) = mpsc::channel(1); let fut = task.prepare( TaskStartTest { task: task.clone(), pause_sender, }, context, ); drop(fut); let fut = task.start(); drop(fut); gst::debug!(RUNTIME_CAT, "pause_from_loop: awaiting pause notification"); block_on(pause_receiver.next()).unwrap(); stop_then_unprepare(task); } #[test] fn trigger_from_action() { // Purpose: make sure an event triggered from a transition action doesn't block. gst::init().unwrap(); struct TaskFlushTest { task: Task, flush_stop_sender: mpsc::Sender<()>, } impl TaskImpl for TaskFlushTest { type Item = (); fn try_next(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { future::pending::>().boxed() } fn handle_item(&mut self, _item: ()) -> BoxFuture<'_, Result<(), gst::FlowError>> { unreachable!("trigger_from_action: handle_item"); } fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!( RUNTIME_CAT, "trigger_from_action: flush_start triggering flush_stop" ); match self.task.flush_stop() { Pending { trigger: FlushStop, origin: Started, .. } => (), other => panic!("{other:?}"), } Ok(()) } .boxed() } fn flush_stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "trigger_from_action: stopped flushing"); self.flush_stop_sender.send(()).await.unwrap(); Ok(()) } .boxed() } } let context = Context::acquire("trigger_from_action", Duration::from_millis(2)).unwrap(); let task = Task::default(); let (flush_stop_sender, mut flush_stop_receiver) = mpsc::channel(1); let fut = task.prepare( TaskFlushTest { task: task.clone(), flush_stop_sender, }, context, ); drop(fut); task.start().block_on().unwrap(); let fut = task.flush_start(); drop(fut); gst::debug!( RUNTIME_CAT, "trigger_from_action: awaiting flush_stop notification" ); block_on(flush_stop_receiver.next()).unwrap(); stop_then_unprepare(task); } #[test] fn pause_flush_start() { gst::init().unwrap(); struct TaskFlushTest { started_sender: mpsc::Sender<()>, flush_start_sender: mpsc::Sender<()>, flush_stop_sender: mpsc::Sender<()>, } impl TaskImpl for TaskFlushTest { type Item = (); fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "pause_flush_start: started"); self.started_sender.send(()).await.unwrap(); Ok(()) } .boxed() } fn try_next(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { future::pending::>().boxed() } fn handle_item(&mut self, _item: ()) -> BoxFuture<'_, Result<(), gst::FlowError>> { unreachable!("pause_flush_start: handle_item"); } fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "pause_flush_start: started flushing"); self.flush_start_sender.send(()).await.unwrap(); Ok(()) } .boxed() } fn flush_stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "pause_flush_start: stopped flushing"); self.flush_stop_sender.send(()).await.unwrap(); Ok(()) } .boxed() } } let context = Context::acquire("pause_flush_start", Duration::from_millis(2)).unwrap(); let task = Task::default(); let (started_sender, mut started_receiver) = mpsc::channel(1); let (flush_start_sender, mut flush_start_receiver) = mpsc::channel(1); let (flush_stop_sender, mut flush_stop_receiver) = mpsc::channel(1); let fut = task.prepare( TaskFlushTest { started_sender, flush_start_sender, flush_stop_sender, }, context, ); drop(fut); // Pause, FlushStart, FlushStop, Start gst::debug!(RUNTIME_CAT, "pause_flush_start: pausing"); assert_eq!( task.pause().block_on().unwrap(), Complete { origin: Prepared, target: Paused, }, ); gst::debug!(RUNTIME_CAT, "pause_flush_start: starting flush"); assert_eq!( task.flush_start().block_on().unwrap(), Complete { origin: Paused, target: PausedFlushing, }, ); assert_eq!(task.state(), PausedFlushing); block_on(flush_start_receiver.next()); gst::debug!(RUNTIME_CAT, "pause_flush_start: stopping flush"); assert_eq!( task.flush_stop().block_on().unwrap(), Complete { origin: PausedFlushing, target: Paused, }, ); assert_eq!(task.state(), Paused); block_on(flush_stop_receiver.next()); // start action not executed started_receiver.try_next().unwrap_err(); gst::debug!(RUNTIME_CAT, "pause_flush_start: starting after flushing"); assert_eq!( task.start().block_on().unwrap(), Complete { origin: Paused, target: Started, }, ); assert_eq!(task.state(), Started); block_on(started_receiver.next()); stop_then_unprepare(task); } #[test] fn pause_flushing_start() { gst::init().unwrap(); struct TaskFlushTest { started_sender: mpsc::Sender<()>, flush_start_sender: mpsc::Sender<()>, flush_stop_sender: mpsc::Sender<()>, } impl TaskImpl for TaskFlushTest { type Item = (); fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "pause_flushing_start: started"); self.started_sender.send(()).await.unwrap(); Ok(()) } .boxed() } fn try_next(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { future::pending::>().boxed() } fn handle_item(&mut self, _item: ()) -> BoxFuture<'_, Result<(), gst::FlowError>> { unreachable!("pause_flushing_start: handle_item"); } fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "pause_flushing_start: started flushing"); self.flush_start_sender.send(()).await.unwrap(); Ok(()) } .boxed() } fn flush_stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "pause_flushing_start: stopped flushing"); self.flush_stop_sender.send(()).await.unwrap(); Ok(()) } .boxed() } } let context = Context::acquire("pause_flushing_start", Duration::from_millis(2)).unwrap(); let task = Task::default(); let (started_sender, mut started_receiver) = mpsc::channel(1); let (flush_start_sender, mut flush_start_receiver) = mpsc::channel(1); let (flush_stop_sender, mut flush_stop_receiver) = mpsc::channel(1); let fut = task.prepare( TaskFlushTest { started_sender, flush_start_sender, flush_stop_sender, }, context, ); drop(fut); // Pause, FlushStart, Start, FlushStop gst::debug!(RUNTIME_CAT, "pause_flushing_start: pausing"); let fut = task.pause(); drop(fut); gst::debug!(RUNTIME_CAT, "pause_flushing_start: starting flush"); block_on(task.flush_start()).unwrap(); assert_eq!(task.state(), PausedFlushing); block_on(flush_start_receiver.next()); gst::debug!(RUNTIME_CAT, "pause_flushing_start: starting while flushing"); assert_eq!( task.start().block_on().unwrap(), Complete { origin: PausedFlushing, target: Flushing, }, ); assert_eq!(task.state(), Flushing); // start action not executed started_receiver.try_next().unwrap_err(); gst::debug!(RUNTIME_CAT, "pause_flushing_start: stopping flush"); assert_eq!( task.flush_stop().block_on().unwrap(), Complete { origin: Flushing, target: Started, }, ); assert_eq!(task.state(), Started); block_on(flush_stop_receiver.next()); block_on(started_receiver.next()); stop_then_unprepare(task); } #[test] fn flush_concurrent_start() { // Purpose: check the racy case of start being triggered in // after flush_start // e.g.: a FlushStart event received on a Pad and the element starting after a Pause gst::init().unwrap(); struct TaskStartTest { flush_start_sender: mpsc::Sender<()>, flush_stop_sender: mpsc::Sender<()>, } impl TaskImpl for TaskStartTest { type Item = (); fn try_next(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { future::pending::>().boxed() } fn handle_item(&mut self, _item: ()) -> BoxFuture<'_, Result<(), gst::FlowError>> { unreachable!("flush_concurrent_start: handle_item"); } fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "flush_concurrent_start: started flushing"); self.flush_start_sender.send(()).await.unwrap(); Ok(()) } .boxed() } fn flush_stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { gst::debug!(RUNTIME_CAT, "flush_concurrent_start: stopped flushing"); self.flush_stop_sender.send(()).await.unwrap(); Ok(()) } .boxed() } } let context = Context::acquire("flush_concurrent_start", Duration::from_millis(2)).unwrap(); let task = Task::default(); let (flush_start_sender, mut flush_start_receiver) = mpsc::channel(1); let (flush_stop_sender, mut flush_stop_receiver) = mpsc::channel(1); let fut = task.prepare( TaskStartTest { flush_start_sender, flush_stop_sender, }, context, ); drop(fut); let oob_context = Context::acquire("flush_concurrent_start_oob", Duration::from_millis(2)).unwrap(); let task_clone = task.clone(); block_on(task.pause()).unwrap(); // Launch flush_start // start let (ready_sender, ready_receiver) = oneshot::channel(); gst::debug!(RUNTIME_CAT, "flush_concurrent_start: spawning flush_start"); let flush_start_handle = oob_context.spawn(async move { gst::debug!(RUNTIME_CAT, "flush_concurrent_start: // flush_start"); ready_sender.send(()).unwrap(); let status = task_clone.flush_start(); match status { Pending { trigger: FlushStart, origin: Paused, .. } => (), Pending { trigger: FlushStart, origin: Started, .. } => (), other => panic!("{other:?}"), }; status.await.unwrap(); flush_start_receiver.next().await.unwrap(); }); gst::debug!( RUNTIME_CAT, "flush_concurrent_start: awaiting for oob_context" ); block_on(ready_receiver).unwrap(); gst::debug!(RUNTIME_CAT, "flush_concurrent_start: // start"); match block_on(task.start()) { Ok(Complete { origin: Paused, target: Started, }) => (), Ok(Complete { origin: PausedFlushing, target: Flushing, }) => (), other => panic!("{other:?}"), } block_on(flush_start_handle).unwrap(); gst::debug!(RUNTIME_CAT, "flush_concurrent_start: requesting flush_stop"); assert_eq!( task.flush_stop().block_on().unwrap(), Complete { origin: Flushing, target: Started, }, ); assert_eq!(task.state(), Started); block_on(flush_stop_receiver.next()); stop_then_unprepare(task); } #[test] fn start_timer() { use crate::runtime::timer; // Purpose: make sure a Timer initialized in a transition is // available when iterating in the loop. gst::init().unwrap(); struct TaskTimerTest { timer: Option, timer_elapsed_sender: Option>, } impl TaskImpl for TaskTimerTest { type Item = (); fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { self.timer = Some(crate::runtime::timer::delay_for(Duration::from_millis(50))); gst::debug!(RUNTIME_CAT, "start_timer: started"); Ok(()) } .boxed() } fn try_next(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { async move { gst::debug!(RUNTIME_CAT, "start_timer: awaiting timer"); self.timer.take().unwrap().await; Ok(()) } .boxed() } fn handle_item(&mut self, _item: ()) -> BoxFuture<'_, Result<(), gst::FlowError>> { async move { gst::debug!(RUNTIME_CAT, "start_timer: timer elapsed"); if let Some(timer_elapsed_sender) = self.timer_elapsed_sender.take() { timer_elapsed_sender.send(()).unwrap(); } Err(gst::FlowError::Eos) } .boxed() } } let context = Context::acquire("start_timer", Duration::from_millis(2)).unwrap(); let task = Task::default(); let (timer_elapsed_sender, timer_elapsed_receiver) = oneshot::channel(); let fut = task.prepare( TaskTimerTest { timer: None, timer_elapsed_sender: Some(timer_elapsed_sender), }, context, ); drop(fut); gst::debug!(RUNTIME_CAT, "start_timer: start"); let fut = task.start(); drop(fut); block_on(timer_elapsed_receiver).unwrap(); gst::debug!(RUNTIME_CAT, "start_timer: timer elapsed received"); stop_then_unprepare(task); } }