├── .projectile ├── .gitignore ├── Cargo.toml ├── LICENCE ├── README.md └── src └── lib.rs /.projectile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | /bld_scripts 4 | /run_scripts -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "threadstack" 3 | version = "0.4.1" 4 | authors = ["Joseph Garvin "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "A more ergonomic and more flexible form of thread local storage." 8 | homepage = "https://github.com/jgarvin/threadstack" 9 | repository = "https://github.com/jgarvin/threadstack" 10 | keywords = ["threads", "globals", "tls"] 11 | categories = ["concurrency", "rust-patterns"] 12 | 13 | [dependencies] 14 | rel-ptr = "0.2.3" 15 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Joseph Garvin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A more ergonomic and more flexible form of thread local storage. 2 | 3 | [![Docs Status](https://docs.rs/threadstack/badge.svg)](https://docs.rs/threadstack) 4 | [![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/jgarvin/threadstack/blob/main/LICENCE) 5 | [![crates.io badge](https://img.shields.io/crates/v/threadstack.svg)](https://crates.io/crates/threadstack) 6 | 7 | Inspired by the [parameters 8 | feature](https://docs.racket-lang.org/reference/parameters.html) 9 | from Racket. 10 | 11 | The general idea is the following. Many applications have 12 | "context" variables that are needed by almost every module in the 13 | application. It is extremely tedious to pass down these values 14 | through every every function in the program. The obvious 15 | temptation is to use a global variable instead, but global 16 | variables have a bunch of widely known downsides: 17 | 18 | * They lack thread safety. 19 | 20 | * They create a hidden side channel between modules in your 21 | application that can create "spooky action at a distance." 22 | 23 | * Because there is only one instance of a global variable modules 24 | in the program can fight over what they want the value of it to 25 | be. 26 | 27 | Threadstacks are a middle ground. Essentially instead of having a 28 | global variable, you keep a thread local stack of values. You can only 29 | refer to the value at the top of the stack, and the borrow checker 30 | will guarantee that your reference goes away before the value is 31 | popped. You can push new values on the stack, but they automatically 32 | expire when the lexical scope containing your push ends. Values on the 33 | threadstack are immutable unless you go out of your way to use a type 34 | with interior mutability like `Cell` or `RefCell`, so code that wants 35 | to customize the value typically will do so by pushing on onto the 36 | stack rather than clobbering the existing value as would normally 37 | occur with a global variable. 38 | 39 | This gives you the effect of a global variable that you can 40 | temporarily override. Functions that before would have referenced 41 | a global variable instead reference the top of the stack, and by 42 | pushing a value on the stack before calling said functions you can 43 | affect their behavior. However you are unable to affect the 44 | behavior when your caller calls those functions because by the 45 | time control returns to your caller the lexical scope containing 46 | your push will have ended and the value you pushed will have 47 | automatically been popped from the stack. This limits the degree 48 | to which different modules can step on each other. 49 | 50 | Because the provided `let_ref_thread_stack_value!` creates 51 | references that have a special lifetime tied to the current stack 52 | frame, it is not necessary to wrap all code using thread stack 53 | values inside a call to something like `my_local_key.with(|data| 54 | {...})` like you would have to with the standard `thread_local!` 55 | TLS implementation. 56 | 57 | 58 | 59 | Example: 60 | 61 | ```rust 62 | use threadstack::*; 63 | 64 | declare_thread_stacks!( 65 | FOO: String = String::from("hello world"); 66 | ); 67 | 68 | let_ref_thread_stack_value!(my_reference, FOO); 69 | assert!(my_reference == "hello world"); 70 | 71 | { 72 | push_thread_stack_value!("hello universe".into(), FOO); 73 | let_ref_thread_stack_value!(my_other_reference, FOO); 74 | assert!(my_other_reference == "hello universe"); 75 | } 76 | 77 | assert!(my_reference == "hello world"); 78 | push_thread_stack_value!("hello galaxy".into(), FOO); 79 | assert!(my_reference == "hello world"); // still is reference to old value! 80 | let_ref_thread_stack_value!(my_reference, FOO); // shadows the old reference 81 | assert!(my_reference == "hello galaxy"); 82 | ```` 83 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A more ergonomic and more flexible form of thread local storage. 2 | //! 3 | //! Inspired by the [parameters 4 | //! feature](https://docs.racket-lang.org/reference/parameters.html) 5 | //! from Racket. 6 | //! 7 | //! The general idea is the following. Many applications have 8 | //! "context" variables that are needed by almost every module in the 9 | //! application. It is extremely tedious to pass down these values 10 | //! through every every function in the program. The obvious 11 | //! temptation is to use a global variable instead, but global 12 | //! variables have a bunch of widely known downsides: 13 | //! 14 | //! * They lack thread safety. 15 | //! 16 | //! * They create a hidden side channel between modules in your 17 | //! application that can create "spooky action at a distance." 18 | //! 19 | //! * Because there is only one instance of a global variable modules 20 | //! in the program can fight over what they want the value of it to 21 | //! be. 22 | //! 23 | //! Threadstacks are a middle ground. Essentially instead of having a 24 | //! global variable, you keep a thread local stack of values. You can 25 | //! only refer to the value at the top of the stack, and the borrow 26 | //! checker will guarantee that your reference goes away before the 27 | //! value is popped. You can push new values on the stack, but they 28 | //! automatically expire when the lexical scope containing your push 29 | //! ends. Values on the threadstack are immutable unless you go out of 30 | //! your way to use a type with interior mutability like `Cell` or 31 | //! `RefCell`, so code that wants to customize the value typically 32 | //! will do so by pushing on onto the stack rather than clobbering the 33 | //! existing value as would normally occur with a global variable. 34 | //! 35 | //! This gives you the effect of a global variable that you can 36 | //! temporarily override. Functions that before would have referenced 37 | //! a global variable instead reference the top of the stack, and by 38 | //! pushing a value on the stack before calling said functions you can 39 | //! affect their behavior. However you are unable to affect the 40 | //! behavior when your caller calls those functions because by the 41 | //! time control returns to your caller the lexical scope containing 42 | //! your push will have ended and the value you pushed will have 43 | //! automatically been popped from the stack. This limits the degree 44 | //! to which different modules can step on each other. 45 | //! 46 | //! Because the provided `let_ref_thread_stack_value!` creates 47 | //! references that have a special lifetime tied to the current stack 48 | //! frame, it is not necessary to wrap all code using thread stack 49 | //! values inside a call to something like `my_local_key.with(|data| 50 | //! {...})` like you would have to with the standard `thread_local!` 51 | //! TLS implementation. 52 | use rel_ptr::RelPtr; 53 | use std::cell::UnsafeCell; 54 | use std::thread::LocalKey; 55 | 56 | // This is done as a separate macro because it is not possible to hide 57 | // a specific macro rules pattern from the documentation. 58 | // 59 | // https://stackoverflow.com/questions/35537758/is-there-a-way-to-hide-a-macro-pattern-from-docs 60 | #[doc(hidden)] 61 | #[macro_export] 62 | macro_rules! declare_thread_stacks_inner { 63 | ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $init:expr) => { 64 | thread_local! { 65 | $(#[$attr])* $vis static $name: $crate::ThreadStackWithInitialValue<$t> = $crate::ThreadStackWithInitialValue::new($init) 66 | } 67 | }; 68 | 69 | ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty) => { 70 | thread_local! { 71 | $(#[$attr])* $vis static $name: $crate::ThreadStack<$t> = $crate::ThreadStack::new(); 72 | } 73 | }; 74 | } 75 | 76 | /// Macro used to declare one or more thread stacks. The syntax pretty 77 | /// closely mirrors thread_local! from the standard library, except 78 | /// that the `static` key word is not used. 79 | /// 80 | /// # Example 81 | /// 82 | /// ``` 83 | /// use threadstack::declare_thread_stacks; 84 | /// 85 | /// declare_thread_stacks!( 86 | /// FOO: u32 = 0xDEADBEEFu32; 87 | /// pub BAR: u32 = 0xDEADBEEFu32; 88 | /// BUZZ: String; 89 | /// ); 90 | /// 91 | /// ``` 92 | /// 93 | /// Note that the value on the right side of the equal sign is only 94 | /// the initial value (which may be overridden by calls to 95 | /// `push_thread_stack_value`). Providing an initial value guarantees 96 | /// that accessing the top of the stack through 97 | /// `let_ref_thread_stack_value` or `clone_thread_stack_value` will 98 | /// never panic. Otherwise they may panic if no value has ever been 99 | /// pushed. 100 | #[macro_export] 101 | macro_rules! declare_thread_stacks { 102 | // empty (base case for the recursion) 103 | () => {}; 104 | 105 | // process multiple declarations 106 | ($(#[$attr:meta])* $vis:vis $name:ident: $t:ty = $init:expr; $($rest:tt)*) => ( 107 | $crate::declare_thread_stacks_inner!($(#[$attr])* $vis $name, $t, $init); 108 | $crate::declare_thread_stacks!($($rest)*); 109 | ); 110 | 111 | // handle a single declaration 112 | ($(#[$attr:meta])* $vis:vis $name:ident: $t:ty = $init:expr;) => ( 113 | $crate::declare_thread_stacks_inner!($(#[$attr])* $vis $name, $t, $init); 114 | ); 115 | 116 | // process multiple declarations 117 | ($(#[$attr:meta])* $vis:vis $name:ident: $t:ty; $($rest:tt)*) => ( 118 | $crate::declare_thread_stacks_inner!($(#[$attr])* $vis $name, $t); 119 | $crate::declare_thread_stacks!($($rest)*); 120 | ); 121 | 122 | // handle a single declaration 123 | ($(#[$attr:meta])* $vis:vis $name:ident: $t:ty;) => ( 124 | $crate::declare_thread_stacks_inner!($(#[$attr])* $vis $name, $t); 125 | ); 126 | } 127 | 128 | // Making this a separate struct lets us have a single 129 | // ThreadStackGuard struct to handle the cases with and without 130 | // initial values. 131 | #[doc(hidden)] 132 | pub struct ThreadStackInner { 133 | top: UnsafeCell>, 134 | } 135 | 136 | /// The container for the underlying array used to implement the stack 137 | /// of values when the stack is not given an initial value (for that 138 | /// see `ThreadStackWithInitialValue`). Generally you will only ever 139 | /// see this type wrapped inside of `std::thread:LocalKey`, and there 140 | /// is never any reason really to use it directly. Instead use 141 | /// `declare_thread_stacks!`, `let_ref_thread_stack_value!`, 142 | /// `push_thread_stack_value!` and `clone_thread_stack_value`. 143 | pub struct ThreadStack { 144 | inner: ThreadStackInner, 145 | } 146 | 147 | /// The container for the underlying array used to implement the stack 148 | /// of values, when in the declaration and initial value for the stack 149 | /// was specified. This is only a separate type from `ThreadStack` so 150 | /// that a branch and possible panic can be omitted for slightly 151 | /// better performance and smaller generated code. Generally you will 152 | /// only ever see this type wrapped inside of `std::thread:LocalKey`, 153 | /// and there is never any reason really to use it directly. Instead 154 | /// use `declare_thread_stacks!`, `let_ref_thread_stack_value!`, 155 | /// `push_thread_stack_value!` and `clone_thread_stack_value`. 156 | pub struct ThreadStackWithInitialValue { 157 | inner: ThreadStackInner, 158 | initial: T, 159 | } 160 | 161 | #[doc(hidden)] 162 | pub trait IsThreadStack { 163 | fn get_inner(&self) -> &ThreadStackInner; 164 | 165 | unsafe fn push_value_impl<'a>(&self, new_value: &'a T) -> ThreadStackGuard<'a, T> { 166 | let inner = self.get_inner(); 167 | let top: &mut RelPtr = &mut *inner.top.get(); 168 | let old_top: RelPtr = *top; 169 | top.set_unchecked((new_value as *const T) as *mut T); 170 | ThreadStackGuard { 171 | previous_top: old_top, 172 | stack: self.get_inner() as *const ThreadStackInner, 173 | stack_lifetime_hack: std::marker::PhantomData, 174 | } 175 | } 176 | 177 | unsafe fn get_value_impl<'a, 'b>(self: &'b Self, _hack: &'a ()) -> &'a T; 178 | } 179 | 180 | impl IsThreadStack for ThreadStack { 181 | fn get_inner(&self) -> &ThreadStackInner { 182 | &self.inner 183 | } 184 | 185 | unsafe fn get_value_impl<'a, 'b>(self: &'b ThreadStack, _hack: &'a ()) -> &'a T { 186 | let p: &RelPtr = &*self.inner.top.get(); 187 | if p.is_null() { 188 | panic!("Tried to access threadstack with no initial value and no set value!"); 189 | } 190 | p.as_ref_unchecked() 191 | } 192 | } 193 | 194 | impl IsThreadStack for ThreadStackWithInitialValue { 195 | fn get_inner(&self) -> &ThreadStackInner { 196 | &self.inner 197 | } 198 | 199 | unsafe fn get_value_impl<'a, 'b>( 200 | self: &'b ThreadStackWithInitialValue, 201 | _hack: &'a (), 202 | ) -> &'a T { 203 | // Because we were defined with an initial value we know we 204 | // can always dereference this pointer. 205 | let p: &RelPtr = &*self.inner.top.get(); 206 | p.as_ref_unchecked() 207 | } 208 | } 209 | 210 | impl ThreadStack { 211 | // This function should be able to be const but can't be because 212 | // RelPtr::null() is not const. 213 | pub fn new() -> Self { 214 | ThreadStack { 215 | inner: ThreadStackInner { 216 | top: UnsafeCell::new(RelPtr::null()), 217 | }, 218 | } 219 | } 220 | } 221 | 222 | impl ThreadStackWithInitialValue { 223 | // This function should be able to be const 224 | // but can't be because of: 225 | // https://github.com/rust-lang/rust/issues/69908 226 | #[doc(hidden)] 227 | pub fn new(initial: T) -> Self { 228 | let mut s = ThreadStackWithInitialValue { 229 | inner: ThreadStackInner { 230 | top: UnsafeCell::new(RelPtr::null()), 231 | }, 232 | initial, 233 | }; 234 | let top: &mut RelPtr = unsafe { &mut *s.inner.top.get() }; 235 | unsafe { 236 | top.set_unchecked(&mut s.initial as *mut T); 237 | } 238 | return s; 239 | } 240 | } 241 | 242 | /// Create a local reference to the value at the top of the 243 | /// threadstack. Even though the top value may have been pushed at a 244 | /// much higher layer in the call stack, the reference has a 245 | /// conservative lifetime to guarantee safety -- the same lifetime as 246 | /// a local variable created on the stack where the macro is invoked. 247 | /// If you don't want to have to worry about lifetimes consider using 248 | /// `clone_thread_stack_value` instead. 249 | /// 250 | /// Note that this can panic, but only if you did not provide an 251 | /// initial value when you declared your thread stack. 252 | /// 253 | /// ``` 254 | /// use threadstack::*; 255 | /// 256 | /// declare_thread_stacks!( 257 | /// FOO: String = String::from("hello world"); 258 | /// ); 259 | /// 260 | /// let_ref_thread_stack_value!(my_reference, FOO); 261 | /// assert!(my_reference == "hello world"); 262 | /// 263 | /// { 264 | /// push_thread_stack_value!("hello universe".into(), FOO); 265 | /// let_ref_thread_stack_value!(my_other_reference, FOO); 266 | /// assert!(my_other_reference == "hello universe"); 267 | /// } 268 | /// 269 | /// assert!(my_reference == "hello world"); 270 | /// push_thread_stack_value!("hello galaxy".into(), FOO); 271 | /// assert!(my_reference == "hello world"); // still is reference to old value! 272 | /// let_ref_thread_stack_value!(my_reference, FOO); // shadows the old reference 273 | /// assert!(my_reference == "hello galaxy"); 274 | /// ```` 275 | #[macro_export] 276 | macro_rules! let_ref_thread_stack_value { 277 | ($new_variable:ident, $thread_stack:expr) => { 278 | let stack_lifetime_hack = (); 279 | let s = &$thread_stack; 280 | $crate::compile_time_assert_is_thread_stack(s); 281 | let $new_variable = s.with(|stack| unsafe { 282 | $crate::IsThreadStack::get_value_impl(stack, &stack_lifetime_hack) 283 | }); 284 | }; 285 | } 286 | 287 | #[doc(hidden)] 288 | pub fn compile_time_assert_is_thread_stack>(_t: &LocalKey) -> () { 289 | () 290 | } 291 | 292 | #[doc(hidden)] 293 | pub struct ThreadStackGuard<'a, T> { 294 | previous_top: RelPtr, // not valid to dereference from here, just a backup! 295 | stack: *const ThreadStackInner, 296 | stack_lifetime_hack: std::marker::PhantomData<&'a ()>, 297 | } 298 | 299 | impl<'a, T> Drop for ThreadStackGuard<'a, T> { 300 | fn drop(&mut self) { 301 | let stack = unsafe { &*self.stack }; 302 | unsafe { 303 | *stack.top.get() = self.previous_top; 304 | } 305 | } 306 | } 307 | 308 | /// Clone the value currently at the top of threadstack. This lets you 309 | /// avoid worrying about lifetimes but does require a clone to be 310 | /// made. This can panic only if nothing has been pushed onto the 311 | /// threadstack and it was created without an initial value. 312 | /// 313 | /// ``` 314 | /// use threadstack::*; 315 | /// 316 | /// declare_thread_stacks!( 317 | /// FOO: String = String::from("hello world"); 318 | /// ); 319 | /// 320 | /// assert!(clone_thread_stack_value(&FOO) == "hello world"); 321 | /// ```` 322 | pub fn clone_thread_stack_value>(stack: &'static LocalKey) -> T { 323 | let_ref_thread_stack_value!(the_value, stack); 324 | the_value.clone() 325 | } 326 | 327 | /// Push a new value on the top of the threadstack. This value becomes 328 | /// the value that will be returned by `clone_thread_stack_value` and 329 | /// that `let_ref_thread_stack_value!` will create a reference to. Can 330 | /// only be invoked inside a function, and the effect will last until 331 | /// the end of the current scope. Pushing a new value onto the 332 | /// threadstack will never panic. The assumption is that threadstacks 333 | /// are mostly used for infrequently set context data, or 334 | /// configuration settings that would otherwise be global variables. 335 | /// 336 | /// ``` 337 | /// use threadstack::*; 338 | /// 339 | /// declare_thread_stacks!( 340 | /// FOO: String = String::from("hello world"); 341 | /// ); 342 | /// 343 | /// assert!(clone_thread_stack_value(&FOO) == "hello world"); 344 | /// 345 | /// { 346 | /// push_thread_stack_value!("hello universe".into(), FOO); 347 | /// assert!(clone_thread_stack_value(&FOO) == "hello universe"); 348 | /// } 349 | /// 350 | /// assert!(clone_thread_stack_value(&FOO) == "hello world"); 351 | /// ```` 352 | #[macro_export] 353 | macro_rules! push_thread_stack_value { 354 | ($new_value:expr, $thread_stack:expr) => { 355 | let s = &$thread_stack; 356 | let v = $new_value; 357 | $crate::compile_time_assert_is_thread_stack(s); 358 | let _push_guard = s.with(|stack| unsafe { stack.push_value_impl(&v) }); 359 | }; 360 | } 361 | 362 | #[cfg(test)] 363 | mod tests { 364 | use super::*; 365 | 366 | declare_thread_stacks!( 367 | STACK: u32 = 0xDEADBEEFu32; 368 | ); 369 | 370 | // We don't want to have any assumptions about how many threads 371 | // are being used to run tests, so we spawn a separate thread for 372 | // every test. 373 | fn run_test () + Send + 'static>(f: F) { 374 | let handle = std::thread::spawn(f); 375 | 376 | if let Err(v) = handle.join() { 377 | if v.downcast_ref::<&str>().is_some() { 378 | // fresh panic 379 | panic!("{}", v.downcast_ref::<&str>().unwrap()); 380 | } else if v.downcast_ref::().is_some() { 381 | // already went through formatting panic 382 | panic!("{}", v.downcast_ref::().unwrap()); 383 | } 384 | } 385 | } 386 | 387 | #[test] 388 | fn it_works() { 389 | run_test(|| { 390 | 391 | let_ref_thread_stack_value!(stack_value, STACK); 392 | assert!(stack_value == &0xDEADBEEFu32); 393 | { 394 | push_thread_stack_value!(stack_value + 1, STACK); 395 | let_ref_thread_stack_value!(stack_value, STACK); 396 | assert!(stack_value == &0xDEADBEF0u32); 397 | } 398 | let_ref_thread_stack_value!(stack_value, STACK); 399 | assert!(stack_value == &0xDEADBEEFu32); 400 | assert!(clone_thread_stack_value(&STACK) == 0xDEADBEEFu32); 401 | }); 402 | } 403 | 404 | declare_thread_stacks!( 405 | STARTS_EMPTY: u32; 406 | ); 407 | 408 | #[test] 409 | #[should_panic(expected = "no initial value")] 410 | fn no_initial_value_test() { 411 | run_test(|| { 412 | let_ref_thread_stack_value!(wont_work, STARTS_EMPTY); 413 | assert!(wont_work == &100); 414 | }); 415 | } 416 | 417 | #[test] 418 | #[should_panic(expected = "no initial value")] 419 | fn revert_to_no_initial() { 420 | run_test(|| { 421 | { 422 | push_thread_stack_value!(50, STARTS_EMPTY); 423 | } 424 | let_ref_thread_stack_value!(wont_work, STARTS_EMPTY); 425 | assert!(wont_work == &100); 426 | }); 427 | } 428 | 429 | #[test] 430 | fn it_works_no_initial() { 431 | run_test(|| { 432 | { 433 | push_thread_stack_value!(50, STARTS_EMPTY); 434 | let_ref_thread_stack_value!(stack_value, STARTS_EMPTY); 435 | assert!(stack_value == &50); 436 | } 437 | push_thread_stack_value!(51, STARTS_EMPTY); 438 | let_ref_thread_stack_value!(stack_value, STARTS_EMPTY); 439 | assert!(stack_value == &51); 440 | assert!(clone_thread_stack_value(&STARTS_EMPTY) == 51); 441 | push_thread_stack_value!(52, STARTS_EMPTY); 442 | let_ref_thread_stack_value!(stack_value, STARTS_EMPTY); 443 | assert!(stack_value == &52); 444 | assert!(clone_thread_stack_value(&STARTS_EMPTY) == 52); 445 | }); 446 | } 447 | } 448 | --------------------------------------------------------------------------------