소스 검색

StoppableTask: use CondVar instead of channels, add logs and make impl more robust

x 2 년 전
부모
커밋
2c94dfdfa9
1개의 변경된 파일57개의 추가작업 그리고 35개의 파일을 삭제
  1. 57 35
      src/system/stoppable_task.rs

+ 57 - 35
src/system/stoppable_task.rs

@@ -16,47 +16,29 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
  */
 
 
-use std::sync::Arc;
-
+use log::trace;
 use rand::{rngs::OsRng, Rng};
 use rand::{rngs::OsRng, Rng};
 use smol::{
 use smol::{
-    channel,
     future::{self, Future},
     future::{self, Future},
     Executor,
     Executor,
 };
 };
+use std::sync::Arc;
 
 
 use super::CondVar;
 use super::CondVar;
 
 
 pub type StoppableTaskPtr = Arc<StoppableTask>;
 pub type StoppableTaskPtr = Arc<StoppableTask>;
 
 
 pub struct StoppableTask {
 pub struct StoppableTask {
-    // NOTE: we could send the error code from stop() instead of having it specified in start()
-    // but then that would introduce lifetimes to the entire struct.
-    stop_send: channel::Sender<()>,
-    stop_recv: channel::Receiver<()>,
-    stop_barrier: CondVar,
-
-    // Used so we can keep StoppableTask in HashMap/HashSet
-    task_id: usize,
-}
-
-impl std::hash::Hash for StoppableTask {
-    fn hash<H>(&self, state: &mut H)
-    where
-        H: std::hash::Hasher,
-    {
-        self.task_id.hash(state);
-    }
-}
+    /// Used to signal to the main running process that it should stop.
+    signal: CondVar,
+    /// When we call `stop()`, we wait until the process is finished. This is used to prevent
+    /// `stop()` from exiting until the task has closed.
+    barrier: CondVar,
 
 
-impl std::cmp::PartialEq for StoppableTask {
-    fn eq(&self, other: &Self) -> bool {
-        self.task_id == other.task_id
-    }
+    /// Used so we can keep StoppableTask in HashMap/HashSet
+    task_id: u32,
 }
 }
 
 
-impl std::cmp::Eq for StoppableTask {}
-
 /// A task that can be prematurely stopped at any time.
 /// A task that can be prematurely stopped at any time.
 ///
 ///
 /// ```rust
 /// ```rust
@@ -72,15 +54,15 @@ impl std::cmp::Eq for StoppableTask {}
 /// Then at any time we can call `task.stop()` to close the task.
 /// Then at any time we can call `task.stop()` to close the task.
 impl StoppableTask {
 impl StoppableTask {
     pub fn new() -> Arc<Self> {
     pub fn new() -> Arc<Self> {
-        let (stop_send, stop_recv) = channel::bounded(1);
-        Arc::new(Self { stop_send, stop_recv, stop_barrier: CondVar::new(), task_id: OsRng.gen() })
+        Arc::new(Self { signal: CondVar::new(), barrier: CondVar::new(), task_id: OsRng.gen() })
     }
     }
 
 
-    /// Stops the task. Will return when the process has fully closed.
+    /// Stops the task. On completion, guarantees the process has stopped.
     pub async fn stop(&self) {
     pub async fn stop(&self) {
-        // Ignore any errors from this send
-        let _ = self.stop_send.send(()).await;
-        self.stop_barrier.wait().await;
+        trace!(target: "system::StoppableTask", "Stopping task {}", self.task_id);
+        self.signal.notify();
+        self.barrier.wait().await;
+        trace!(target: "system::StoppableTask", "Stopped task {}", self.task_id);
     }
     }
 
 
     /// Starts the task.
     /// Starts the task.
@@ -100,17 +82,57 @@ impl StoppableTask {
         StopFn: FnOnce(std::result::Result<(), Error>) -> StopFut + Send + 'a,
         StopFn: FnOnce(std::result::Result<(), Error>) -> StopFut + Send + 'a,
         Error: std::error::Error + Send + 'a,
         Error: std::error::Error + Send + 'a,
     {
     {
+        // NOTE: we could send the error code from stop() instead of having it specified in start()
+        trace!(target: "system::StoppableTask", "Starting task {}", self.task_id);
+        // Allow stopping and starting task again.
+        // NOTE: maybe we should disallow this with a panic?
+        self.signal.reset();
+        self.barrier.reset();
+
         executor
         executor
             .spawn(async move {
             .spawn(async move {
+                // Task which waits for a stop signal
                 let stop_fut = async {
                 let stop_fut = async {
-                    let _ = self.stop_recv.recv().await;
+                    self.signal.wait().await;
+                    trace!(
+                        target: "system::StoppableTask",
+                        "Stop signal received for task {}",
+                        self.task_id
+                    );
                     Err(stop_value)
                     Err(stop_value)
                 };
                 };
 
 
+                // Wait on our main task or stop task - whichever finishes first
                 let result = future::or(main, stop_fut).await;
                 let result = future::or(main, stop_fut).await;
+
+                trace!(
+                    target: "system::StoppableTask",
+                    "Closing task {} with result: {:?}",
+                    self.task_id,
+                    result
+                );
+
                 stop_handler(result).await;
                 stop_handler(result).await;
-                self.stop_barrier.notify();
+                // Allow `stop()` to finish
+                self.barrier.notify();
             })
             })
             .detach();
             .detach();
     }
     }
 }
 }
+
+impl std::hash::Hash for StoppableTask {
+    fn hash<H>(&self, state: &mut H)
+    where
+        H: std::hash::Hasher,
+    {
+        self.task_id.hash(state);
+    }
+}
+
+impl std::cmp::PartialEq for StoppableTask {
+    fn eq(&self, other: &Self) -> bool {
+        self.task_id == other.task_id
+    }
+}
+
+impl std::cmp::Eq for StoppableTask {}