Skip to main content

yo_resp/dispatch/
blocking.rs

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