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