Skip to main content

yo_resp/dispatch/
blocking.rs

1//! The six list commands that wait, and the machinery that lets a client wait.
2//!
3//! `BLPOP` is `LPOP` with one difference: when there is nothing to pop, the
4//! client waits instead of being told no. Everything here is about that wait,
5//! and nothing here knows anything about lists that [`super::lists`] does not
6//! already know.
7//!
8//! # The command is kept, not the client
9//!
10//! A parked client is a [`Waiter`]: the keys it named, what it wanted to do with
11//! them, and when to give up. It is not a suspended stack and it is not a task.
12//! Answering it later is running the same attempt again against a database that
13//! has changed since, which is why [`Want::attempt`] is the whole of both paths.
14//! The command handler calls it once to see whether the client has to wait at
15//! all, and the retry calls it again each time something might have arrived.
16//!
17//! That is also why the six commands cost nothing when they do not block. A
18//! `BLPOP` on a list with something in it runs the same three lines `LPOP` runs
19//! and never touches the waiter list.
20//!
21//! # A slot is reused and a client id is not
22//!
23//! A waiter remembers both. The slot is where that connection's reply buffer
24//! is, and the client id is what says the connection sitting on that slot is
25//! still the one that blocked. The engine takes a waiter off the list when its
26//! connection closes, so the check should never fail, and it is there because
27//! the cost of being wrong about it is a reply written into somebody else's
28//! socket.
29//!
30//! # What wakes a waiter
31//!
32//! Any command at all, which is more than is needed and is not the cost it
33//! sounds like: the engine looks at whether anybody is parked before it looks at
34//! anything else, so a server with no blocked clients pays one load and one
35//! branch per command and nothing more. Narrowing it to writes would save
36//! nothing measurable and would need a rule about which commands can put a list
37//! under a key, which `RENAME`, `COPY` and `RESTORE` all make longer than it
38//! looks.
39//!
40//! What is left is that the waiter list is walked rather than indexed by key, so
41//! a server with a thousand parked workers walks a thousand entries per command.
42//! The fix when that matters is an index from key to waiter, not a different
43//! rule about when to look.
44
45use yo_common::{Code, Error, Result, num};
46use yo_kv::{Db, End, Entry, Member, Movem, ZEnd};
47
48use super::args::{self, Args, NOT_AN_INT};
49use super::lists::{BAD_MPOP_COUNT, BAD_NUMKEYS, end_of, movem_options};
50use super::streams;
51use super::table::Spec;
52use super::zsets;
53use super::{Flow, Server, Session};
54use crate::reply::Out;
55
56/// What Redis says about a timeout it cannot read as a number.
57const NOT_A_FLOAT: &str = "timeout is not a float or out of range";
58/// What it says about one it can read and will not take.
59const NEGATIVE: &str = "timeout is negative";
60/// And about one so far away that milliseconds do not fit in an `i64`.
61const OUT_OF_RANGE: &str = "timeout is out of range";
62/// `WAIT` and `WAITAOF` take their timeout in whole milliseconds rather than in
63/// seconds, so a timeout they cannot read is a different complaint again.
64const TIMEOUT_NOT_AN_INT: &str = "timeout is not an integer or out of range";
65/// What `WAITAOF` says about a `numlocal` that is neither of the two it takes.
66const NOT_ZERO_OR_ONE: &str = "value is out of range, value must between 0 and 1";
67/// And about a negative `numreplicas`.
68const NOT_POSITIVE: &str = "value is out of range, must be positive";
69/// And what it says when asked to wait for a file the server does not keep. The
70/// full stop at the end is Redis's and is the one message in the group that has
71/// one, which is why it is worth writing down rather than tidying up.
72const NO_AOF: &str = "WAITAOF cannot be used when numlocal is set but appendonly is disabled.";
73
74/// Run one blocking command.
75///
76/// `Flow::Block` means nothing was written and the client is on the waiter
77/// list. The engine is what knows which socket that client is on, so it is the
78/// engine that finishes the registration and the engine that stops reading
79/// commands from a connection that is now waiting for one.
80///
81/// # Errors
82///
83/// A timeout that is not a timeout, a direction that is not a direction, and a
84/// key holding something that is not a list.
85pub(super) fn execute(
86    server: &mut Server,
87    session: &Session,
88    spec: &Spec,
89    args: Args<'_>,
90    out: &mut Out,
91) -> Result<Flow> {
92    // The two that wait on replication rather than on a key. They are here
93    // because they carry the blocking flag and that flag is what routes a
94    // command to this file, and they leave immediately because there is nothing
95    // for them to wait for yet. See [`replication`] for what they answer.
96    if spec.name == "wait" || spec.name == "waitaof" {
97        return replication(spec.name, args, out).map(|()| Flow::Continue);
98    }
99    let now = server.now_ms();
100    // The two stream reads, which are here for the same reason the list six
101    // are and leave through a different door. `BLOCK` is optional on both, so
102    // where `BLPOP` always has a timeout to read, `XREAD` may have been told to
103    // answer now and take nothing for an answer. That is the difference between
104    // parking and writing the null, and it cannot be said with a deadline of
105    // `None`, which already means wait for as long as it takes.
106    if spec.name == "xread" || spec.name == "xreadgroup" {
107        let db = session.db();
108        let want = streams::parse_read(spec.name, args, server.striped(db), now)?;
109        let block = Block::xread(want.keys, want.reads);
110        if block.now(server.striped(db), now, out)? {
111            return Ok(Flow::Continue);
112        }
113        let Some(deadline) = want.wait else {
114            // No `BLOCK` at all, so nothing arriving is the answer and not a
115            // reason to wait for it. A null array on both protocols, which is
116            // also what a `BLOCK` that runs out sends.
117            out.nil_array();
118            return Ok(Flow::Continue);
119        };
120        server.park(session.id(), db, deadline, block);
121        return Ok(Flow::Block);
122    }
123    let last = args.len() - 1;
124    let (deadline, block) = match spec.name {
125        // The keys are everything between the name and the timeout, so `BLPOP a
126        // b c 0` waits on three keys and answers with whichever one arrives
127        // first rather than with the first one named.
128        "blpop" | "brpop" => {
129            let end = if spec.name == "blpop" {
130                End::Left
131            } else {
132                End::Right
133            };
134            let deadline = timeout(args.get(last), now)?;
135            (deadline, Block::pop((1..last).map(|i| args.get(i)), end))
136        }
137        // The directions before the timeout, which is the order Redis checks
138        // them in, so `BLMOVE a b UP DOWN nonsense` is a syntax error and not a
139        // complaint about the timeout.
140        "blmove" => {
141            let (from, to) = (end_of(args.get(3))?, end_of(args.get(4))?);
142            let deadline = timeout(args.get(5), now)?;
143            (deadline, Block::moved(args.get(1), args.get(2), from, to))
144        }
145        // The same order again with one more thing to read: ends, then timeout,
146        // then the options behind it. `BLMOVEM s d UP DOWN abc` complains about
147        // the directions and `BLMOVEM s d LEFT RIGHT abc COUNT abc BULK` about
148        // the timeout, both measured against 8.10.1 rather than assumed, because
149        // a line wrong in two places has exactly one right answer.
150        "blmovem" => {
151            let (from, to) = (end_of(args.get(3))?, end_of(args.get(4))?);
152            let deadline = timeout(args.get(5), now)?;
153            let mv = movem_options(args, 6, from, to)?;
154            (deadline, Block::movem(args.get(1), args.get(2), mv))
155        }
156        "brpoplpush" => {
157            let deadline = timeout(args.get(3), now)?;
158            (
159                deadline,
160                Block::moved(args.get(1), args.get(2), End::Right, End::Left),
161            )
162        }
163        "blmpop" => mpop(args, now)?,
164        // The sorted set three, which are the same three shapes again with a
165        // different collection under them. `BZPOPMIN` reads its keys up to the
166        // timeout the way `BLPOP` does, and `BZMPOP` counts them the way
167        // `BLMPOP` does.
168        "bzpopmin" | "bzpopmax" => {
169            let end = zsets::end_of_name(spec.name);
170            let deadline = timeout(args.get(last), now)?;
171            (deadline, Block::zpop((1..last).map(|i| args.get(i)), end))
172        }
173        "bzmpop" => {
174            let deadline = timeout(args.get(1), now)?;
175            let (end, from, to, count) = zsets::parse_mpop(args, 2)?;
176            (
177                deadline,
178                Block::zmpop((from..to).map(|i| args.get(i)), end, count),
179            )
180        }
181        // The table and this match are checked against each other by
182        // `cargo xtask check`, so a name reaching here is a table row without a
183        // handler and there is nothing sensible to answer.
184        _ => return Err(args::syntax()),
185    };
186
187    let db = session.db();
188    if block.now(server.striped(db), now, out)? {
189        return Ok(Flow::Continue);
190    }
191    server.park(session.id(), db, deadline, block);
192    Ok(Flow::Block)
193}
194
195/// `BLMPOP timeout numkeys key [key ...] LEFT|RIGHT [COUNT count]`.
196///
197/// The same parse as `LMPOP` shifted along by one, including the check that the
198/// key count leaves room for the direction behind it. `BLMPOP 0 2 k LEFT` names
199/// two keys and only gives one, so the word that should have been the direction
200/// is a key and there is no direction left, which Redis calls a syntax error
201/// rather than anything about counts.
202fn mpop(args: Args<'_>, now: u64) -> Result<(Option<u64>, Block)> {
203    let deadline = timeout(args.get(1), now)?;
204    let numkeys = match args.int(2) {
205        Ok(n) if n > 0 => usize::try_from(n).unwrap_or(usize::MAX),
206        _ => return Err(Error::new(Code::Invalid, BAD_NUMKEYS)),
207    };
208    if numkeys >= args.len() - 3 {
209        return Err(args::syntax());
210    }
211    let at = 3 + numkeys;
212    let end = end_of(args.get(at))?;
213    let mut want = 1usize;
214    if at + 1 < args.len() {
215        if args.len() != at + 3 || !args::is(args.get(at + 1), b"count") {
216            return Err(args::syntax());
217        }
218        want = match args.int(at + 2) {
219            Ok(n) if n > 0 => usize::try_from(n).unwrap_or(usize::MAX),
220            _ => return Err(Error::new(Code::Invalid, BAD_MPOP_COUNT)),
221        };
222    }
223    Ok((
224        deadline,
225        Block::mpop((3..at).map(|i| args.get(i)), end, want),
226    ))
227}
228
229/// `WAIT numreplicas timeout` and `WAITAOF numlocal numreplicas timeout`.
230///
231/// Both of them ask the same question, which is whether this connection's writes
232/// have got somewhere durable, and both of them answer zero here. There are no
233/// replicas because there is no replication, and there is no append only file
234/// because `appendonly` is fixed at `no`, so nothing can ever move either count
235/// off zero and there is nothing to wait for. Redis in the same state gives the
236/// same numbers, it just takes the timeout to do it, and that is registered as
237/// D-25.
238///
239/// What is not a formality is the argument checking, because that is what a
240/// client sees when it gets something wrong, and the three numbers are read by
241/// three different Redis helpers with three different complaints. `numlocal` is
242/// a range and says so. `numreplicas` is a positive number for `WAITAOF` and any
243/// number at all for `WAIT`, where a negative one is accepted and satisfied on
244/// the spot because zero replicas is already more than it asked for. The timeout
245/// is milliseconds here and not the seconds the list commands take, so it does
246/// not go through [`timeout`] above, and a negative one is refused with its own
247/// message rather than the range one.
248fn replication(name: &str, args: Args<'_>, out: &mut Out) -> Result<()> {
249    let aof = name == "waitaof";
250    // `WAITAOF` has one number in front of the two `WAIT` has, and everything
251    // after it is in the same place, so the offset is the whole difference.
252    let at = usize::from(aof);
253    let mut wants_local = false;
254    if aof {
255        let local = whole(args.get(1))?;
256        if !(0..=1).contains(&local) {
257            return Err(Error::new(Code::Invalid, NOT_ZERO_OR_ONE));
258        }
259        wants_local = local == 1;
260    }
261    let replicas = whole(args.get(at + 1))?;
262    if aof && replicas < 0 {
263        return Err(Error::new(Code::Invalid, NOT_POSITIVE));
264    }
265    let ms = whole(args.get(at + 2)).map_err(|_| Error::new(Code::Invalid, TIMEOUT_NOT_AN_INT))?;
266    if ms < 0 {
267        return Err(Error::new(Code::Invalid, NEGATIVE));
268    }
269    // The one complaint here that is about the server rather than about the
270    // arguments, and the reason it comes last is that Redis reads all three
271    // arguments before it looks at itself. `appendonly` is `no` here and cannot
272    // be set, so asking to wait for a local copy is asking for something that
273    // cannot happen rather than something that has not happened yet.
274    if wants_local {
275        return Err(Error::new(Code::Invalid, NO_AOF));
276    }
277    if aof {
278        // Two integers and not a map, whichever protocol is in use. The local
279        // count is first and it is zero for the same reason the other one is:
280        // this server has no append only file to be behind.
281        out.array(2);
282        out.int(0);
283        out.int(0);
284    } else {
285        out.int(0);
286    }
287    Ok(())
288}
289
290/// A whole number argument, with the message Redis gives when it is not one.
291fn whole(arg: &[u8]) -> Result<i64> {
292    num::parse_i64(arg).ok_or_else(|| Error::new(Code::Invalid, NOT_AN_INT))
293}
294
295/// The moment to give up at, or `None` for a wait with no end to it.
296///
297/// Seconds as a float on the wire and a millisecond deadline here. Redis reads
298/// it as a long double, refuses a negative one, multiplies by a thousand and
299/// refuses what will not fit in an `i64`, and treats a timeout of exactly zero
300/// as no timeout at all. All four of those are visible from a client:
301///
302/// - `-0.0` is not negative, so it is accepted, and it is zero, so it waits
303///   forever. `-0.1` is refused.
304/// - `1e400` and `inf` parse, so they are not the not-a-float error, and both
305///   are further away than an `i64` of milliseconds reaches, so they are the out
306///   of range one.
307/// - `0.0000001` is a real timeout however small, so it expires on the next turn
308///   of the loop rather than waiting for anything.
309fn timeout(arg: &[u8], now: u64) -> Result<Option<u64>> {
310    let Some(secs) = num::parse_f64(arg) else {
311        return Err(Error::new(Code::Invalid, NOT_A_FLOAT));
312    };
313    if secs < 0.0 {
314        return Err(Error::new(Code::Invalid, NEGATIVE));
315    }
316    let ms = secs * 1000.0;
317    // `>` rather than a negated `<=`, and the two are not the same: an infinite
318    // timeout is greater than the bound and lands here, while a NaN would be
319    // neither, which is why the parse refuses one before this line is reached.
320    if ms > i64::MAX as f64 {
321        return Err(Error::new(Code::Invalid, OUT_OF_RANGE));
322    }
323    if ms <= 0.0 {
324        return Ok(None);
325    }
326    Ok(Some(now.saturating_add(ms as u64)))
327}
328
329/// What a parked client is still trying to do.
330enum Want {
331    /// `BLPOP` and `BRPOP`: one element off the first key that has one, with the
332    /// reply saying which key that turned out to be.
333    Pop { end: End },
334    /// `BLMOVE` and `BRPOPLPUSH`: one element, onto an end of another list.
335    Move { dst: Vec<u8>, from: End, to: End },
336    /// `BLMOVEM`: a block of them, onto an end of another list.
337    ///
338    /// The only want in this file where how many elements are there decides
339    /// whether the client is ready, rather than just whether any are. `COUNT`
340    /// takes what has arrived and so wakes on the first push, and `EXACTLY`
341    /// waits until the source actually holds the whole block.
342    MoveM { dst: Vec<u8>, mv: Movem },
343    /// `BLMPOP`: up to `count` elements off the first key that has any.
344    Mpop { end: End, count: usize },
345    /// `BZPOPMIN` and `BZPOPMAX`: one member and its score off the first sorted
346    /// set that has one, with the reply saying which key that turned out to be.
347    ZPop { end: ZEnd },
348    /// `BZMPOP`: up to `count` members off the first sorted set that has any.
349    ZMpop { end: ZEnd, count: usize },
350    /// `XREAD BLOCK` and `XREADGROUP BLOCK`: whatever has arrived on any of the
351    /// streams since the ID this asked from.
352    ///
353    /// Unlike the other six this takes nothing away, so several clients parked
354    /// on one stream all get the same entry rather than one of them getting it.
355    /// That is the whole point of a stream over a list, and it costs nothing
356    /// here because the attempt is a read.
357    XRead(streams::Reads),
358}
359
360impl Want {
361    /// Try to do it now.
362    ///
363    /// `Ok(true)` means a reply was written and the client is finished with.
364    /// `Ok(false)` means there was nothing to take and nothing was written.
365    ///
366    /// `strict` is the difference between the two callers. The command handler
367    /// passes `true`, so `BLPOP string 0` is a `WRONGTYPE` on the spot the way
368    /// `LPOP string` is. The retry passes `false`, so a key somebody has since
369    /// made into a set is skipped rather than turned into an error on a command
370    /// that was accepted seconds ago. That is what a running Redis does: a
371    /// `SADD` to a key a client is blocked on leaves it blocked, and it times
372    /// out in its own time.
373    ///
374    /// # Errors
375    ///
376    /// Whatever the keyspace says, which under `strict` includes a key of
377    /// another type.
378    fn attempt(
379        &self,
380        keys: &[Vec<u8>],
381        db: &Db,
382        now: u64,
383        out: &mut Out,
384        strict: bool,
385    ) -> Result<bool> {
386        match self {
387            // The one arm that needs to know what time it is, because a group
388            // read records when each entry was handed out. The other six take
389            // an element off a collection and the clock does not come into it.
390            Want::XRead(r) => streams::read(db, keys, r, now, strict, out),
391            Want::Pop { end } => {
392                for key in keys {
393                    if !ready(db, key, strict)? {
394                        continue;
395                    }
396                    out.array(2);
397                    out.bulk(key);
398                    db.hold(key).pop_into(key, *end, 1, |e| element(out, e))?;
399                    return Ok(true);
400                }
401                Ok(false)
402            }
403            Want::Mpop { end, count } => {
404                for key in keys {
405                    if !ready(db, key, strict)? {
406                        continue;
407                    }
408                    out.array(2);
409                    out.bulk(key);
410                    let mark = out.len();
411                    let n = db
412                        .hold(key)
413                        .pop_into(key, *end, *count, |e| element(out, e))?;
414                    out.close_array(mark, n);
415                    return Ok(true);
416                }
417                Ok(false)
418            }
419            // Three elements and not two, because `BZPOPMIN` puts the key, the
420            // member and the score side by side rather than pairing the last
421            // two. That is Redis's shape and it is not the shape `ZPOPMIN` has.
422            Want::ZPop { end } => {
423                for key in keys {
424                    if !zready(db, key, strict)? {
425                        continue;
426                    }
427                    out.array(3);
428                    out.bulk(key);
429                    db.hold(key).zpop(key, *end, 1, |m, sc| {
430                        member(out, m);
431                        out.double(sc);
432                    })?;
433                    return Ok(true);
434                }
435                Ok(false)
436            }
437            Want::ZMpop { end, count } => {
438                for key in keys {
439                    if !zready(db, key, strict)? {
440                        continue;
441                    }
442                    out.array(2);
443                    out.bulk(key);
444                    let mark = out.len();
445                    let n = db.hold(key).zpop(key, *end, *count, |m, sc| {
446                        out.array(2);
447                        member(out, m);
448                        out.double(sc);
449                    })?;
450                    out.close_array(mark, n);
451                    return Ok(true);
452                }
453                Ok(false)
454            }
455            // The source's length first, so that an empty source never reaches
456            // the destination's type check. `BLMOVE empty string LEFT RIGHT 0.1`
457            // times out on a running Redis rather than answering `WRONGTYPE`,
458            // because the destination is only looked at once there is something
459            // to put in it, and this order gives that answer.
460            Want::Move { dst, from, to } => {
461                let src = &keys[0];
462                if !ready(db, src, strict)? {
463                    return Ok(false);
464                }
465                match db.lmove(src, dst, *from, *to, |v| out.bulk(v)) {
466                    Ok(true) => Ok(true),
467                    // The source had something in it a line ago and this is the
468                    // only thread that could have taken it.
469                    Ok(false) => Ok(false),
470                    Err(e) if strict => Err(e),
471                    // The destination is not a list any more. Nothing was taken,
472                    // because `lmove` checks the destination before it pops, so
473                    // the client goes back to waiting with the queue as it was.
474                    Err(_) => Ok(false),
475                }
476            }
477            // The same shape as `Move` with a different question about the
478            // source. `ready` asks whether there is anything and that is not
479            // enough here, because an `EXACTLY` client is not ready until the
480            // whole block has arrived, and asking it any earlier would take
481            // nothing and answer nothing while looking like it had tried.
482            Want::MoveM { dst, mv } => {
483                let src = &keys[0];
484                let have = match db.hold(src).llen(src) {
485                    Ok(n) => n,
486                    Err(e) if strict => return Err(e),
487                    Err(_) => return Ok(false),
488                };
489                // Not ready is not the same as nothing to do, so the
490                // destination is never looked at from here. `BLMOVEM empty
491                // string LEFT RIGHT 0.1` times out on a running 8.10.1 rather
492                // than answering `WRONGTYPE`, and so does an `EXACTLY` whose
493                // source is short, both of which were measured.
494                if have == 0 || (mv.exactly && have < mv.count) {
495                    return Ok(false);
496                }
497                let mark = out.len();
498                let mut n = 0;
499                match db.lmovem(src, dst, *mv, |v| {
500                    out.bulk(v);
501                    n += 1;
502                }) {
503                    Ok(_) => {}
504                    Err(e) if strict => return Err(e),
505                    // As `Move`: the destination stopped being a list while
506                    // this client waited, and nothing was taken.
507                    Err(_) => {
508                        out.truncate(mark);
509                        return Ok(false);
510                    }
511                }
512                out.close_array(mark, n);
513                Ok(true)
514            }
515        }
516    }
517}
518
519/// Whether this key is a list with something in it.
520///
521/// A key of the wrong type is an error to the command handler and not one to the
522/// retry, which is the whole of what `strict` decides.
523fn ready(db: &Db, key: &[u8], strict: bool) -> Result<bool> {
524    match db.hold(key).llen(key) {
525        Ok(n) => Ok(n > 0),
526        Err(e) if strict => Err(e),
527        Err(_) => Ok(false),
528    }
529}
530
531/// The same for a sorted set, which has its own emptiness to ask about.
532fn zready(db: &Db, key: &[u8], strict: bool) -> Result<bool> {
533    match db.hold(key).zcard(key) {
534        Ok(n) => Ok(n > 0),
535        Err(e) if strict => Err(e),
536        Err(_) => Ok(false),
537    }
538}
539
540/// One element as the client sees it, the same as [`super::lists`] writes it.
541#[inline]
542fn element(out: &mut Out, e: Entry<'_>) {
543    match e {
544        Entry::Int(n) => out.bulk_int(n),
545        Entry::Str(s) => out.bulk(s),
546    }
547}
548
549/// One member as the client sees it, the same as [`super::zsets`] writes it.
550#[inline]
551fn member(out: &mut Out, m: Member<'_>) {
552    match m {
553        Member::Int(n) => out.bulk_int(n),
554        Member::Str(s) => out.bulk(s),
555    }
556}
557
558/// A parsed blocking command, ready to be tried or to be parked.
559pub struct Block {
560    /// The keys, already copied out of the connection's read buffer.
561    ///
562    /// This is the allocation blocking costs and it is once per block rather
563    /// than once per attempt. The arguments are slices of a buffer that is
564    /// reused as soon as the batch is over, and a waiter outlives the batch.
565    keys: Vec<Vec<u8>>,
566    want: Want,
567}
568
569impl Block {
570    /// `BLPOP` and `BRPOP`.
571    fn pop<'a>(keys: impl Iterator<Item = &'a [u8]>, end: End) -> Block {
572        Block {
573            keys: owned(keys),
574            want: Want::Pop { end },
575        }
576    }
577
578    /// `BLMPOP`.
579    fn mpop<'a>(keys: impl Iterator<Item = &'a [u8]>, end: End, count: usize) -> Block {
580        Block {
581            keys: owned(keys),
582            want: Want::Mpop { end, count },
583        }
584    }
585
586    /// `BZPOPMIN` and `BZPOPMAX`.
587    fn zpop<'a>(keys: impl Iterator<Item = &'a [u8]>, end: ZEnd) -> Block {
588        Block {
589            keys: owned(keys),
590            want: Want::ZPop { end },
591        }
592    }
593
594    /// `BZMPOP`.
595    fn zmpop<'a>(keys: impl Iterator<Item = &'a [u8]>, end: ZEnd, count: usize) -> Block {
596        Block {
597            keys: owned(keys),
598            want: Want::ZMpop { end, count },
599        }
600    }
601
602    /// `BLMOVE` and `BRPOPLPUSH`.
603    fn moved(src: &[u8], dst: &[u8], from: End, to: End) -> Block {
604        yo_alloc::allow(|| Block {
605            keys: vec![src.to_vec()],
606            want: Want::Move {
607                dst: dst.to_vec(),
608                from,
609                to,
610            },
611        })
612    }
613
614    /// `BLMOVEM`.
615    fn movem(src: &[u8], dst: &[u8], mv: Movem) -> Block {
616        yo_alloc::allow(|| Block {
617            keys: vec![src.to_vec()],
618            want: Want::MoveM {
619                dst: dst.to_vec(),
620                mv,
621            },
622        })
623    }
624
625    /// Do it now if it can be done now.
626    ///
627    /// # Errors
628    ///
629    /// A key of another type, which is an error rather than a wait.
630    fn now(&self, db: &Db, now: u64, out: &mut Out) -> Result<bool> {
631        self.want.attempt(&self.keys, db, now, out, true)
632    }
633
634    /// `XREAD BLOCK` and `XREADGROUP BLOCK`, whose keys and IDs were read
635    /// together by [`streams::parse_read`] because neither makes sense alone.
636    fn xread(keys: Vec<Vec<u8>>, reads: streams::Reads) -> Block {
637        Block {
638            keys,
639            want: Want::XRead(reads),
640        }
641    }
642}
643
644/// The keys a blocking command named, copied so they outlive the read buffer.
645fn owned<'a>(keys: impl Iterator<Item = &'a [u8]>) -> Vec<Vec<u8>> {
646    yo_alloc::allow(|| keys.map(<[u8]>::to_vec).collect())
647}
648
649/// One parked client.
650struct Waiter {
651    /// The client id, which is never reused.
652    client: u64,
653    /// The slot its reply buffer is on, which is.
654    conn: u32,
655    /// The database it was on when it blocked. A push into another database is
656    /// not this client's push, even when the key has the same name.
657    db: usize,
658    /// The millisecond to give up at, or `None` for `BLPOP key 0`, which waits
659    /// for as long as the connection is open.
660    deadline: Option<u64>,
661    keys: Vec<Vec<u8>>,
662    want: Want,
663}
664
665/// Every parked client, oldest first.
666///
667/// The order is the order they blocked in and it is the order they are served
668/// in, which is what makes a queue with several workers on it fair: two clients
669/// blocked on the same key take the two elements a `RPUSH q first second` adds
670/// in the order they arrived. A `Vec` is the right structure for that while the
671/// list is short, and it is short, because a waiter is a client doing nothing.
672#[derive(Default)]
673pub struct Waiters {
674    list: Vec<Waiter>,
675}
676
677/// Where the reply to a parked client has to go.
678#[derive(Debug, Clone, Copy)]
679pub struct Parked {
680    /// The slot holding its reply buffer.
681    pub conn: u32,
682    /// The client that was on that slot when it blocked.
683    pub client: u64,
684}
685
686impl Waiters {
687    /// Whether anybody is waiting.
688    #[must_use]
689    #[inline]
690    pub fn is_empty(&self) -> bool {
691        self.list.is_empty()
692    }
693
694    /// How many clients are parked, which is what `INFO clients` calls
695    /// `blocked_clients`.
696    #[must_use]
697    #[inline]
698    pub fn len(&self) -> usize {
699        self.list.len()
700    }
701
702    /// Where the waiter at `at` has to be answered.
703    ///
704    /// # Panics
705    ///
706    /// If `at` is past the end, which only a caller that ignored [`Waiters::len`]
707    /// can manage.
708    #[must_use]
709    pub fn at(&self, at: usize) -> Parked {
710        let w = &self.list[at];
711        Parked {
712            conn: w.conn,
713            client: w.client,
714        }
715    }
716
717    /// The database the waiter at `at` blocked on.
718    ///
719    /// # Panics
720    ///
721    /// As [`Waiters::at`].
722    #[must_use]
723    pub fn db_of(&self, at: usize) -> usize {
724        self.list[at].db
725    }
726
727    /// Take a waiter off the list.
728    ///
729    /// # Panics
730    ///
731    /// As [`Waiters::at`].
732    pub fn drop_at(&mut self, at: usize) {
733        self.list.remove(at);
734    }
735
736    /// Take off every waiter belonging to a client that has gone.
737    ///
738    /// Called when a connection closes rather than left for the deadline sweep
739    /// to find, because a `BLPOP key 0` on a connection nobody will ever write
740    /// to again has no deadline to be found by.
741    pub fn forget(&mut self, client: u64) {
742        self.list.retain(|w| w.client != client);
743    }
744
745    /// Say which slot the waiter this client just registered is answered on.
746    ///
747    /// The command layer knows which client blocked and the engine knows which
748    /// slot that client is on, so the slot is filled in afterwards by the half
749    /// that has it. A client can only be parked once, since it is not reading
750    /// commands while it waits, so the search finds the one that was just added.
751    pub fn bind(&mut self, client: u64, conn: u32) {
752        if let Some(w) = self.list.iter_mut().rev().find(|w| w.client == client) {
753            w.conn = conn;
754        }
755    }
756
757    /// Park a client that could not be answered.
758    ///
759    /// The slot is filled in by [`Waiters::bind`] once the engine has it, so
760    /// this leaves it at zero rather than pretending to know.
761    fn park(&mut self, client: u64, db: usize, deadline: Option<u64>, block: Block) {
762        yo_alloc::allow(|| {
763            self.list.push(Waiter {
764                client,
765                conn: 0,
766                db,
767                deadline,
768                keys: block.keys,
769                want: block.want,
770            });
771        });
772    }
773
774    /// Try to answer the waiter at `at`, and say whether it is finished with.
775    ///
776    /// `true` means a reply is in `out` and the caller should take the waiter
777    /// off the list, which covers both a client that got what it asked for and
778    /// one that ran out of time.
779    ///
780    /// The attempt comes before the deadline, so a push that landed in the same
781    /// millisecond the client gave up in serves it rather than racing it.
782    ///
783    /// # Panics
784    ///
785    /// As [`Waiters::at`].
786    fn try_serve(&self, at: usize, dbs: &[Db], now: u64, out: &mut Out) -> bool {
787        let w = &self.list[at];
788        let mark = out.len();
789        match w.want.attempt(&w.keys, &dbs[w.db], now, out, false) {
790            Ok(true) => return true,
791            Ok(false) => {}
792            // `strict` is off, so nothing in there returns an error today.
793            // Putting the buffer back is what makes it safe to be wrong about
794            // that later.
795            Err(_) => out.truncate(mark),
796        }
797        if w.deadline.is_some_and(|d| now >= d) {
798            // A null array for all six, `BLMOVE` and `BRPOPLPUSH` included,
799            // even though what they send when they succeed is a single element.
800            // That is Redis's and it is not what reading the reply schema would
801            // suggest: a RESP2 client sees `*-1` and not `$-1`.
802            out.nil_array();
803            return true;
804        }
805        false
806    }
807}
808
809impl Server {
810    /// The clock reading this batch is working against.
811    #[must_use]
812    pub fn now_ms(&self) -> u64 {
813        self.clock.now_ms()
814    }
815
816    /// Who is parked, for the engine and for `INFO`.
817    #[must_use]
818    pub const fn waiters(&self) -> &Waiters {
819        &self.waiters
820    }
821
822    /// The same, for the engine, which is what binds and forgets them.
823    pub const fn waiters_mut(&mut self) -> &mut Waiters {
824        &mut self.waiters
825    }
826
827    /// Park a client on a command that could not be answered yet.
828    pub(super) fn park(&mut self, client: u64, db: usize, deadline: Option<u64>, block: Block) {
829        self.waiters.park(client, db, deadline, block);
830    }
831
832    /// Try to answer the waiter at `at`, writing into the buffer the engine
833    /// found for it, and say whether it is finished with.
834    ///
835    /// The engine cannot reach the databases and this cannot reach the
836    /// connections, so the two meet here: the caller hands in one connection's
837    /// reply buffer and gets back whether to unpark the client behind it.
838    ///
839    /// # Panics
840    ///
841    /// If `at` is not a waiter.
842    pub fn serve_waiter(&mut self, at: usize, now: u64, out: &mut Out) -> bool {
843        // Serving a waiter pops an element, which makes garbage, and it happens
844        // outside `execute` so nothing else has marked the database for the
845        // maintenance turn.
846        self.dirty |= 1u64 << self.waiters.db_of(at);
847        self.waiters.try_serve(at, &self.dbs, now, out)
848    }
849}