Skip to main content

qubit_executor/task/
task_runner.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10use std::panic::{
11    AssertUnwindSafe,
12    catch_unwind,
13};
14
15use qubit_function::Callable;
16
17use super::{
18    TaskExecutionError,
19    TaskResult,
20    running_task_slot::RunningTaskSlot,
21    task_slot::TaskSlot,
22};
23
24/// Runner that executes a callable task with standard task-handle semantics.
25///
26/// `TaskRunner` owns the accepted callable, converts task failures and panics
27/// into [`TaskExecutionError`], and can publish the final result through a
28/// [`TaskSlot`] endpoint.
29pub struct TaskRunner<C> {
30    /// Callable task owned by this runner.
31    task: C,
32}
33
34impl<C> TaskRunner<C> {
35    /// Creates a runner for the supplied callable task.
36    ///
37    /// # Parameters
38    ///
39    /// * `task` - Callable task to execute later.
40    ///
41    /// # Returns
42    ///
43    /// A runner that owns the callable task.
44    #[inline]
45    pub const fn new(task: C) -> Self {
46        Self { task }
47    }
48
49    /// Runs the callable and converts task failure and panic into a handle result.
50    ///
51    /// # Returns
52    ///
53    /// `Ok(R)` if the task succeeds. If the task returns `Err(E)` or panics, the
54    /// corresponding [`TaskExecutionError`] is returned.
55    pub fn call<R, E>(self) -> TaskResult<R, E>
56    where
57        C: Callable<R, E>,
58    {
59        let mut task = self.task;
60        match catch_unwind(AssertUnwindSafe(|| task.call())) {
61            Ok(Ok(value)) => Ok(value),
62            Ok(Err(err)) => Err(TaskExecutionError::Failed(err)),
63            Err(_) => Err(TaskExecutionError::Panicked),
64        }
65    }
66
67    /// Runs this task through a task completion endpoint.
68    ///
69    /// # Parameters
70    ///
71    /// * `completion` - Completion endpoint that publishes the final result.
72    ///
73    /// # Returns
74    ///
75    /// `true` if the task started and its result was published, or `false` if
76    /// the completion endpoint had already been completed before the runner
77    /// started.
78    #[inline]
79    pub fn run<R, E>(self, slot: TaskSlot<R, E>) -> bool
80    where
81        C: Callable<R, E>,
82    {
83        slot.start_and_complete(|| self.call())
84    }
85
86    /// Runs this task through an already-started task slot.
87    ///
88    /// # Parameters
89    ///
90    /// * `slot` - Running slot that has already won the pending-to-running
91    ///   transition.
92    ///
93    /// # Returns
94    ///
95    /// `true` if the task result was published, or `false` if completion was
96    /// no longer possible.
97    #[inline]
98    pub fn run_started<R, E>(self, slot: RunningTaskSlot<R, E>) -> bool
99    where
100        C: Callable<R, E>,
101    {
102        slot.complete(self.call())
103    }
104
105    /// Runs this task and discards its final result.
106    ///
107    /// This is intended for detached runnable submissions whose caller only
108    /// observes acceptance. The callable is still wrapped in panic conversion,
109    /// but success, task failure, and panic results are intentionally dropped.
110    pub fn run_detached<R, E>(self)
111    where
112        C: Callable<R, E>,
113    {
114        let _ignored = self.call::<R, E>();
115    }
116}