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