use std::cell::{Cell, RefCell};
use yo_kv::Db;
use yo_kv::news;
use super::Server;
use super::pubsub::{self, Kind};
pub(crate) mod class {
pub(crate) const KEYSPACE: u32 = 1 << 0;
pub(crate) const KEYEVENT: u32 = 1 << 1;
pub(crate) const GENERIC: u32 = 1 << 2;
pub(crate) const STRING: u32 = 1 << 3;
pub(crate) const LIST: u32 = 1 << 4;
pub(crate) const SET: u32 = 1 << 5;
pub(crate) const HASH: u32 = 1 << 6;
pub(crate) const ZSET: u32 = 1 << 7;
pub(crate) const EXPIRED: u32 = 1 << 8;
pub(crate) const EVICTED: u32 = 1 << 9;
pub(crate) const STREAM: u32 = 1 << 10;
pub(crate) const KEY_MISS: u32 = 1 << 11;
pub(crate) const MODULE: u32 = 1 << 13;
pub(crate) const NEW: u32 = 1 << 14;
pub(crate) const OVERWRITTEN: u32 = 1 << 15;
pub(crate) const TYPE_CHANGED: u32 = 1 << 16;
pub(crate) const SUBKEYSPACE: u32 = 1 << 19;
pub(crate) const SUBKEYEVENT: u32 = 1 << 20;
pub(crate) const SUBKEYSPACEITEM: u32 = 1 << 21;
pub(crate) const SUBKEYSPACEEVENT: u32 = 1 << 22;
pub(crate) const ARRAY: u32 = 1 << 23;
pub(crate) const ALL: u32 =
GENERIC | STRING | LIST | SET | HASH | ZSET | EXPIRED | EVICTED | STREAM | MODULE | ARRAY;
}
const SUBKEY: u32 =
class::SUBKEYSPACE | class::SUBKEYEVENT | class::SUBKEYSPACEITEM | class::SUBKEYSPACEEVENT;
const CHANNELS: u32 = class::KEYSPACE | class::KEYEVENT | SUBKEY;
const FIRST: &[(u8, u32)] = &[
(b'g', class::GENERIC),
(b'$', class::STRING),
(b'l', class::LIST),
(b's', class::SET),
(b'h', class::HASH),
(b'z', class::ZSET),
(b'x', class::EXPIRED),
(b'e', class::EVICTED),
(b't', class::STREAM),
(b'd', class::MODULE),
(b'a', class::ARRAY),
(b'n', class::NEW),
(b'o', class::OVERWRITTEN),
(b'c', class::TYPE_CHANGED),
];
const SECOND: &[(u8, u32)] = &[
(b'K', class::KEYSPACE),
(b'E', class::KEYEVENT),
(b'm', class::KEY_MISS),
(b'S', class::SUBKEYSPACE),
(b'T', class::SUBKEYEVENT),
(b'I', class::SUBKEYSPACEITEM),
(b'V', class::SUBKEYSPACEEVENT),
];
pub(crate) const ACCEPTED: &str = "Ag$lshzxeKEtmdnocaSTIV";
const WIDEST: usize = FIRST.len() + SECOND.len();
pub(crate) fn parse(classes: &[u8]) -> Option<u32> {
let mut flags = 0;
for &c in classes {
flags |= match c {
b'A' => class::ALL,
_ => {
let found = FIRST
.iter()
.chain(SECOND)
.find(|(ch, _)| *ch == c)
.map(|(_, bit)| *bit);
found?
}
};
}
Some(flags)
}
pub(crate) fn format(flags: u32) -> ([u8; WIDEST], usize) {
let mut out = [0u8; WIDEST];
let mut len = 0;
if flags & class::ALL == class::ALL {
out[len] = b'A';
len += 1;
} else {
for (c, bit) in FIRST {
if flags & bit != 0 {
out[len] = *c;
len += 1;
}
}
}
for (c, bit) in SECOND {
if flags & bit != 0 {
out[len] = *c;
len += 1;
}
}
(out, len)
}
struct Event {
db: usize,
name: &'static str,
key: Vec<u8>,
subs: Vec<Vec<u8>>,
}
thread_local! {
static PENDING: RefCell<Vec<Event>> = const { RefCell::new(Vec::new()) };
static ARMED: Cell<u32> = const { Cell::new(0) };
static WHERE: Cell<usize> = const { Cell::new(0) };
}
#[derive(Clone, Copy)]
pub(super) struct Armed {
flags: u32,
db: usize,
}
pub(super) fn arm(server: &Server, db: usize) -> Armed {
let flags = server.notify_flags();
let live = flags & CHANNELS != 0 && server.anyone_subscribed();
news::tell(live.then_some(heard as news::Told));
Armed {
flags: ARMED.replace(if live { flags } else { 0 }),
db: WHERE.replace(db),
}
}
pub(crate) fn about(db: usize) -> usize {
WHERE.replace(db)
}
fn heard(key: &[u8], what: news::What) {
let (class, name) = match what {
news::What::Born => (class::NEW, "new"),
news::What::Overwritten => (class::OVERWRITTEN, "overwritten"),
news::What::TypeChanged => (class::TYPE_CHANGED, "type_changed"),
news::What::Expired => (class::EXPIRED, "expired"),
news::What::Evicted => (class::EVICTED, "evicted"),
news::What::Missed => (class::KEY_MISS, MISS),
};
fire(WHERE.get(), class, name, key);
}
pub(crate) const MISS: &str = "keymiss";
pub(crate) fn unsay_misses() {
if ARMED.get() & class::KEY_MISS == 0 {
return;
}
PENDING.with_borrow_mut(|pending| pending.retain(|event| event.name != MISS));
}
pub(crate) fn armed() -> bool {
ARMED.get() != 0
}
pub(crate) fn wanted(class: u32) -> bool {
ARMED.get() & class != 0
}
pub(crate) fn subkeys_wanted(class: u32) -> bool {
let armed = ARMED.get();
armed & class != 0 && armed & SUBKEY != 0
}
pub(crate) fn emptied(db: &Db, on: usize, key: &[u8]) {
if armed() && !db.hold(key).exists(key) {
fire(on, class::GENERIC, "del", key);
}
}
pub(crate) fn replaced(db: usize, key: &[u8], was: Option<yo_kv::Kind>, now: Option<yo_kv::Kind>) {
let Some(was) = was else {
return;
};
fire(db, class::OVERWRITTEN, "overwritten", key);
if Some(was) != now {
fire(db, class::TYPE_CHANGED, "type_changed", key);
}
}
pub(crate) fn kind_now(look: impl FnOnce() -> Option<yo_kv::Kind>) -> Option<yo_kv::Kind> {
if wanted(class::OVERWRITTEN | class::TYPE_CHANGED) {
look()
} else {
None
}
}
pub(crate) fn fire(db: usize, class: u32, name: &'static str, key: &[u8]) {
if ARMED.get() & class == 0 {
return;
}
keep(db, name, key, Vec::new());
}
pub(crate) struct Subkeys {
names: Vec<Vec<u8>>,
wanted: bool,
}
impl Subkeys {
pub(crate) fn new(class: u32) -> Self {
Self {
names: Vec::new(),
wanted: subkeys_wanted(class),
}
}
pub(crate) fn of<'a>(class: u32, names: impl Iterator<Item = &'a [u8]>) -> Self {
let mut subs = Self::new(class);
for name in names {
subs.push(name);
}
subs
}
pub(crate) fn push(&mut self, name: &[u8]) {
if self.wanted {
yo_alloc::allow(|| self.names.push(name.to_vec()));
}
}
}
pub(crate) fn fire_subkeys(db: usize, class: u32, name: &'static str, key: &[u8], subs: Subkeys) {
if ARMED.get() & class == 0 {
return;
}
keep(db, name, key, subs.names);
}
#[cold]
#[inline(never)]
fn keep(db: usize, name: &'static str, key: &[u8], subs: Vec<Vec<u8>>) {
let event = yo_alloc::allow(|| Event {
db,
name,
key: key.to_vec(),
subs,
});
PENDING.with_borrow_mut(|pending| yo_alloc::allow(|| pending.push(event)));
}
pub(super) fn drain(server: &Server, was: Armed) {
WHERE.set(was.db);
let flags = ARMED.replace(was.flags);
if flags == 0 {
return;
}
let events = PENDING.with_borrow_mut(std::mem::take);
if events.is_empty() {
return;
}
for event in &events {
send(server, flags, event);
}
PENDING.with_borrow_mut(|pending| {
if pending.is_empty() {
let mut events = events;
events.clear();
*pending = events;
}
});
}
fn send(server: &Server, flags: u32, event: &Event) {
let mut channel = [0u8; CHANNEL_MAX];
if flags & class::KEYSPACE != 0 {
let head = prefix(&mut channel, b"__keyspace@", event.db);
yo_alloc::allow(|| {
let mut name = channel[..head].to_vec();
name.extend_from_slice(&event.key);
pubsub::deliver(server, Kind::Channel, &name, event.name.as_bytes());
});
}
if flags & class::KEYEVENT != 0 {
let head = prefix(&mut channel, b"__keyevent@", event.db);
yo_alloc::allow(|| {
let mut name = channel[..head].to_vec();
name.extend_from_slice(event.name.as_bytes());
pubsub::deliver(server, Kind::Channel, &name, &event.key);
});
}
if flags & SUBKEY != 0 && !event.subs.is_empty() {
yo_alloc::allow(|| subkeys(server, flags, event));
}
}
fn subkeys(server: &Server, flags: u32, event: &Event) {
let mut channel = [0u8; CHANNEL_MAX];
let bar = event.name.contains('|');
if flags & class::SUBKEYSPACE != 0 && !bar {
let head = prefix(&mut channel, b"__subkeyspace@", event.db);
let mut name = channel[..head].to_vec();
name.extend_from_slice(&event.key);
let mut payload = event.name.as_bytes().to_vec();
payload.push(b'|');
cat(&mut payload, &event.subs);
pubsub::deliver(server, Kind::Channel, &name, &payload);
}
if flags & class::SUBKEYEVENT != 0 {
let head = prefix(&mut channel, b"__subkeyevent@", event.db);
let mut name = channel[..head].to_vec();
name.extend_from_slice(event.name.as_bytes());
let mut payload = Vec::new();
len_prefixed(&mut payload, &event.key);
payload.push(b'|');
cat(&mut payload, &event.subs);
pubsub::deliver(server, Kind::Channel, &name, &payload);
}
if flags & class::SUBKEYSPACEITEM != 0 && !event.key.contains(&b'\n') {
let head = prefix(&mut channel, b"__subkeyspaceitem@", event.db);
for sub in &event.subs {
let mut name = channel[..head].to_vec();
name.extend_from_slice(&event.key);
name.push(b'\n');
name.extend_from_slice(sub);
pubsub::deliver(server, Kind::Channel, &name, event.name.as_bytes());
}
}
if flags & class::SUBKEYSPACEEVENT != 0 && !bar {
let head = prefix(&mut channel, b"__subkeyspaceevent@", event.db);
let mut name = channel[..head].to_vec();
name.extend_from_slice(event.name.as_bytes());
name.push(b'|');
name.extend_from_slice(&event.key);
let mut payload = Vec::new();
cat(&mut payload, &event.subs);
pubsub::deliver(server, Kind::Channel, &name, &payload);
}
}
fn cat(into: &mut Vec<u8>, subs: &[Vec<u8>]) {
for (i, sub) in subs.iter().enumerate() {
if i > 0 {
into.push(b',');
}
len_prefixed(into, sub);
}
}
fn len_prefixed(into: &mut Vec<u8>, bytes: &[u8]) {
let mut digits = [0u8; yo_common::num::DIGITS_MAX];
let len = i64::try_from(bytes.len()).unwrap_or(i64::MAX);
into.extend_from_slice(yo_common::num::i64_digits(&mut digits, len));
into.push(b':');
into.extend_from_slice(bytes);
}
const CHANNEL_MAX: usize = 19 + 20 + 3;
fn prefix(into: &mut [u8; CHANNEL_MAX], head: &[u8], db: usize) -> usize {
into[..head.len()].copy_from_slice(head);
let mut at = head.len();
let mut digits = [0u8; 20];
let mut n = db;
let mut count = 0;
loop {
digits[count] = b'0' + (n % 10) as u8;
count += 1;
n /= 10;
if n == 0 {
break;
}
}
for i in (0..count).rev() {
into[at] = digits[i];
at += 1;
}
into[at..at + 3].copy_from_slice(b"__:");
at + 3
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_accepted_characters_are_the_ones_the_error_message_lists() {
for c in ACCEPTED.bytes() {
assert!(parse(&[c]).is_some(), "{} was refused", c as char);
}
for c in 0u8..=127 {
if !ACCEPTED.as_bytes().contains(&c) {
assert!(parse(&[c]).is_none(), "{} was accepted", c as char);
}
}
assert_eq!(parse(b""), Some(0), "the empty setting is off and is legal");
}
#[test]
fn a_setting_reads_back_in_one_order_whatever_order_it_was_set_in() {
let round = |s: &str| {
let flags = parse(s.as_bytes()).unwrap();
let (buf, len) = format(flags);
String::from_utf8(buf[..len].to_vec()).unwrap()
};
assert_eq!(round("KEA"), "AKE");
assert_eq!(round("AKE"), "AKE");
assert_eq!(round("gxE"), "gxE");
assert_eq!(round("K"), "K");
assert_eq!(round("E"), "E");
assert_eq!(round(""), "");
assert_eq!(round("A"), "A");
assert_eq!(round("Kg"), "gK");
assert_eq!(round("nKE"), "nKE");
assert_eq!(round("KEg$lshzxetdmn"), "g$lshzxetdnKEm");
}
#[test]
fn all_leaves_out_the_four_classes_it_leaves_out() {
let all = parse(b"A").unwrap();
for (c, bit) in [
(b'm', class::KEY_MISS),
(b'n', class::NEW),
(b'o', class::OVERWRITTEN),
(b'c', class::TYPE_CHANGED),
] {
assert_eq!(all & bit, 0, "A should not contain {}", c as char);
}
let both = parse(b"An").unwrap();
assert_eq!(both & class::NEW, class::NEW);
assert_eq!(both & class::EXPIRED, class::EXPIRED);
let (buf, len) = format(both);
assert_eq!(&buf[..len], b"A");
let (buf, len) = format(parse(b"Am").unwrap());
assert_eq!(&buf[..len], b"Am");
}
#[test]
fn a_channel_name_carries_the_database_it_happened_on() {
let mut buf = [0u8; CHANNEL_MAX];
let len = prefix(&mut buf, b"__keyspace@", 0);
assert_eq!(&buf[..len], b"__keyspace@0__:");
let len = prefix(&mut buf, b"__keyevent@", 15);
assert_eq!(&buf[..len], b"__keyevent@15__:");
let len = prefix(&mut buf, b"__subkeyspaceevent@", 15);
assert_eq!(&buf[..len], b"__subkeyspaceevent@15__:");
}
#[test]
fn a_field_list_is_length_prefixed_and_comma_joined() {
let mut out = Vec::new();
cat(&mut out, &[b"foo".to_vec(), b"hello".to_vec()]);
assert_eq!(out, b"3:foo,5:hello");
let mut out = Vec::new();
cat(&mut out, &[b"a,b".to_vec()]);
assert_eq!(out, b"3:a,b");
let mut out = Vec::new();
cat(&mut out, &[]);
assert_eq!(out, b"");
}
#[test]
fn the_subkey_channels_count_as_channels() {
for one in ["S", "T", "I", "V"] {
let flags = parse(one.as_bytes()).unwrap();
assert_ne!(flags & CHANNELS, 0, "{one} should be a channel");
}
assert_eq!(parse(b"h").unwrap() & CHANNELS, 0, "h is a class");
}
}