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