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 {
55 EXECUTOR.scope(executor, fut).await
56}
57
58pub async fn with_request_executor<F: Future>(executor: Arc<dyn Executor>, fut: F) -> F::Output {
61 EXECUTOR
62 .scope(executor, EXECUTOR_SCOPE.scope(ExecutorScope::Request, fut))
63 .await
64}
65
66pub async fn with_job_executor<F: Future>(executor: Arc<dyn Executor>, fut: F) -> F::Output {
69 EXECUTOR
70 .scope(executor, EXECUTOR_SCOPE.scope(ExecutorScope::Job, fut))
71 .await
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 struct StubExecutor;
79 impl Executor for StubExecutor {
80 fn as_any(&self) -> &dyn Any {
81 self
82 }
83 }
84
85 fn stub() -> Arc<dyn Executor> {
86 Arc::new(StubExecutor)
87 }
88
89 #[tokio::test]
90 async fn no_ambient_state_outside_any_scope() {
91 assert!(current_executor().is_none());
92 assert!(current_executor_scope().is_none());
93 }
94
95 #[tokio::test]
96 async fn with_executor_installs_but_does_not_tag() {
97 with_executor(stub(), async {
98 assert!(current_executor().is_some());
99 assert!(current_executor_scope().is_none());
100 })
101 .await;
102 }
103
104 #[tokio::test]
105 async fn with_request_executor_tags_request() {
106 with_request_executor(stub(), async {
107 assert_eq!(current_executor_scope(), Some(ExecutorScope::Request));
108 assert!(current_executor().is_some());
109 })
110 .await;
111 }
112
113 #[tokio::test]
114 async fn with_job_executor_tags_job() {
115 with_job_executor(stub(), async {
116 assert_eq!(current_executor_scope(), Some(ExecutorScope::Job));
117 assert!(current_executor().is_some());
118 })
119 .await;
120 }
121
122 #[tokio::test]
123 async fn scope_unwinds_on_exit() {
124 with_request_executor(stub(), async {}).await;
125 assert!(current_executor().is_none());
126 assert!(current_executor_scope().is_none());
127 }
128
129 #[tokio::test]
130 async fn nested_scope_shadows_outer() {
131 with_request_executor(stub(), async {
132 assert_eq!(current_executor_scope(), Some(ExecutorScope::Request));
133 with_job_executor(stub(), async {
134 assert_eq!(current_executor_scope(), Some(ExecutorScope::Job));
135 })
136 .await;
137 assert_eq!(current_executor_scope(), Some(ExecutorScope::Request));
138 })
139 .await;
140 }
141
142 #[tokio::test]
143 async fn downcast_round_trips_the_concrete_type() {
144 with_request_executor(stub(), async {
145 let e = current_executor().expect("installed");
146 assert!(e.as_any().is::<StubExecutor>());
147 })
148 .await;
149 }
150}