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