Skip to main content

apollo_router/context/extensions/
sync.rs

1use std::ops::Deref;
2use std::ops::DerefMut;
3use std::sync::Arc;
4#[cfg(debug_assertions)]
5use std::time::Duration;
6#[cfg(debug_assertions)]
7use std::time::Instant;
8
9/// You can use `Extensions` to pass data between plugins that is not serializable. Such data is not accessible from Rhai or co-processoers.
10///
11/// This can be accessed at any point in the request lifecycle and is useful for passing data between services.
12/// Extensions are thread safe, and must be locked for mutation.
13///
14/// For example:
15/// `context.extensions().lock().insert::<MyData>(data);`
16#[derive(Default, Clone, Debug)]
17pub struct ExtensionsMutex {
18    extensions: Arc<parking_lot::Mutex<super::Extensions>>,
19}
20
21impl ExtensionsMutex {
22    /// Locks the extensions for mutation.
23    ///
24    /// It is CRITICAL to avoid holding on to the mutex guard for too long, particularly across async calls.
25    /// Doing so may cause performance degradation or even deadlocks.
26    ///
27    /// DEPRECATED: prefer with_lock()
28    ///
29    /// See related clippy lint for examples: <https://rust-lang.github.io/rust-clippy/master/index.html#/await_holding_lock>
30    #[deprecated]
31    pub fn lock(&self) -> ExtensionsGuard {
32        ExtensionsGuard::new(&self.extensions)
33    }
34
35    /// Locks the extensions for interaction.
36    ///
37    /// The lock will be dropped once the closure completes.
38    pub fn with_lock<'a, T, F: FnOnce(ExtensionsGuard<'a>) -> T>(&'a self, func: F) -> T {
39        let locked = ExtensionsGuard::new(&self.extensions);
40        func(locked)
41    }
42}
43
44pub struct ExtensionsGuard<'a> {
45    #[cfg(debug_assertions)]
46    start: Instant,
47    guard: parking_lot::MutexGuard<'a, super::Extensions>,
48}
49impl<'a> ExtensionsGuard<'a> {
50    fn new(guard: &'a parking_lot::Mutex<super::Extensions>) -> Self {
51        // IMPORTANT: Rust fields are constructed in the order that in which you write the fields in the initializer
52        // The guard MUST be initialized first otherwise time waiting for a lock is included in this time.
53        Self {
54            guard: guard.lock(),
55            #[cfg(debug_assertions)]
56            start: Instant::now(),
57        }
58    }
59}
60
61impl Deref for ExtensionsGuard<'_> {
62    type Target = super::Extensions;
63
64    fn deref(&self) -> &super::Extensions {
65        &self.guard
66    }
67}
68
69impl DerefMut for ExtensionsGuard<'_> {
70    fn deref_mut(&mut self) -> &mut super::Extensions {
71        &mut self.guard
72    }
73}
74
75#[cfg(debug_assertions)]
76impl Drop for ExtensionsGuard<'_> {
77    fn drop(&mut self) {
78        // In debug builds we check that extensions is never held for too long.
79        // We  only check if the current runtime is multi-threaded, because a bunch of unit tests fail the assertion and these need to be investigated separately.
80        if let Ok(runtime) = tokio::runtime::Handle::try_current() {
81            if runtime.runtime_flavor() == tokio::runtime::RuntimeFlavor::MultiThread {
82                let elapsed = self.start.elapsed();
83                if elapsed > Duration::from_millis(10) {
84                    panic!(
85                        "ExtensionsGuard held for {}ms. This is probably a bug that will stall the Router and cause performance problems. Run with `RUST_BACKTRACE=1` environment variable to display a backtrace",
86                        elapsed.as_millis()
87                    );
88                }
89            }
90        }
91    }
92}