├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── examples └── basic.rs ├── src ├── ctx.rs ├── data.rs └── lib.rs └── tests └── test_basic.rs /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "execution-context" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "im 10.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "im" 11 | version = "10.2.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | dependencies = [ 14 | "rustc_version 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 15 | ] 16 | 17 | [[package]] 18 | name = "lazy_static" 19 | version = "1.0.1" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | 22 | [[package]] 23 | name = "rustc_version" 24 | version = "0.2.2" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | dependencies = [ 27 | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 28 | ] 29 | 30 | [[package]] 31 | name = "semver" 32 | version = "0.9.0" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | dependencies = [ 35 | "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 36 | ] 37 | 38 | [[package]] 39 | name = "semver-parser" 40 | version = "0.7.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | 43 | [metadata] 44 | "checksum im 10.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "006e5c34d6fc287f91a90f53f19a8a859bade826e3153b137b8e1022ea54250b" 45 | "checksum lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6412c5e2ad9584b0b8e979393122026cdd6d2a80b933f890dcd694ddbe73739" 46 | "checksum rustc_version 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a54aa04a10c68c1c4eacb4337fd883b435997ede17a9385784b990777686b09a" 47 | "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 48 | "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 49 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "execution-context" 3 | version = "0.1.0" 4 | license = "Apache-2.0" 5 | description = "An experimental .NET inspired execution context" 6 | authors = ["Armin Ronacher "] 7 | keywords = ["executioncontext", "flow", "logicalcallcontext", "callcontext"] 8 | readme = "README.md" 9 | repository = "https://github.com/mitsuhiko/rust-execution-context" 10 | 11 | [dependencies] 12 | im = "10.2.0" 13 | lazy_static = "1.0.1" 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Execution Context for Rust 2 | 3 | This implements a .NET inspired execution context. The idea is that something like 4 | this could become a core language concept if it can be shown to be reasonably performant. 5 | 6 | ## What are Execution Contexts? 7 | 8 | An execution context is a container for a logical call flow. The idea is that any code 9 | that follows the same flow of execution can access flow-local data. An example where 10 | this is useful is security relevant data that code might want to carry from operation 11 | to operation without accidentally dropping it. 12 | 13 | This gives you the most trivial example: 14 | 15 | ```rust 16 | flow_local!(static TEST: u32 = 42); 17 | 18 | assert_eq!(*TEST.get(), 42); 19 | let ec = ExecutionContext::capture(); 20 | TEST.set(23); 21 | 22 | assert_eq!(*TEST.get(), 23); 23 | ec.run(|| { 24 | assert_eq!(*TEST.get(), 42); 25 | }); 26 | ``` 27 | 28 | Execution contexts can be forwarded to other threads and an API is provided to 29 | temporarily or permanently suppress the flow propagation. 30 | 31 | ## Why Are Execution Contexts? 32 | 33 | It's pretty clear that implicit data propagation is a hotly contended topic. For a 34 | very long time the author of this crate was convinced that implicit data passing 35 | through either thread locals or to have global variables is a severe case of code 36 | smell. However my experience has shown that there are many situations where it's 37 | hard to avoid having a system like this. 38 | 39 | * Systems like distributed tracing, during-production debug tools like Sentry and 40 | many more require the ability to figure out at runtime what belongs together. 41 | * many APIs use thread locality which are becoming a problem when async/await 42 | are used in the language. 43 | * security and auditing code can be written in a safer way if security relevant 44 | context information is not accidentally dropped. 45 | 46 | While obviously explicitly passing information is generally preferred in a lot of 47 | situations, implicit data passing along the logical thread of execution is a very 48 | potent tool. 49 | -------------------------------------------------------------------------------- /examples/basic.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate execution_context; 3 | 4 | use execution_context::ExecutionContext; 5 | use std::env; 6 | use std::thread; 7 | 8 | flow_local!(static LOCALE: String = env::var("LANG").unwrap_or_else(|_| "en_US".into())); 9 | 10 | fn main() { 11 | println!("the current locale is {}", LOCALE.get()); 12 | LOCALE.set("de_DE".into()); 13 | println!("changing locale to {}", LOCALE.get()); 14 | 15 | let ec = ExecutionContext::capture(); 16 | thread::spawn(move || { 17 | ec.run(|| { 18 | println!("the locale in the child thread is {}", LOCALE.get()); 19 | LOCALE.set("fr_FR".into()); 20 | println!("the new locale in the child thread is {}", LOCALE.get()); 21 | }); 22 | }).join() 23 | .unwrap(); 24 | 25 | println!("the locale of the parent thread is again {}", LOCALE.get()); 26 | } 27 | -------------------------------------------------------------------------------- /src/ctx.rs: -------------------------------------------------------------------------------- 1 | use std::any::TypeId; 2 | use std::cell::UnsafeCell; 3 | use std::fmt; 4 | use std::panic; 5 | use std::rc::Rc; 6 | use std::sync::Arc; 7 | 8 | use data::{LocalMap, Opaque}; 9 | 10 | lazy_static! { 11 | static ref DEFAULT_ACTIVE_CONTEXT: Arc = Arc::new(ExecutionContextImpl { 12 | flow_propagation: FlowPropagation::Active, 13 | locals: Default::default(), 14 | }); 15 | static ref DEFAULT_DISABLED_CONTEXT: Arc = Arc::new(ExecutionContextImpl { 16 | flow_propagation: FlowPropagation::Disabled, 17 | locals: Default::default(), 18 | }); 19 | } 20 | 21 | thread_local! { 22 | // we are using an unsafe cell here because current context is held 23 | // in thread local storage only and as such there can never be 24 | // references to it accessed from multiple places. 25 | static CURRENT_CONTEXT: UnsafeCell> = 26 | UnsafeCell::new(DEFAULT_ACTIVE_CONTEXT.clone()); 27 | } 28 | 29 | #[derive(PartialEq, Debug, Copy, Clone)] 30 | enum FlowPropagation { 31 | Active, 32 | Suppressed, 33 | Disabled, 34 | } 35 | 36 | #[derive(Clone)] 37 | pub(crate) struct ExecutionContextImpl { 38 | flow_propagation: FlowPropagation, 39 | locals: LocalMap, 40 | } 41 | 42 | impl ExecutionContextImpl { 43 | /// Wraps the execution context implementation in an Arc. 44 | /// 45 | /// Ths optimizes the two well known default cases. 46 | fn into_arc(self) -> Arc { 47 | match (self.flow_propagation, self.locals.is_empty()) { 48 | (FlowPropagation::Active, true) => DEFAULT_ACTIVE_CONTEXT.clone(), 49 | (FlowPropagation::Disabled, true) => DEFAULT_DISABLED_CONTEXT.clone(), 50 | _ => Arc::new(self), 51 | } 52 | } 53 | 54 | /// Checks if the flow is currently active. 55 | fn has_active_flow(&self) -> bool { 56 | match self.flow_propagation { 57 | FlowPropagation::Active => true, 58 | FlowPropagation::Suppressed => false, 59 | FlowPropagation::Disabled => false, 60 | } 61 | } 62 | } 63 | 64 | /// An execution context is a container for the current logical flow of execution. 65 | /// 66 | /// This container holds all state that needs to be carried forward with the logical thread 67 | /// of execution. 68 | /// 69 | /// The ExecutionContext class provides the functionality to capture and transfer the 70 | /// encapsulated context across asynchronous points such as threads or tasks. 71 | /// 72 | /// An execution context can be captured, send and cloned. This permits a context to be 73 | /// carried to other threads. The default execution context can be acquired at any 74 | /// by calling `ExecutionContext::default`. 75 | #[derive(Clone)] 76 | pub struct ExecutionContext { 77 | inner: Arc, 78 | } 79 | 80 | impl fmt::Debug for ExecutionContext { 81 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 82 | f.pad("ExecutionContext { .. }") 83 | } 84 | } 85 | 86 | impl Default for ExecutionContext { 87 | fn default() -> ExecutionContext { 88 | ExecutionContext { 89 | inner: DEFAULT_ACTIVE_CONTEXT.clone(), 90 | } 91 | } 92 | } 93 | 94 | /// A guard for suspended flows. 95 | /// 96 | /// This object is used as a guard to resume the flow that was suppressed by a 97 | /// call to `ExecutionContext::suppress_flow` or `ExecutionContext::disable_flow`. 98 | /// When it is dropped the flow is resumed. 99 | /// 100 | /// The guard is internally reference counted. 101 | // the Rc is to make it non send 102 | #[derive(Clone)] 103 | #[must_use = "if this value is dropped the flow is restored. Assign to a temporary: let _guard = ..."] 104 | pub struct FlowGuard(Rc); 105 | 106 | impl ExecutionContext { 107 | /// Captures the current execution context and returns it. 108 | /// 109 | /// If the current execution context is suppressed then this will instead 110 | /// capture an empty default scope (Same as if `ExecutionContext::default` 111 | /// was called). Capturing will always succeed. 112 | /// 113 | /// Capturing the execution context means that the flow of data will 114 | /// branch off here. If a flow local is modified after the flow is 115 | /// captured it will not be reflected in the captured context. 116 | /// 117 | /// ## Example 118 | /// 119 | /// ``` 120 | /// # use execution_context::ExecutionContext; 121 | /// let ec = ExecutionContext::capture(); 122 | /// ec.run(|| { 123 | /// // this code runs in the flow of the given execution context. 124 | /// }); 125 | /// ``` 126 | pub fn capture() -> ExecutionContext { 127 | ExecutionContext { 128 | inner: CURRENT_CONTEXT.with(|ctx| unsafe { 129 | let current = ctx.get(); 130 | match (*current).flow_propagation { 131 | FlowPropagation::Active => (*current).clone(), 132 | FlowPropagation::Suppressed => DEFAULT_ACTIVE_CONTEXT.clone(), 133 | FlowPropagation::Disabled => DEFAULT_DISABLED_CONTEXT.clone(), 134 | } 135 | }), 136 | } 137 | } 138 | 139 | /// Suppresses the flow. 140 | /// 141 | /// This returns a clonable non-send guard that when dropped restores the 142 | /// flow. This can be used to spawn an operation that should not be considered 143 | /// to be part of the same logical flow. Once a new execution context has been 144 | /// created, that context will start its own flow again. 145 | /// 146 | /// To permanently disable flow propagation use `disable_flow`. 147 | /// 148 | /// ## Example 149 | /// 150 | /// ``` 151 | /// # use execution_context::ExecutionContext; 152 | /// { 153 | /// let _guard = ExecutionContext::suppress_flow(); 154 | /// let ec = ExecutionContext::capture(); 155 | /// ec.run(|| { 156 | /// // a new flow is started here because the captured flow was 157 | /// // suppressed. 158 | /// }); 159 | /// } 160 | /// // the flow is resumed here 161 | /// ``` 162 | pub fn suppress_flow() -> FlowGuard { 163 | FlowGuard(Rc::new(ExecutionContext::set_flow_propagation( 164 | FlowPropagation::Suppressed, 165 | ))) 166 | } 167 | 168 | /// Permanently disables the flow. 169 | /// 170 | /// This works similar to `suppress_flow` but instead of just starting a new 171 | /// flow this permanently disables the flow. The flow can be manually restored 172 | /// by a call to `restore_flow`. 173 | pub fn disable_flow() -> FlowGuard { 174 | FlowGuard(Rc::new(ExecutionContext::set_flow_propagation( 175 | FlowPropagation::Disabled, 176 | ))) 177 | } 178 | 179 | /// Restores the flow. 180 | /// 181 | /// In normal situations the flow is restored when the flow guard is 182 | /// dropped. However when for instance the flow is permanently disabled 183 | /// with `disable_flow` new branches will never have their flow restored. 184 | /// In those situations it might be useful to call into this function to 185 | /// restore the flow. 186 | pub fn restore_flow() { 187 | ExecutionContext::set_flow_propagation(FlowPropagation::Active); 188 | } 189 | 190 | /// Checks if the flow is currently suppressed. 191 | /// 192 | /// A caller cannot determine if the flow is just temporarily suppressed 193 | /// or permanently disabled. 194 | pub fn is_flow_suppressed() -> bool { 195 | CURRENT_CONTEXT.with(|ctx| unsafe { !(*ctx.get()).has_active_flow() }) 196 | } 197 | 198 | /// Runs a function in the context of the given execution context. 199 | /// 200 | /// The captured execution flow will be carried forward. If the flow 201 | /// was suppressed a new flow is started. In case the flow was disabled 202 | /// then it's also disabled here. 203 | /// 204 | /// ## Example 205 | /// 206 | /// ``` 207 | /// # use std::thread; 208 | /// # use execution_context::ExecutionContext; 209 | /// let ec = ExecutionContext::capture(); 210 | /// thread::spawn(move || { 211 | /// ec.run(|| { 212 | /// // the captured execution context is carried into 213 | /// // another thread. 214 | /// }); 215 | /// }); 216 | /// ``` 217 | pub fn run R, R>(&self, f: F) -> R { 218 | // figure out where we want to switch to. In case the current 219 | // flow is the target flow, we can get away without having to do 220 | // any panic handling and pointer swapping. Otherwise this block 221 | // switches us to the new context and returns the old one. 222 | let did_switch = CURRENT_CONTEXT.with(|ctx| unsafe { 223 | let ptr = ctx.get(); 224 | if &**ptr as *const _ == &*self.inner as *const _ { 225 | None 226 | } else { 227 | let old = (*ptr).clone(); 228 | *ptr = self.inner.clone(); 229 | Some(old) 230 | } 231 | }); 232 | 233 | match did_switch { 234 | None => { 235 | // None means no switch happened. We can invoke the function 236 | // just like that, no changes necessary. 237 | f() 238 | } 239 | Some(old_ctx) => { 240 | // this is for the case where we just switched the execution 241 | // context. This means we need to catch the panic, restore the 242 | // old context and resume the panic if needed. 243 | let rv = panic::catch_unwind(panic::AssertUnwindSafe(|| f())); 244 | CURRENT_CONTEXT.with(|ctx| unsafe { *ctx.get() = old_ctx }); 245 | match rv { 246 | Err(err) => panic::resume_unwind(err), 247 | Ok(rv) => rv, 248 | } 249 | } 250 | } 251 | } 252 | 253 | /// Internal helper to update the flow propagation. 254 | fn set_flow_propagation(new: FlowPropagation) -> FlowPropagation { 255 | CURRENT_CONTEXT.with(|ctx| unsafe { 256 | let ptr = ctx.get(); 257 | let old = (*ptr).flow_propagation; 258 | if old != new { 259 | *ptr = ExecutionContextImpl { 260 | flow_propagation: new, 261 | locals: (*ptr).locals.clone(), 262 | }.into_arc(); 263 | } 264 | old 265 | }) 266 | } 267 | 268 | /// Inserts a value into the locals. 269 | pub(crate) fn set_local_value(key: TypeId, value: Arc>) { 270 | CURRENT_CONTEXT.with(|ctx| unsafe { 271 | let ptr = ctx.get(); 272 | let locals = (*ptr).locals.insert(key, value); 273 | *ptr = ExecutionContextImpl { 274 | flow_propagation: (*ptr).flow_propagation, 275 | locals: locals, 276 | }.into_arc(); 277 | }); 278 | } 279 | 280 | /// Returns a value from the locals. 281 | pub(crate) fn get_local_value(key: TypeId) -> Option>> { 282 | CURRENT_CONTEXT.with(|ctx| unsafe { (*ctx.get()).locals.get(&key) }) 283 | } 284 | } 285 | 286 | impl Drop for FlowGuard { 287 | fn drop(&mut self) { 288 | if let Some(old) = Rc::get_mut(&mut self.0) { 289 | ExecutionContext::set_flow_propagation(*old); 290 | } 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/data.rs: -------------------------------------------------------------------------------- 1 | use im::hashmap::HashMap; 2 | use std::any::TypeId; 3 | use std::fmt; 4 | use std::hash::{BuildHasherDefault, Hasher}; 5 | use std::mem; 6 | use std::ops::Deref; 7 | use std::sync::Arc; 8 | 9 | use ctx::ExecutionContext; 10 | 11 | /// A macro to create a `static` of type `FlowLocal` 12 | /// 13 | /// This macro is intentionally similar to the `thread_local!`, and creates a 14 | /// `static` which has a `get_mut` method to access the data on a flow. 15 | /// 16 | /// The data associated with each flow local is per-flow, so different flows 17 | /// will contain different values. 18 | #[macro_export] 19 | macro_rules! flow_local { 20 | (static $NAME:ident : $t:ty = $e:expr) => { 21 | static $NAME: $crate::FlowLocal<$t> = { 22 | fn __init() -> $t { 23 | $e 24 | } 25 | fn __key() -> ::std::any::TypeId { 26 | struct __A; 27 | ::std::any::TypeId::of::<__A>() 28 | } 29 | $crate::FlowLocal { __init, __key } 30 | }; 31 | }; 32 | } 33 | 34 | /// Am immutable wrapper around a value from a flow local. 35 | /// 36 | /// This is returned when a flow local is accessed and is internally 37 | /// reference counted. Cloning this value is reasonably efficient. 38 | #[derive(Clone)] 39 | pub struct FlowBox(Arc>); 40 | 41 | pub(crate) type LocalMap = HashMap, BuildHasherDefault>; 42 | 43 | pub(crate) trait Opaque: Send + Sync {} 44 | impl Opaque for T {} 45 | 46 | /// A key for flow-local data stored in the execution context. 47 | /// 48 | /// This type is generated by the `flow_local!` macro and performs very 49 | /// similarly to the `thread_local!` macro and `std::thread::FlowLocal` types. 50 | /// Data associated with a `FlowLocal` is stored inside the current 51 | /// execution context and the data is destroyed when the execution context 52 | /// is destroyed. 53 | /// 54 | /// Flow-local data can migrate between threads and hence requires a `Send` 55 | /// bound. Additionally, flow-local data also requires the `'static` bound to 56 | /// ensure it lives long enough. When a key is accessed for the first time the 57 | /// flow's data is initialized with the provided initialization expression to 58 | /// the macro. 59 | pub struct FlowLocal { 60 | // "private" fields which have to be public to get around macro hygiene, not 61 | // included in the stability story for this type. Can change at any time. 62 | #[doc(hidden)] 63 | pub __key: fn() -> TypeId, 64 | #[doc(hidden)] 65 | pub __init: fn() -> T, 66 | } 67 | 68 | impl fmt::Debug for FlowLocal { 69 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 70 | f.pad("FlowLocal { .. }") 71 | } 72 | } 73 | 74 | pub(crate) struct IdHasher { 75 | id: u64, 76 | } 77 | 78 | impl Default for IdHasher { 79 | fn default() -> IdHasher { 80 | IdHasher { id: 0 } 81 | } 82 | } 83 | 84 | impl Hasher for IdHasher { 85 | fn write(&mut self, bytes: &[u8]) { 86 | for &x in bytes { 87 | self.id.wrapping_add(x as u64); 88 | } 89 | } 90 | 91 | fn write_u64(&mut self, u: u64) { 92 | self.id = u; 93 | } 94 | 95 | fn finish(&self) -> u64 { 96 | self.id 97 | } 98 | } 99 | 100 | impl FlowLocal { 101 | /// Access this flow-local. 102 | /// 103 | /// This function will access this flow-local key to retrieve the data 104 | /// associated with the current flow and this key. If this is the first time 105 | /// this key has been accessed on this flow, then the key will be 106 | /// initialized with the initialization expression provided at the time the 107 | /// `flow_local!` macro was called. 108 | pub fn get(&self) -> FlowBox { 109 | let key = (self.__key)(); 110 | 111 | if let Some(rv) = ExecutionContext::get_local_value(key) { 112 | return unsafe { mem::transmute(rv) }; 113 | }; 114 | 115 | let arc = Arc::new(Box::new((self.__init)())); 116 | ExecutionContext::set_local_value(key, unsafe { 117 | mem::forget(arc.clone()); 118 | mem::transmute(arc.clone()) 119 | }); 120 | FlowBox(arc) 121 | } 122 | 123 | /// Similar to `get` but invokes the closure with a reference to the value. 124 | /// 125 | /// This API exists for consistency with thread locals and also can be 126 | /// implemented to give improved performance over the required arc clone 127 | /// that `get` requires. However currently no such performance improvement 128 | /// is implemented. 129 | pub fn with R, R>(&self, f: F) -> R { 130 | f(&*self.get()) 131 | } 132 | 133 | /// Sets a new value for the flow-local. 134 | pub fn set(&self, value: T) { 135 | let key = (self.__key)(); 136 | let arc = Arc::new(Box::new(value)); 137 | ExecutionContext::set_local_value(key, unsafe { 138 | mem::forget(arc.clone()); 139 | mem::transmute(arc.clone()) 140 | }); 141 | } 142 | } 143 | 144 | impl fmt::Display for FlowBox { 145 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 146 | fmt::Display::fmt(&**self, f) 147 | } 148 | } 149 | 150 | impl fmt::Debug for FlowBox { 151 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 152 | fmt::Debug::fmt(&**self, f) 153 | } 154 | } 155 | 156 | impl fmt::Pointer for FlowBox { 157 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 158 | // It's not possible to extract the inner Uniq directly from the FlowBox, 159 | // instead we cast it to a *const which aliases the Unique 160 | let ptr: *const T = &**self; 161 | fmt::Pointer::fmt(&ptr, f) 162 | } 163 | } 164 | 165 | impl Deref for FlowBox { 166 | type Target = T; 167 | 168 | fn deref(&self) -> &T { 169 | &*self.0 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate implements an execution context for Rust. An execution context 2 | //! carries data through a logical flow of execution. 3 | //! 4 | //! This is heavily inspired by the .NET `ExecutionContext` system as an 5 | //! experiment if such an API would make sense in Rust. 6 | //! 7 | //! The main differences to the system in .NET: 8 | //! 9 | //! * This purely implements an execution context and not a synchronization 10 | //! context. As such the implementation is simplified. 11 | //! * This provides the ability to permanently disable the flow propagation. 12 | //! * This crate refers to "ambient data" and "async locals" as "flow-local data". 13 | //! * Capturing always returns an execution context even if the flow is 14 | //! suppressed. Consequently the run method is no longer static but 15 | //! stored on the instance. 16 | //! * This uses atomic reference counting underneath the hood instead of 17 | //! using a garbage collector. This also means that performance 18 | //! characteristics will be different. 19 | //! * `FlowLocal` data have a initialization expression that is invoked if 20 | //! no local data is stored in the current flow. 21 | //! 22 | //! # Example Usage 23 | //! 24 | //! ``` 25 | //! #[macro_use] 26 | //! extern crate execution_context; 27 | //! 28 | //! use execution_context::ExecutionContext; 29 | //! use std::env; 30 | //! use std::thread; 31 | //! 32 | //! flow_local!(static LOCALE: String = env::var("LANG").unwrap_or_else(|_| "en_US".into())); 33 | //! 34 | //! fn main() { 35 | //! println!("the current locale is {}", LOCALE.get()); 36 | //! LOCALE.set("de_DE".into()); 37 | //! println!("changing locale to {}", LOCALE.get()); 38 | //! 39 | //! let ec = ExecutionContext::capture(); 40 | //! thread::spawn(move || { 41 | //! ec.run(|| { 42 | //! println!("the locale in the child thread is {}", LOCALE.get()); 43 | //! LOCALE.set("fr_FR".into()); 44 | //! println!("the new locale in the child thread is {}", LOCALE.get()); 45 | //! }); 46 | //! }).join().unwrap(); 47 | //! 48 | //! println!("the locale of the parent thread is again {}", LOCALE.get()); 49 | //! } 50 | //! ``` 51 | //! 52 | //! This example will give the following output assuming no default language was 53 | //! set as environment variable (or the environment variable is `en_US`): 54 | //! 55 | //! ```plain 56 | //! the current locale is en_US 57 | //! changing locale to de_DE 58 | //! the locale in the child thread is de_DE 59 | //! the new locale in the child thread is fr_FR 60 | //! the locale of the parent thread is again de_DE 61 | //! ``` 62 | extern crate im; 63 | #[macro_use] 64 | extern crate lazy_static; 65 | 66 | mod ctx; 67 | mod data; 68 | 69 | pub use ctx::*; 70 | pub use data::*; 71 | -------------------------------------------------------------------------------- /tests/test_basic.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate execution_context; 3 | 4 | use execution_context::ExecutionContext; 5 | use std::thread; 6 | 7 | #[test] 8 | fn test_basic_propagation() { 9 | flow_local!(static TEST: u32 = 42); 10 | assert_eq!(*TEST.get(), 42); 11 | 12 | let ec = ExecutionContext::capture(); 13 | TEST.set(23); 14 | assert_eq!(*TEST.get(), 23); 15 | ec.run(|| { 16 | assert_eq!(*TEST.get(), 42); 17 | }); 18 | } 19 | 20 | #[test] 21 | fn test_basic_propagation_to_threads() { 22 | flow_local!(static TEST: u32 = 42); 23 | assert_eq!(*TEST.get(), 42); 24 | 25 | let ec = ExecutionContext::capture(); 26 | TEST.set(23); 27 | assert_eq!(*TEST.get(), 23); 28 | thread::spawn(move || { 29 | ec.run(|| { 30 | assert_eq!(*TEST.get(), 42); 31 | TEST.set(22); 32 | }); 33 | }).join() 34 | .unwrap(); 35 | 36 | assert_eq!(*TEST.get(), 23); 37 | } 38 | 39 | #[test] 40 | fn test_flow_suppression() { 41 | flow_local!(static TEST: u32 = 42); 42 | 43 | { 44 | let _guard = ExecutionContext::suppress_flow(); 45 | TEST.set(11111); 46 | 47 | assert!(ExecutionContext::is_flow_suppressed()); 48 | 49 | let ec = ExecutionContext::capture(); 50 | ec.run(|| { 51 | assert!(!ExecutionContext::is_flow_suppressed()); 52 | assert_eq!(*TEST.get(), 42); 53 | }); 54 | } 55 | 56 | assert!(!ExecutionContext::is_flow_suppressed()); 57 | } 58 | 59 | #[test] 60 | fn test_flow_disabling() { 61 | flow_local!(static TEST: u32 = 42); 62 | 63 | { 64 | let _guard = ExecutionContext::disable_flow(); 65 | TEST.set(11111); 66 | 67 | assert!(ExecutionContext::is_flow_suppressed()); 68 | 69 | let ec = ExecutionContext::capture(); 70 | ec.run(|| { 71 | assert!(ExecutionContext::is_flow_suppressed()); 72 | assert_eq!(*TEST.get(), 42); 73 | }); 74 | } 75 | 76 | assert!(!ExecutionContext::is_flow_suppressed()); 77 | } 78 | 79 | #[test] 80 | fn test_running_default() { 81 | flow_local!(static TEST: u32 = 23); 82 | TEST.set(24); 83 | assert_eq!(*TEST.get(), 24); 84 | ExecutionContext::default().run(|| { 85 | assert_eq!(*TEST.get(), 23); 86 | }); 87 | } 88 | --------------------------------------------------------------------------------