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}