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