redis_module/context/
thread_safe.rs

1use std::borrow::Borrow;
2use std::cell::UnsafeCell;
3use std::ops::{Deref, DerefMut};
4use std::ptr;
5
6use crate::context::blocked::BlockedClient;
7use crate::{raw, Context, RedisResult};
8
9pub struct RedisGILGuardScope<'ctx, 'mutex, T, G: RedisLockIndicator> {
10    _context: &'ctx G,
11    mutex: &'mutex RedisGILGuard<T>,
12}
13
14impl<'ctx, 'mutex, T, G: RedisLockIndicator> Deref for RedisGILGuardScope<'ctx, 'mutex, T, G> {
15    type Target = T;
16
17    fn deref(&self) -> &Self::Target {
18        unsafe { &*self.mutex.obj.get() }
19    }
20}
21
22impl<'ctx, 'mutex, T, G: RedisLockIndicator> DerefMut for RedisGILGuardScope<'ctx, 'mutex, T, G> {
23    fn deref_mut(&mut self) -> &mut Self::Target {
24        unsafe { &mut *self.mutex.obj.get() }
25    }
26}
27
28/// Whenever the user gets a reference to a struct that
29/// implements this trait, it can assume that the Redis GIL
30/// is held. Any struct that implements this trait can be
31/// used to retrieve objects which are GIL protected (see
32/// [RedisGILGuard] for more information)
33///
34/// Notice that this trait only gives indication that the
35/// GIL is locked, unlike [RedisGILGuard] which protect data
36/// access and make sure the protected data is only accesses
37/// when the GIL is locked.
38///
39/// # Safety
40///
41/// In general this trait should not be implemented by the
42/// user, the crate knows when the Redis GIL is held and will
43/// make sure to implement this trait correctly on different
44/// struct (such as [Context], [ConfigurationContext], [ContextGuard]).
45/// User might also decide to implement this trait but he should
46/// carefully consider that because it is easy to make mistakes,
47/// this is why the trait is marked as unsafe.
48pub unsafe trait RedisLockIndicator {}
49
50/// This struct allows to guard some data and makes sure
51/// the data is only access when the Redis GIL is locked.
52/// From example, assuming you module want to save some
53/// statistics inside some global variable, but without the
54/// need to protect this variable with some mutex (because
55/// we know this variable is protected by Redis lock).
56/// For example, look at examples/threads.rs
57pub struct RedisGILGuard<T> {
58    obj: UnsafeCell<T>,
59}
60
61impl<T> RedisGILGuard<T> {
62    pub fn new(obj: T) -> RedisGILGuard<T> {
63        RedisGILGuard {
64            obj: UnsafeCell::new(obj),
65        }
66    }
67
68    pub fn lock<'mutex, 'ctx, G: RedisLockIndicator>(
69        &'mutex self,
70        context: &'ctx G,
71    ) -> RedisGILGuardScope<'ctx, 'mutex, T, G> {
72        RedisGILGuardScope {
73            _context: context,
74            mutex: self,
75        }
76    }
77}
78
79impl<T: Default> Default for RedisGILGuard<T> {
80    fn default() -> Self {
81        Self::new(T::default())
82    }
83}
84
85unsafe impl<T> Sync for RedisGILGuard<T> {}
86unsafe impl<T> Send for RedisGILGuard<T> {}
87
88pub struct ContextGuard {
89    ctx: Context,
90}
91
92unsafe impl RedisLockIndicator for ContextGuard {}
93
94impl Drop for ContextGuard {
95    fn drop(&mut self) {
96        unsafe {
97            raw::RedisModule_ThreadSafeContextUnlock.unwrap()(self.ctx.ctx);
98            raw::RedisModule_FreeThreadSafeContext.unwrap()(self.ctx.ctx);
99        };
100    }
101}
102
103impl Deref for ContextGuard {
104    type Target = Context;
105
106    fn deref(&self) -> &Self::Target {
107        &self.ctx
108    }
109}
110
111impl Borrow<Context> for ContextGuard {
112    fn borrow(&self) -> &Context {
113        &self.ctx
114    }
115}
116
117/// A ``ThreadSafeContext`` can either be bound to a blocked client, or detached from any client.
118pub struct DetachedFromClient;
119
120pub struct ThreadSafeContext<B: Send> {
121    pub(crate) ctx: *mut raw::RedisModuleCtx,
122
123    /// This field is only used implicitly by `Drop`, so avoid a compiler warning
124    #[allow(dead_code)]
125    blocked_client: B,
126}
127
128unsafe impl<B: Send> Send for ThreadSafeContext<B> {}
129unsafe impl<B: Send> Sync for ThreadSafeContext<B> {}
130
131impl ThreadSafeContext<DetachedFromClient> {
132    #[must_use]
133    pub fn new() -> Self {
134        let ctx = unsafe { raw::RedisModule_GetThreadSafeContext.unwrap()(ptr::null_mut()) };
135        Self {
136            ctx,
137            blocked_client: DetachedFromClient,
138        }
139    }
140}
141
142impl Default for ThreadSafeContext<DetachedFromClient> {
143    fn default() -> Self {
144        Self::new()
145    }
146}
147
148impl ThreadSafeContext<BlockedClient> {
149    #[must_use]
150    pub fn with_blocked_client(blocked_client: BlockedClient) -> Self {
151        let ctx = unsafe { raw::RedisModule_GetThreadSafeContext.unwrap()(blocked_client.inner) };
152        Self {
153            ctx,
154            blocked_client,
155        }
156    }
157
158    /// The Redis modules API does not require locking for `Reply` functions,
159    /// so we pass through its functionality directly.
160    #[allow(clippy::must_use_candidate)]
161    pub fn reply(&self, r: RedisResult) -> raw::Status {
162        let ctx = Context::new(self.ctx);
163        ctx.reply(r)
164    }
165}
166
167impl<B: Send> ThreadSafeContext<B> {
168    /// All other APIs require locking the context, so we wrap it in a way
169    /// similar to `std::sync::Mutex`.
170    pub fn lock(&self) -> ContextGuard {
171        unsafe { raw::RedisModule_ThreadSafeContextLock.unwrap()(self.ctx) };
172        let ctx = unsafe { raw::RedisModule_GetThreadSafeContext.unwrap()(ptr::null_mut()) };
173        let ctx = Context::new(ctx);
174        ContextGuard { ctx }
175    }
176}
177
178impl<B: Send> Drop for ThreadSafeContext<B> {
179    fn drop(&mut self) {
180        unsafe { raw::RedisModule_FreeThreadSafeContext.unwrap()(self.ctx) };
181    }
182}