nest_rs_database/
executor.rs1use std::any::Any;
2use std::future::Future;
3use std::sync::Arc;
4
5pub trait Executor: Any + Send + Sync + 'static {
20 fn as_any(&self) -> &dyn Any;
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub enum ExecutorScope {
31 Request,
32 Job,
33}
34
35tokio::task_local! {
36 static EXECUTOR: Arc<dyn Executor>;
37 static EXECUTOR_SCOPE: ExecutorScope;
38}
39
40pub fn current_executor() -> Option<Arc<dyn Executor>> {
43 EXECUTOR.try_with(Arc::clone).ok()
44}
45
46pub fn current_executor_scope() -> Option<ExecutorScope> {
48 EXECUTOR_SCOPE.try_with(Clone::clone).ok()
49}
50
51pub async fn with_executor<F: Future>(executor: Arc<dyn Executor>, fut: F) -> F::Output {
57 EXECUTOR.scope(executor, fut).await
58}
59
60pub async fn with_request_executor<F: Future>(executor: Arc<dyn Executor>, fut: F) -> F::Output {
63 EXECUTOR
64 .scope(executor, EXECUTOR_SCOPE.scope(ExecutorScope::Request, fut))
65 .await
66}
67
68pub async fn with_job_executor<F: Future>(executor: Arc<dyn Executor>, fut: F) -> F::Output {
71 EXECUTOR
72 .scope(executor, EXECUTOR_SCOPE.scope(ExecutorScope::Job, fut))
73 .await
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 struct StubExecutor;
81 impl Executor for StubExecutor {
82 fn as_any(&self) -> &dyn Any {
83 self
84 }
85 }
86
87 fn stub() -> Arc<dyn Executor> {
88 Arc::new(StubExecutor)
89 }
90
91 #[tokio::test]
92 async fn no_ambient_state_outside_any_scope() {
93 assert!(current_executor().is_none());
94 assert!(current_executor_scope().is_none());
95 }
96
97 #[tokio::test]
98 async fn with_executor_installs_but_does_not_tag() {
99 with_executor(stub(), async {
100 assert!(current_executor().is_some());
101 assert!(current_executor_scope().is_none());
102 })
103 .await;
104 }
105
106 #[tokio::test]
107 async fn with_request_executor_tags_request() {
108 with_request_executor(stub(), async {
109 assert_eq!(current_executor_scope(), Some(ExecutorScope::Request));
110 assert!(current_executor().is_some());
111 })
112 .await;
113 }
114
115 #[tokio::test]
116 async fn with_job_executor_tags_job() {
117 with_job_executor(stub(), async {
118 assert_eq!(current_executor_scope(), Some(ExecutorScope::Job));
119 assert!(current_executor().is_some());
120 })
121 .await;
122 }
123
124 #[tokio::test]
125 async fn scope_unwinds_on_exit() {
126 with_request_executor(stub(), async {}).await;
127 assert!(current_executor().is_none());
128 assert!(current_executor_scope().is_none());
129 }
130
131 #[tokio::test]
132 async fn nested_scope_shadows_outer() {
133 with_request_executor(stub(), async {
134 assert_eq!(current_executor_scope(), Some(ExecutorScope::Request));
135 with_job_executor(stub(), async {
136 assert_eq!(current_executor_scope(), Some(ExecutorScope::Job));
137 })
138 .await;
139 assert_eq!(current_executor_scope(), Some(ExecutorScope::Request));
140 })
141 .await;
142 }
143
144 #[tokio::test]
145 async fn downcast_round_trips_the_concrete_type() {
146 with_request_executor(stub(), async {
147 let e = current_executor().expect("installed");
148 assert!(e.as_any().is::<StubExecutor>());
149 })
150 .await;
151 }
152}