qubit_executor/hook/task_hook.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::{
11 panic::{
12 AssertUnwindSafe,
13 catch_unwind,
14 },
15 sync::Arc,
16};
17
18use crate::{
19 TaskStatus,
20 service::SubmissionError,
21};
22
23use super::TaskId;
24
25/// Observes task lifecycle events emitted by executors and executor services.
26///
27/// Hook events follow a strict lifecycle ordering for each task. A rejected
28/// submission emits only [`Self::on_rejected`] and never receives a task id. An
29/// accepted submission emits [`Self::on_accepted`] before any later
30/// [`Self::on_started`] or [`Self::on_finished`] event for the same task.
31/// `on_started` is emitted only when the task actually begins running; a task
32/// cancelled before start or abandoned by its accepted runner endpoint may emit
33/// `on_finished` without `on_started`.
34///
35/// Executors contain hook panics so hook implementations cannot break task
36/// execution or result publication.
37pub trait TaskHook: Send + Sync + 'static {
38 /// Called after a task is accepted.
39 ///
40 /// # Parameters
41 ///
42 /// * `task_id` - Identifier assigned to the accepted task.
43 #[inline]
44 fn on_accepted(&self, _task_id: TaskId) {}
45
46 /// Called when a submitted task is rejected.
47 ///
48 /// Rejected tasks do not have task ids and never emit started or finished
49 /// events.
50 ///
51 /// # Parameters
52 ///
53 /// * `error` - Submission error explaining the rejection.
54 #[inline]
55 fn on_rejected(&self, _error: &SubmissionError) {}
56
57 /// Called immediately before an accepted task starts running.
58 ///
59 /// This callback is emitted after [`Self::on_accepted`] for the same task.
60 ///
61 /// # Parameters
62 ///
63 /// * `task_id` - Identifier assigned to the accepted task.
64 #[inline]
65 fn on_started(&self, _task_id: TaskId) {}
66
67 /// Called after an accepted task reaches a terminal status.
68 ///
69 /// This callback is emitted after [`Self::on_accepted`] for the same task.
70 /// It is normally emitted after [`Self::on_started`], except for pre-start
71 /// cancellation or accepted runner-endpoint abandonment.
72 ///
73 /// # Parameters
74 ///
75 /// * `task_id` - Identifier assigned to the accepted task.
76 /// * `status` - Terminal status observed for the task.
77 #[inline]
78 fn on_finished(&self, _task_id: TaskId, _status: TaskStatus) {}
79}
80
81/// Calls a task hook and contains hook panics.
82///
83/// # Parameters
84///
85/// * `callback` - Hook callback to invoke.
86fn contain_hook_panic(callback: impl FnOnce()) {
87 if catch_unwind(AssertUnwindSafe(callback)).is_err() {
88 log::warn!("task hook panicked");
89 }
90}
91
92/// Notifies a hook that a task was accepted.
93///
94/// # Parameters
95///
96/// * `hook` - Hook to notify.
97/// * `task_id` - Accepted task id.
98#[inline]
99pub(crate) fn notify_accepted(hook: &dyn TaskHook, task_id: TaskId) {
100 contain_hook_panic(|| hook.on_accepted(task_id));
101}
102
103/// Notifies a hook that a task was rejected.
104///
105/// # Parameters
106///
107/// * `hook` - Hook to notify.
108/// * `error` - Submission error explaining the rejection.
109#[inline]
110pub(crate) fn notify_rejected(hook: &dyn TaskHook, error: &SubmissionError) {
111 contain_hook_panic(|| hook.on_rejected(error));
112}
113
114/// Notifies an optional hook that a task was rejected.
115///
116/// # Parameters
117///
118/// * `hook` - Optional hook to notify.
119/// * `error` - Submission error explaining the rejection.
120#[inline]
121pub(crate) fn notify_rejected_optional(hook: Option<&Arc<dyn TaskHook>>, error: &SubmissionError) {
122 if let Some(hook) = hook {
123 notify_rejected(hook.as_ref(), error);
124 }
125}
126
127/// Notifies a hook that a task started.
128///
129/// # Parameters
130///
131/// * `hook` - Hook to notify.
132/// * `task_id` - Started task id.
133#[inline]
134pub(crate) fn notify_started(hook: &dyn TaskHook, task_id: TaskId) {
135 contain_hook_panic(|| hook.on_started(task_id));
136}
137
138/// Notifies a hook that a task finished.
139///
140/// # Parameters
141///
142/// * `hook` - Hook to notify.
143/// * `task_id` - Finished task id.
144/// * `status` - Terminal task status.
145#[inline]
146pub(crate) fn notify_finished(hook: &dyn TaskHook, task_id: TaskId, status: TaskStatus) {
147 contain_hook_panic(|| hook.on_finished(task_id, status));
148}