use yo_common::{Code, Error, Result, num};
use yo_kv::{End, Entry, Keyspace, Member, ZEnd};
use super::args::{self, Args};
use super::lists::{BAD_MPOP_COUNT, BAD_NUMKEYS, end_of};
use super::table::Spec;
use super::zsets;
use super::{Flow, Server, Session};
use crate::reply::Out;
const NOT_A_FLOAT: &str = "timeout is not a float or out of range";
const NEGATIVE: &str = "timeout is negative";
const OUT_OF_RANGE: &str = "timeout is out of range";
pub(super) fn execute(
server: &mut Server,
session: &Session,
spec: &Spec,
args: Args<'_>,
out: &mut Out,
) -> Result<Flow> {
let now = server.now_ms();
let last = args.len() - 1;
let (deadline, block) = match spec.name {
"blpop" | "brpop" => {
let end = if spec.name == "blpop" {
End::Left
} else {
End::Right
};
let deadline = timeout(args.get(last), now)?;
(deadline, Block::pop((1..last).map(|i| args.get(i)), end))
}
"blmove" => {
let (from, to) = (end_of(args.get(3))?, end_of(args.get(4))?);
let deadline = timeout(args.get(5), now)?;
(deadline, Block::moved(args.get(1), args.get(2), from, to))
}
"brpoplpush" => {
let deadline = timeout(args.get(3), now)?;
(
deadline,
Block::moved(args.get(1), args.get(2), End::Right, End::Left),
)
}
"blmpop" => mpop(args, now)?,
"bzpopmin" | "bzpopmax" => {
let end = zsets::end_of_name(spec.name);
let deadline = timeout(args.get(last), now)?;
(deadline, Block::zpop((1..last).map(|i| args.get(i)), end))
}
"bzmpop" => {
let deadline = timeout(args.get(1), now)?;
let (end, from, to, count) = zsets::parse_mpop(args, 2)?;
(
deadline,
Block::zmpop((from..to).map(|i| args.get(i)), end, count),
)
}
_ => return Err(args::syntax()),
};
let db = session.db();
if block.now(server.db(db), out)? {
return Ok(Flow::Continue);
}
server.park(session.id(), db, deadline, block);
Ok(Flow::Block)
}
fn mpop(args: Args<'_>, now: u64) -> Result<(Option<u64>, Block)> {
let deadline = timeout(args.get(1), now)?;
let numkeys = match args.int(2) {
Ok(n) if n > 0 => usize::try_from(n).unwrap_or(usize::MAX),
_ => return Err(Error::new(Code::Invalid, BAD_NUMKEYS)),
};
if numkeys >= args.len() - 3 {
return Err(args::syntax());
}
let at = 3 + numkeys;
let end = end_of(args.get(at))?;
let mut want = 1usize;
if at + 1 < args.len() {
if args.len() != at + 3 || !args::is(args.get(at + 1), b"count") {
return Err(args::syntax());
}
want = match args.int(at + 2) {
Ok(n) if n > 0 => usize::try_from(n).unwrap_or(usize::MAX),
_ => return Err(Error::new(Code::Invalid, BAD_MPOP_COUNT)),
};
}
Ok((
deadline,
Block::mpop((3..at).map(|i| args.get(i)), end, want),
))
}
fn timeout(arg: &[u8], now: u64) -> Result<Option<u64>> {
let Some(secs) = num::parse_f64(arg) else {
return Err(Error::new(Code::Invalid, NOT_A_FLOAT));
};
if secs < 0.0 {
return Err(Error::new(Code::Invalid, NEGATIVE));
}
let ms = secs * 1000.0;
if ms > i64::MAX as f64 {
return Err(Error::new(Code::Invalid, OUT_OF_RANGE));
}
if ms <= 0.0 {
return Ok(None);
}
Ok(Some(now.saturating_add(ms as u64)))
}
enum Want {
Pop { end: End },
Move { dst: Vec<u8>, from: End, to: End },
Mpop { end: End, count: usize },
ZPop { end: ZEnd },
ZMpop { end: ZEnd, count: usize },
}
impl Want {
fn attempt(
&self,
keys: &[Vec<u8>],
db: &mut Keyspace,
out: &mut Out,
strict: bool,
) -> Result<bool> {
match self {
Want::Pop { end } => {
for key in keys {
if !ready(db, key, strict)? {
continue;
}
out.array(2);
out.bulk(key);
db.pop_into(key, *end, 1, |e| element(out, e))?;
return Ok(true);
}
Ok(false)
}
Want::Mpop { end, count } => {
for key in keys {
if !ready(db, key, strict)? {
continue;
}
out.array(2);
out.bulk(key);
let mark = out.len();
let n = db.pop_into(key, *end, *count, |e| element(out, e))?;
out.close_array(mark, n);
return Ok(true);
}
Ok(false)
}
Want::ZPop { end } => {
for key in keys {
if !zready(db, key, strict)? {
continue;
}
out.array(3);
out.bulk(key);
db.zpop(key, *end, 1, |m, sc| {
member(out, m);
out.double(sc);
})?;
return Ok(true);
}
Ok(false)
}
Want::ZMpop { end, count } => {
for key in keys {
if !zready(db, key, strict)? {
continue;
}
out.array(2);
out.bulk(key);
let mark = out.len();
let n = db.zpop(key, *end, *count, |m, sc| {
out.array(2);
member(out, m);
out.double(sc);
})?;
out.close_array(mark, n);
return Ok(true);
}
Ok(false)
}
Want::Move { dst, from, to } => {
let src = &keys[0];
if !ready(db, src, strict)? {
return Ok(false);
}
match db.lmove(src, dst, *from, *to) {
Ok(Some(v)) => {
out.bulk(&v);
Ok(true)
}
Ok(None) => Ok(false),
Err(e) if strict => Err(e),
Err(_) => Ok(false),
}
}
}
}
}
fn ready(db: &mut Keyspace, key: &[u8], strict: bool) -> Result<bool> {
match db.llen(key) {
Ok(n) => Ok(n > 0),
Err(e) if strict => Err(e),
Err(_) => Ok(false),
}
}
fn zready(db: &mut Keyspace, key: &[u8], strict: bool) -> Result<bool> {
match db.zcard(key) {
Ok(n) => Ok(n > 0),
Err(e) if strict => Err(e),
Err(_) => Ok(false),
}
}
#[inline]
fn element(out: &mut Out, e: Entry<'_>) {
match e {
Entry::Int(n) => out.bulk_int(n),
Entry::Str(s) => out.bulk(s),
}
}
#[inline]
fn member(out: &mut Out, m: Member<'_>) {
match m {
Member::Int(n) => out.bulk_int(n),
Member::Str(s) => out.bulk(s),
}
}
pub struct Block {
keys: Vec<Vec<u8>>,
want: Want,
}
impl Block {
fn pop<'a>(keys: impl Iterator<Item = &'a [u8]>, end: End) -> Block {
Block {
keys: owned(keys),
want: Want::Pop { end },
}
}
fn mpop<'a>(keys: impl Iterator<Item = &'a [u8]>, end: End, count: usize) -> Block {
Block {
keys: owned(keys),
want: Want::Mpop { end, count },
}
}
fn zpop<'a>(keys: impl Iterator<Item = &'a [u8]>, end: ZEnd) -> Block {
Block {
keys: owned(keys),
want: Want::ZPop { end },
}
}
fn zmpop<'a>(keys: impl Iterator<Item = &'a [u8]>, end: ZEnd, count: usize) -> Block {
Block {
keys: owned(keys),
want: Want::ZMpop { end, count },
}
}
fn moved(src: &[u8], dst: &[u8], from: End, to: End) -> Block {
yo_alloc::allow(|| Block {
keys: vec![src.to_vec()],
want: Want::Move {
dst: dst.to_vec(),
from,
to,
},
})
}
fn now(&self, db: &mut Keyspace, out: &mut Out) -> Result<bool> {
self.want.attempt(&self.keys, db, out, true)
}
}
fn owned<'a>(keys: impl Iterator<Item = &'a [u8]>) -> Vec<Vec<u8>> {
yo_alloc::allow(|| keys.map(<[u8]>::to_vec).collect())
}
struct Waiter {
client: u64,
conn: u32,
db: usize,
deadline: Option<u64>,
keys: Vec<Vec<u8>>,
want: Want,
}
#[derive(Default)]
pub struct Waiters {
list: Vec<Waiter>,
}
#[derive(Debug, Clone, Copy)]
pub struct Parked {
pub conn: u32,
pub client: u64,
}
impl Waiters {
#[must_use]
#[inline]
pub fn is_empty(&self) -> bool {
self.list.is_empty()
}
#[must_use]
#[inline]
pub fn len(&self) -> usize {
self.list.len()
}
#[must_use]
pub fn at(&self, at: usize) -> Parked {
let w = &self.list[at];
Parked {
conn: w.conn,
client: w.client,
}
}
#[must_use]
pub fn db_of(&self, at: usize) -> usize {
self.list[at].db
}
pub fn drop_at(&mut self, at: usize) {
self.list.remove(at);
}
pub fn forget(&mut self, client: u64) {
self.list.retain(|w| w.client != client);
}
pub fn bind(&mut self, client: u64, conn: u32) {
if let Some(w) = self.list.iter_mut().rev().find(|w| w.client == client) {
w.conn = conn;
}
}
fn park(&mut self, client: u64, db: usize, deadline: Option<u64>, block: Block) {
yo_alloc::allow(|| {
self.list.push(Waiter {
client,
conn: 0,
db,
deadline,
keys: block.keys,
want: block.want,
});
});
}
fn try_serve(&self, at: usize, dbs: &mut [Keyspace], now: u64, out: &mut Out) -> bool {
let w = &self.list[at];
let mark = out.len();
match w.want.attempt(&w.keys, &mut dbs[w.db], out, false) {
Ok(true) => return true,
Ok(false) => {}
Err(_) => out.truncate(mark),
}
if w.deadline.is_some_and(|d| now >= d) {
out.nil_array();
return true;
}
false
}
}
impl Server {
#[must_use]
pub fn now_ms(&self) -> u64 {
self.clock.now_ms()
}
#[must_use]
pub const fn waiters(&self) -> &Waiters {
&self.waiters
}
pub const fn waiters_mut(&mut self) -> &mut Waiters {
&mut self.waiters
}
pub(super) fn park(&mut self, client: u64, db: usize, deadline: Option<u64>, block: Block) {
self.waiters.park(client, db, deadline, block);
}
pub fn serve_waiter(&mut self, at: usize, now: u64, out: &mut Out) -> bool {
self.dirty |= 1u64 << self.waiters.db_of(at);
self.waiters.try_serve(at, &mut self.dbs, now, out)
}
}