redis_module/context/
timer.rs

1use std::convert::TryInto;
2use std::ffi::c_void;
3use std::time::Duration;
4
5use crate::raw;
6use crate::raw::RedisModuleTimerID;
7use crate::{Context, RedisError};
8
9// We use `repr(C)` since we access the underlying data field directly.
10// The order matters: the data field must come first.
11#[repr(C)]
12struct CallbackData<F: FnOnce(&Context, T), T> {
13    data: T,
14    callback: F,
15}
16
17impl Context {
18    /// Wrapper for `RedisModule_CreateTimer`.
19    ///
20    /// This function takes ownership of the provided data, and transfers it to Redis.
21    /// The callback will get the original data back in a type safe manner.
22    /// When the callback is done, the data will be dropped.
23    pub fn create_timer<F, T: 'static>(
24        &self,
25        period: Duration,
26        callback: F,
27        data: T,
28    ) -> RedisModuleTimerID
29    where
30        F: FnOnce(&Self, T),
31    {
32        let cb_data = CallbackData { data, callback };
33
34        // Store the user-provided data on the heap before passing ownership of it to Redis,
35        // so that it will outlive the current scope.
36        let data = Box::from(cb_data);
37
38        // Take ownership of the data inside the box and obtain a raw pointer to pass to Redis.
39        let data = Box::into_raw(data);
40
41        unsafe {
42            raw::RedisModule_CreateTimer.unwrap()(
43                self.ctx,
44                period
45                    .as_millis()
46                    .try_into()
47                    .expect("Value must fit in 64 bits"),
48                Some(raw_callback::<F, T>),
49                data.cast::<c_void>(),
50            )
51        }
52    }
53
54    /// Wrapper for `RedisModule_StopTimer`.
55    ///
56    /// The caller is responsible for specifying the correct type for the returned data.
57    /// This function has no way to know what the original type of the data was, so the
58    /// same data type that was used for `create_timer` needs to be passed here to ensure
59    /// their types are identical.
60    pub fn stop_timer<T>(&self, timer_id: RedisModuleTimerID) -> Result<T, RedisError> {
61        let mut data: *mut c_void = std::ptr::null_mut();
62
63        let status: raw::Status =
64            unsafe { raw::RedisModule_StopTimer.unwrap()(self.ctx, timer_id, &mut data) }.into();
65
66        if status != raw::Status::Ok {
67            return Err(RedisError::Str(
68                "RedisModule_StopTimer failed, timer may not exist",
69            ));
70        }
71
72        let data: T = take_data(data);
73        Ok(data)
74    }
75
76    /// Wrapper for `RedisModule_GetTimerInfo`.
77    ///
78    /// The caller is responsible for specifying the correct type for the returned data.
79    /// This function has no way to know what the original type of the data was, so the
80    /// same data type that was used for `create_timer` needs to be passed here to ensure
81    /// their types are identical.
82    pub fn get_timer_info<T>(
83        &self,
84        timer_id: RedisModuleTimerID,
85    ) -> Result<(Duration, &T), RedisError> {
86        let mut remaining: u64 = 0;
87        let mut data: *mut c_void = std::ptr::null_mut();
88
89        let status: raw::Status = unsafe {
90            raw::RedisModule_GetTimerInfo.unwrap()(self.ctx, timer_id, &mut remaining, &mut data)
91        }
92        .into();
93
94        if status != raw::Status::Ok {
95            return Err(RedisError::Str(
96                "RedisModule_GetTimerInfo failed, timer may not exist",
97            ));
98        }
99
100        // Cast the *mut c_void supplied by the Redis API to a raw pointer of our custom type.
101        let data = data.cast::<T>();
102
103        // Dereference the raw pointer (we know this is safe, since Redis should return our
104        // original pointer which we know to be good) and turn it into a safe reference
105        let data = unsafe { &*data };
106
107        Ok((Duration::from_millis(remaining), data))
108    }
109}
110
111fn take_data<T>(data: *mut c_void) -> T {
112    // Cast the *mut c_void supplied by the Redis API to a raw pointer of our custom type.
113    let data = data.cast::<T>();
114
115    // Take back ownership of the original boxed data, so we can unbox it safely.
116    // If we don't do this, the data's memory will be leaked.
117    let data = unsafe { Box::from_raw(data) };
118
119    *data
120}
121
122extern "C" fn raw_callback<F, T>(ctx: *mut raw::RedisModuleCtx, data: *mut c_void)
123where
124    F: FnOnce(&Context, T),
125{
126    let ctx = &Context::new(ctx);
127
128    if data.is_null() {
129        ctx.log_debug("[callback] Data is null; this should not happen!");
130        return;
131    }
132
133    let cb_data: CallbackData<F, T> = take_data(data);
134    (cb_data.callback)(ctx, cb_data.data);
135}