Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion examples/events.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use redis_module::{
redis_module, Context, NotifyEvent, RedisError, RedisResult, RedisString, RedisValue, Status,
raw, redis_module, Context, NotifyEvent, RedisError, RedisResult, RedisString, RedisValue,
Status,
};
use std::ffi::CString;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::Mutex;

static NUM_KEY_MISSES: AtomicI64 = AtomicI64::new(0);
static NUM_KEYS: AtomicI64 = AtomicI64::new(0);
static LAST_GENERIC_EVENT: Mutex<String> = Mutex::new(String::new());

fn on_event(ctx: &Context, event_type: NotifyEvent, event: &str, key: &[u8]) {
if key == b"num_sets" {
Expand Down Expand Up @@ -45,6 +49,45 @@ fn event_send(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
}
}

fn on_generic(_ctx: &Context, _event_type: NotifyEvent, event: &str, _key: &[u8]) {
*LAST_GENERIC_EVENT.lock().unwrap() = event.to_string();
}

fn event_send_invalid_utf8(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
if args.len() > 1 {
return Err(RedisError::WrongArity);
}

let key_name = RedisString::create(NonNull::new(ctx.ctx), "mykey");
// Fire a keyspace event whose name is not valid UTF-8 (0xFF can never
// appear in a UTF-8 string), the way a C module could. This has to go
// through the raw API because the safe wrapper only accepts &str.
let event = CString::new(&b"ev\xFFnt"[..]).unwrap();
// SAFETY: `RedisModule_NotifyKeyspaceEvent` is set by Redis before any
// command handler runs, so the `unwrap` cannot fail. `ctx.ctx` is the
// valid context passed to this command invocation, `event` is a
// NUL-terminated C string that outlives the call, and `key_name.inner`
// is a valid `RedisModuleString` owned by `key_name` for the duration
// of the call.
let status: Status = unsafe {
Comment thread
oshadmi marked this conversation as resolved.
raw::RedisModule_NotifyKeyspaceEvent.unwrap()(
ctx.ctx,
NotifyEvent::GENERIC.bits(),
event.as_ptr(),
key_name.inner,
)
}
.into();
match status {
Status::Ok => Ok("Event sent".into()),
Status::Err => Err(RedisError::Str("Generic error")),
}
}

fn last_generic_event(_ctx: &Context, _args: Vec<RedisString>) -> RedisResult {
Ok(LAST_GENERIC_EVENT.lock().unwrap().clone().into())
}

fn on_key_miss(_ctx: &Context, _event_type: NotifyEvent, _event: &str, _key: &[u8]) {
NUM_KEY_MISSES.fetch_add(1, Ordering::SeqCst);
}
Expand All @@ -69,11 +112,14 @@ redis_module! {
data_types: [],
commands: [
["events.send", event_send, "", 0, 0, 0, ""],
["events.send_invalid_utf8", event_send_invalid_utf8, "", 0, 0, 0, ""],
["events.last_generic_event", last_generic_event, "", 0, 0, 0, ""],
["events.num_key_miss", num_key_miss, "", 0, 0, 0, ""],
["events.num_keys", num_keys, "", 0, 0, 0, ""],
],
event_handlers: [
[@STRING: on_event],
[@GENERIC: on_generic],
[@STREAM: on_stream],
[@MISSED: on_key_miss],
[@NEW: on_new_key],
Expand Down
4 changes: 2 additions & 2 deletions src/macros.rs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can add a test with invalid UTF-8?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a test for an invalid UTF-8 event name.

The ACL category currently cannot be invalid UTF-8 (but I still think the change is reasonable), so no test for that.

Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ macro_rules! redis_command {
$ctx,
&format!(
"Warning: failed to set command `{}` ACL categories `{}`",
$command_name, acl_categories.to_str().unwrap()
$command_name, &acl_categories.to_string_lossy()
Comment thread
oshadmi marked this conversation as resolved.
),
);
}
Expand Down Expand Up @@ -129,7 +129,7 @@ macro_rules! redis_event_handler {
$event_handler(
&context,
$crate::NotifyEvent::from_bits_truncate(event_type),
event_str.to_str().unwrap(),
&event_str.to_string_lossy(),
redis_key,
);

Expand Down
21 changes: 21 additions & 0 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,27 @@ fn test_key_space_notifications() -> Result<()> {
Ok(())
}

#[test]
fn test_key_space_notification_with_invalid_utf8_event_name() -> Result<()> {
let mut con = TestConnection::new("events");

// Fires a keyspace event whose name contains the byte 0xFF, which is
// never valid UTF-8. The event handler must not panic on it.
let res: String = redis::cmd("events.send_invalid_utf8").query(&mut con)?;
assert_eq!(res, "Event sent");

// The handler received the event name with the invalid byte replaced
// by U+FFFD (lossy conversion).
let res: String = redis::cmd("events.last_generic_event").query(&mut con)?;
assert_eq!(res, "ev\u{FFFD}nt");

// The server survived.
let res: String = redis::cmd("PING").query(&mut con)?;
assert_eq!(res, "PONG");

Ok(())
}

#[test]
fn test_context_mutex() -> Result<()> {
let mut con = TestConnection::new("threads");
Expand Down
Loading