Skip to main content

yo_resp/dispatch/
debug.rs

1//! `DEBUG`, the container a test suite talks to rather than a client.
2//!
3//! # What it is for
4//!
5//! Every other command here exists so that somebody can store something and get
6//! it back. This one exists so that somebody can make the server do a thing that
7//! would otherwise be impossible to arrange from the outside: send a reply of a
8//! type no ordinary command sends, stop sweeping expired keys, stop the clock
9//! work, fill a database with a hundred thousand keys without a hundred thousand
10//! round trips, or answer with an error whose text the caller chose.
11//!
12//! Redis's own test suite leans on it heavily, which is why it is here at all:
13//! most of the suite's `assert_encoding` and expiry tests do not run at all
14//! against a server that has no `DEBUG`.
15//!
16//! # Which subcommands are here
17//!
18//! A real server has around sixty and most of them are about parts that do not
19//! exist here: the AOF, cluster links, atomic slot migration, forking, crashing
20//! on purpose. What is here is the part that is about this server, and `DEBUG
21//! HELP` lists exactly that rather than listing what Redis has, for the same
22//! reason `CLIENT HELP` does: somebody reading it to find out what they can send
23//! should not be told about a subcommand that would come back unknown.
24//!
25//! The four knobs are the interesting ones, because a knob that is remembered
26//! and read by nothing is worse than no knob at all. Three of them really move
27//! something: `SET-ACTIVE-EXPIRE` gates the sweep that reclaims keys nobody asks
28//! for again, `PAUSE-CRON` gates the whole maintenance slice the shard loop runs
29//! between batches, and `SET-SKIP-CHECKSUM-VALIDATION` is read by the code that
30//! opens a `RESTORE` payload. `DICT-RESIZING` gates arena compaction, which is
31//! the nearest thing here to the dictionary resize it turns off on a real
32//! server: both are the background reclaim of room a table no longer needs. The
33//! one that is remembered and does nothing is
34//! `QUICKLIST-PACKED-THRESHOLD`, which is D-128.
35//!
36//! `RELOAD` is the odd one out, because it moves the whole dataset rather than a
37//! knob. It is here for the same reason the knobs are: the suite calls it after
38//! almost every case, and a case that passes on both sides of it has proved that
39//! the writer and the reader of the file agree about the value it just made.
40//!
41//! Then there are the four that only look: `OBJECT`, `SDSLEN`, `LISTPACK` and
42//! `QUICKLIST`. None of them change anything and none of them count as a use of
43//! the key they are about, which is the property that makes them worth having at
44//! all. A suite that wants to know how big a value really is, or how many nodes
45//! a list broke into, has nowhere else to ask, because everything on the ordinary
46//! command surface answers about the value rather than about the way it is
47//! written down.
48//!
49//! `DIGEST` and `DIGEST-VALUE` only look as well, and they are the pair the
50//! suite leans on hardest. One number for a whole server, another for one value,
51//! and the whole worth of both is that another server computes them the same
52//! way, so the recipe in [`yo_kv::digest`] is copied to the byte. Everything the
53//! suite checks after a reload, a replica catching up or a rewrite comes down to
54//! holding two of these next to each other.
55//!
56//! # How the errors work
57//!
58//! Every complaint in this file is the same sentence, `unknown subcommand or
59//! wrong number of arguments for '<what was sent>'. Try DEBUG HELP.`, and that
60//! is not a shortcut. A real server's `DEBUG` is a chain of `strcasecmp` tests
61//! each of which also checks `argc`, and anything that falls off the end of the
62//! chain gets that one line, so a subcommand that does not exist and a
63//! subcommand handed the wrong number of arguments are the same case. The name
64//! is echoed in the case it was sent in.
65//!
66//! The two exceptions are the two subcommands that read their argument and can
67//! fail on the value rather than on the count, which are
68//! `QUICKLIST-PACKED-THRESHOLD` and `POPULATE`, and each has its own sentence.
69
70use core::fmt::Write as _;
71use std::sync::atomic::AtomicU64;
72use std::sync::atomic::Ordering::Relaxed;
73
74use yo_common::num::parse_i64;
75use yo_common::{Code, Error, Result};
76use yo_kv::{SetOptions, digest, lookups};
77
78use super::args::{self, Args, is};
79use super::{Server, Session, persist};
80use crate::reply::Out;
81
82/// The knobs `DEBUG` turns, all of them on a word each.
83///
84/// One word rather than a lock because the readers are the shard loop's
85/// maintenance slice and the payload reader, which is to say the hottest places
86/// that could possibly read a debugging flag, and the writer is a human at a
87/// test suite. The three gates are stored as their `true` meaning, so a default
88/// `Knobs` is a server with everything running.
89#[derive(Debug)]
90pub(crate) struct Knobs {
91    /// Whether the expiry sweep runs, which `SET-ACTIVE-EXPIRE 0` turns off.
92    expiring: AtomicU64,
93    /// Whether the maintenance slice runs at all, which `PAUSE-CRON 1` stops.
94    cron: AtomicU64,
95    /// Whether arena compaction runs, which `DICT-RESIZING 0` stops.
96    resizing: AtomicU64,
97    /// The packed node threshold, which nothing here reads. See D-128.
98    packed: AtomicU64,
99}
100
101impl Default for Knobs {
102    fn default() -> Knobs {
103        Knobs {
104            expiring: AtomicU64::new(1),
105            cron: AtomicU64::new(1),
106            resizing: AtomicU64::new(1),
107            packed: AtomicU64::new(DEFAULT_PACKED),
108        }
109    }
110}
111
112/// What the packed threshold goes back to when it is set to nought, which is a
113/// gigabyte and is Redis's default.
114const DEFAULT_PACKED: u64 = 1 << 30;
115
116/// The largest packed threshold that is taken, which is four gigabytes less a
117/// megabyte.
118///
119/// Redis's `quicklistSetPackedThreshold` refuses anything above this, with a
120/// comment saying it will not allow the threshold even slightly below four
121/// gigabytes. The error text says bigger than one and smaller than 4gb, and
122/// neither half of that sentence is quite what the code checks, since one is
123/// taken and `4294967295` is not.
124const MAX_PACKED: u64 = (1 << 32) - (1 << 20);
125
126impl Server {
127    /// Whether the expiry sweep should run.
128    #[must_use]
129    pub(crate) fn expiring(&self) -> bool {
130        self.debug.expiring.load(Relaxed) != 0
131    }
132
133    /// Whether the maintenance slice should run at all.
134    #[must_use]
135    pub fn cron_running(&self) -> bool {
136        self.debug.cron.load(Relaxed) != 0
137    }
138
139    /// Whether arena compaction should run.
140    #[must_use]
141    pub(crate) fn resizing(&self) -> bool {
142        self.debug.resizing.load(Relaxed) != 0
143    }
144}
145
146/// `DEBUG <subcommand> [...]`.
147pub(super) fn execute(
148    server: &Server,
149    session: &mut Session,
150    args: Args<'_>,
151    out: &mut Out,
152) -> Result<()> {
153    let sub = args.get(1);
154    if is(sub, b"HELP") && args.len() == 2 {
155        super::server::help(out, HELP);
156    } else if is(sub, b"PROTOCOL") && args.len() == 3 {
157        return protocol(args.get(2), out);
158    } else if is(sub, b"ERROR") && args.len() == 3 {
159        // Straight out, with no code in front of it and no checking of what is
160        // in it beyond the newlines, because the whole point is to hand a client
161        // library an error line it chose. The empty prefix is there because this
162        // is the one error line the server did not write any of, and the newline
163        // folding that comes with it is what a real server does too and is what
164        // stops this from being a way to write two replies with one command.
165        out.error_line(b"", args.get(2));
166    } else if is(sub, b"LOG") && args.len() == 3 {
167        // The server log is stderr here, which is what the service file or the
168        // shell redirection points wherever the operator wants it.
169        yo_alloc::allow(|| {
170            eprintln!("yodb: DEBUG LOG: {}", String::from_utf8_lossy(args.get(2)));
171        });
172        out.ok();
173    } else if is(sub, b"SLEEP") && args.len() == 3 {
174        sleep(args.get(2));
175        out.ok();
176    } else if is(sub, b"POPULATE") && (3..=5).contains(&args.len()) {
177        return populate(server, session, args, out);
178    } else if is(sub, b"SET-ACTIVE-EXPIRE") && args.len() == 3 {
179        server.debug.expiring.store(flag(args.get(2)), Relaxed);
180        out.ok();
181    } else if is(sub, b"PAUSE-CRON") && args.len() == 3 {
182        // The one gate that is stored the other way up from how it is written,
183        // because the subcommand names the stopping and the field names the
184        // running.
185        server.debug.cron.store(1 - flag(args.get(2)), Relaxed);
186        out.ok();
187    } else if is(sub, b"DICT-RESIZING") && args.len() == 3 {
188        server.debug.resizing.store(flag(args.get(2)), Relaxed);
189        out.ok();
190    } else if is(sub, b"SET-SKIP-CHECKSUM-VALIDATION") && args.len() == 3 {
191        yo_kv::rdb::skip_checksums(flag(args.get(2)) != 0);
192        out.ok();
193    } else if is(sub, b"QUICKLIST-PACKED-THRESHOLD") && args.len() == 3 {
194        return packed(server, args.get(2), out);
195    } else if is(sub, b"RELOAD") {
196        return reload(server, args, out);
197    } else if is(sub, b"OBJECT") && args.len() == 3 {
198        return object(server, session, args.get(2), out);
199    } else if is(sub, b"SDSLEN") && args.len() == 3 {
200        return sdslen(server, session, args.get(2), out);
201    } else if is(sub, b"LISTPACK") && args.len() == 3 {
202        return packing(server, session, args.get(2), Packing::Listpack, out);
203    } else if is(sub, b"QUICKLIST") && (3..=4).contains(&args.len()) {
204        return packing(server, session, args.get(2), Packing::Quicklist, out);
205    } else if is(sub, b"MARK-INTERNAL-CLIENT")
206        && (args.len() == 2 || (args.len() == 3 && is(args.get(2), b"UNMARK")))
207    {
208        // The one way to become a node of the cluster without being one, which
209        // is what a test uses to drive the slot migration protocol by hand
210        // rather than standing up a second server.
211        session.serve_internal(args.len() == 2);
212        out.ok();
213    } else if is(sub, b"INTERNAL_SECRET") && args.len() == 2 {
214        // The secret itself never leaves the server, which is the point of it,
215        // so what comes back is a checksum of it. That is enough for a test to
216        // see that two nodes have converged on the same one and no use at all to
217        // anybody trying to log in with it.
218        let secret = server.cluster_secret();
219        if secret.is_empty() {
220            return Err(Error::new(Code::Invalid, "Internal secret is missing"));
221        }
222        out.int(i64::from(yo_common::crc::crc16(secret.as_bytes())));
223    } else if is(sub, b"CHANGE-REPL-ID") && args.len() == 2 {
224        server.change_id();
225        out.ok();
226    } else if is(sub, b"DIGEST") && args.len() == 2 {
227        whole_digest(server, out);
228    } else if is(sub, b"DIGEST-VALUE") {
229        value_digests(server, session, args, out);
230    } else {
231        return Err(args::subcommand_syntax(sub, "DEBUG"));
232    }
233    Ok(())
234}
235
236/// A `0` or `1` argument, read the way C reads one.
237///
238/// Which is `atoi`, so anything that is not a number at all is nought and the
239/// gate goes off. That is worth reproducing rather than tidying up, because a
240/// test suite that sends `DEBUG SET-ACTIVE-EXPIRE no` gets a server with the
241/// sweep turned off on a real server and would get one with it left on here if
242/// this refused what it could not read.
243fn flag(value: &[u8]) -> u64 {
244    let value = value.strip_prefix(b"-").unwrap_or(value);
245    let digits = value
246        .iter()
247        .take_while(|b| b.is_ascii_digit())
248        .fold(0u64, |n, b| {
249            n.saturating_mul(10).saturating_add(u64::from(b - b'0'))
250        });
251    u64::from(digits != 0)
252}
253
254/// `DEBUG SLEEP <seconds>`, which stops this thread where it stands.
255///
256/// Decimals allowed and read with C's `strtod`, so a word is nought seconds and
257/// a negative number is nought seconds, and both answer `OK` at once. There is
258/// no upper bound, which is the point: a suite that wants a server that does not
259/// answer for ten seconds asks for ten seconds.
260///
261/// On a server with one shard thread, which is the default, this is the whole
262/// server, which is what it is on Redis. Above one thread it is the thread this
263/// connection landed on and the others keep answering, which is D-129.
264fn sleep(value: &[u8]) {
265    let text = core::str::from_utf8(value).unwrap_or("");
266    let seconds = leading_double(text);
267    if seconds > 0.0 {
268        std::thread::sleep(std::time::Duration::from_secs_f64(seconds));
269    }
270}
271
272/// As much of the front of `text` as reads as a double, or nought.
273///
274/// `strtod` takes the longest prefix that is a number and stops, so `1.5s` is a
275/// second and a half and `abc` is nothing. Rust's parser wants the whole string,
276/// so the prefix is found here.
277fn leading_double(text: &str) -> f64 {
278    let mut end = 0;
279    for (at, _) in text.char_indices() {
280        if text[..=at].parse::<f64>().is_ok() {
281            end = at + 1;
282        }
283    }
284    text[..end].parse().unwrap_or(0.0)
285}
286
287/// `DEBUG QUICKLIST-PACKED-THRESHOLD <size>`.
288fn packed(server: &Server, value: &[u8], out: &mut Out) -> Result<()> {
289    let size = super::server::parse_memory(value).filter(|&n| n <= MAX_PACKED);
290    let Some(size) = size else {
291        return Err(Error::new(
292            Code::Invalid,
293            "argument must be a memory value bigger than 1 and smaller than 4gb",
294        ));
295    };
296    // Nought is not a threshold of nothing, it is the word for putting the
297    // default back, which is the one part of this subcommand that is not
298    // guessable from its name.
299    let size = if size == 0 { DEFAULT_PACKED } else { size };
300    server.debug.packed.store(size, Relaxed);
301    out.ok();
302    Ok(())
303}
304
305/// What a reload says when the file did not come back.
306///
307/// One sentence for every way it can go wrong, which is the reference's answer
308/// too. A client can do nothing with the difference between a bad checksum and a
309/// file that stops halfway, and whoever can is reading the log, so that is where
310/// the reason goes.
311const LOAD_FAILED: &str = "Error trying to load the RDB dump, check server logs.";
312
313/// `DEBUG RELOAD [MERGE] [NOFLUSH] [NOSAVE]`, the round trip a suite leans on.
314///
315/// Write the whole dataset out as an RDB, throw away what is in memory and build
316/// it again out of the file. It is here because Redis's own suite calls it after
317/// almost every case: a value that comes back the same way it went in has proved
318/// its writer and its reader agree, and a value that does not has found a bug in
319/// one of them without anybody having to say which.
320///
321/// The three options are the reference's three. `NOSAVE` skips the write and
322/// reads whatever file is already on disk, which is how a suite loads a file it
323/// put there itself. `NOFLUSH` keeps what is in memory and lets the file land on
324/// top of it. `MERGE` is read and changes nothing here, and that is D-131: on a
325/// real server it is what makes a key that is in the file and in memory legal,
326/// and without it the server takes itself down with `Duplicated key found in RDB
327/// file`. A key arriving over one that is already there is an ordinary import
328/// here, so there is nothing for the word to turn on.
329///
330/// The other difference from a real server is the window. Redis forks for the
331/// save and has one thread for the load, so nothing can write in between. Here
332/// the save walks one stripe at a time and another connection can write to a
333/// stripe that has already been walked, which is D-132 and is the same window
334/// [`super::persist::build`] already has for `SAVE`.
335fn reload(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
336    let (mut save, mut flush) = (true, true);
337    for i in 2..args.len() {
338        let word = args.get(i);
339        if is(word, b"NOSAVE") {
340            save = false;
341        } else if is(word, b"NOFLUSH") {
342            flush = false;
343        } else if !is(word, b"MERGE") {
344            return Err(Error::new(
345                Code::Invalid,
346                "DEBUG RELOAD only supports the MERGE, NOFLUSH and NOSAVE options.",
347            ));
348        }
349    }
350    if save {
351        if !persist::write_file(server) {
352            // The bare line `SAVE` answers, for the reason it gives.
353            out.error(b"ERR");
354            return Ok(());
355        }
356        // A key with no RDB shape is not in the file that was just written, so
357        // flushing and reading it back would be a way of deleting it. Nothing on
358        // a real server can be in this position, which is why the sentence is
359        // ours: the reply says which way out there is rather than leaving the
360        // caller to find out from a `DBSIZE` that came back short.
361        let lost = persist::skipped(server);
362        if flush && lost > 0 {
363            return Err(if lost == 1 {
364                Error::new(
365                    Code::Invalid,
366                    "DEBUG RELOAD would drop 1 key with no RDB form, use NOFLUSH to keep it",
367                )
368            } else {
369                Error::fmt(
370                    Code::Invalid,
371                    format_args!(
372                        "DEBUG RELOAD would drop {lost} keys with no RDB form, use NOFLUSH to keep them"
373                    ),
374                )
375            });
376        }
377    }
378    if yo_alloc::allow(|| load_file(server, flush)) {
379        out.ok();
380        Ok(())
381    } else {
382        Err(Error::new(Code::Invalid, LOAD_FAILED))
383    }
384}
385
386/// Read `dump.rdb` back over the keyspace, and say whether all of it landed.
387///
388/// The walk itself is [`Server::load_image`], which is shared with the restore
389/// the tool does at startup. What is here is the two things that are this
390/// command's own: the file it reads is always the one `SAVE` writes, and a
391/// reason it could not is a line in the log rather than a sentence to a client,
392/// for the reason [`LOAD_FAILED`] gives.
393///
394/// The libraries the file carries are counted and dropped rather than loaded.
395/// They are already here, because the flush above takes the databases and not
396/// the function registry, and loading a library that is already registered is an
397/// error rather than a no op.
398fn load_file(server: &Server, flush: bool) -> bool {
399    let path = server.dir().join(persist::FILE);
400    let image = match std::fs::read(&path) {
401        Ok(image) => image,
402        Err(e) => {
403            eprintln!("yodb: DEBUG RELOAD: {}: {e}", path.display());
404            return false;
405        }
406    };
407    match server.load_image(&image, flush) {
408        Ok(_) => true,
409        Err(refused) => {
410            eprintln!("yodb: DEBUG RELOAD: {refused}");
411            false
412        }
413    }
414}
415
416/// What every one of the inspection subcommands says about a key that is not
417/// there.
418///
419/// `OBJECT` is the odd one out among the key commands generally, since
420/// `OBJECT ENCODING` on a missing key is a nil rather than this. `DEBUG OBJECT`
421/// is not `OBJECT` and answers the error, which is checked rather than assumed.
422const NO_SUCH_KEY: &str = "no such key";
423
424/// The LRU clock is twenty four bits of seconds, and wraps every 194 days.
425///
426/// A real server keeps the same three bytes for the same reason it is worth
427/// keeping here: the field lives inside the object header next to the type and
428/// the encoding, and a client that reads it is comparing two of them rather than
429/// reading it as a date.
430const LRU_CLOCK_MAX: u64 = (1 << 24) - 1;
431
432/// What a `DUMP` payload carries that the value itself is not.
433///
434/// One type byte in front, then two bytes of RDB version and eight of checksum
435/// behind. `serializedlength` is the body between them, which is what
436/// `rdbSavedObjectLen` counts on a real server, so taking these off the payload
437/// this server already knows how to build is the whole of that number.
438const DUMP_AROUND: usize = 11;
439
440/// `DEBUG OBJECT <key>`, the low level line about one value.
441///
442/// Seven fields, or twelve for a quicklist. Three of them are about the value as
443/// bytes, which is the encoding, the serialized length and the quicklist shape,
444/// and those are the ones a person actually reads. The rest are about the object
445/// header a real server keeps: the address it is at, how many things point at it
446/// and when it was last touched.
447///
448/// `serializedlength` is the value's RDB body, without the type byte in front of
449/// it and without the version and checksum a `DUMP` puts behind it. That is
450/// `rdbSavedObjectLen` on a real server and it means the same thing here, so a
451/// value this build writes differently is a value with a different number, and
452/// the five list shapes D-111 already covers are the ones that differ.
453///
454/// `refcount` is one, always, for the reason `OBJECT REFCOUNT` gives. `at` is
455/// where the record sits rather than where an object header would, which is
456/// D-134: see [`yo_kv::keyspace::Keyspace::value_address`] for why that is the
457/// same answer to the question anybody asks it.
458///
459/// `lru` is the same clock `OBJECT IDLETIME` counts back from, so the two agree
460/// by construction: the clock now, less the seconds the key has been idle,
461/// wrapped into twenty four bits. Reading it is not using the key, so a second
462/// call answers a larger idle time and the same `lru`.
463fn object(server: &Server, session: &Session, key: &[u8], out: &mut Out) -> Result<()> {
464    let mut held = server.dbs[session.db].hold(key);
465    let Some(encoding) = held.encoding_name(key) else {
466        return Err(Error::new(Code::Invalid, NO_SUCH_KEY));
467    };
468    // The encoding above is the lookup this is counted for, and everything
469    // below asks about the same key again.
470    let _quiet = lookups::quiet();
471    let at = held.value_address(key).unwrap_or(0);
472    let idle = held.idle_secs(key).unwrap_or(0);
473    // A value with no RDB shape has no serialized length either, and nought is
474    // the honest answer rather than a refusal: the rest of the line is about
475    // the same value and is still true.
476    let serialized = held
477        .dump(key)
478        .map_or(0, |payload| payload.len() - DUMP_AROUND);
479    let quicklist = (encoding == "quicklist")
480        .then(|| held.list_shape(key))
481        .flatten()
482        .map(|(nodes, bytes)| {
483            // The average is elements over nodes, which is what the reference
484            // divides too, and both sides print it to two places.
485            let len = held.llen(key).unwrap_or(0);
486            let fill = list_fill(&held.bands().list);
487            (nodes, len as f64 / nodes.max(1) as f64, fill, bytes)
488        });
489    drop(held);
490
491    let now = server.clock.now_ms() / 1_000;
492    let lru = now.saturating_sub(idle) & LRU_CLOCK_MAX;
493    let mut line = String::with_capacity(192);
494    yo_alloc::allow(|| {
495        let _ = write!(
496            line,
497            "Value at:{at:#x} refcount:1 encoding:{encoding} \
498             serializedlength:{serialized} lru:{lru} lru_seconds_idle:{idle}",
499        );
500        if let Some((nodes, avg, fill, bytes)) = quicklist {
501            let _ = write!(
502                line,
503                " ql_nodes:{nodes} ql_avg_node:{avg:.2} ql_listpack_max:{fill} \
504                 ql_compressed:0 ql_uncompressed_size:{bytes}",
505            );
506        }
507    });
508    out.simple(line.as_bytes());
509    Ok(())
510}
511
512/// The `list-max-listpack-size` a set of list thresholds came from.
513///
514/// Backwards, because the setting is one number and the bands are two fields,
515/// and the two fields are what everything downstream of the parse wants. A count
516/// is itself and a size is the index into Redis's five, so this reads a band
517/// nobody set as the `-2` that made it.
518fn list_fill(limits: &yo_kv::list::Limits) -> i32 {
519    if let Some(count) = limits.max_packed_entries {
520        return i32::try_from(count).unwrap_or(i32::MAX);
521    }
522    match limits.max_packed_bytes {
523        4096 => -1,
524        16384 => -3,
525        32768 => -4,
526        65536 => -5,
527        _ => -2,
528    }
529}
530
531/// `DEBUG SDSLEN <key>`, the six numbers about a string and its name.
532///
533/// The two lengths are real and the four numbers around them are D-135. On a
534/// real server they are `sds` and `zmalloc` internals: how much spare room the
535/// string header left on the end and how many bytes the allocator handed back
536/// for the request, which are questions about jemalloc rather than about the
537/// value. Nothing here has either. A name is held packed with no spare and a
538/// string value is held at exactly its length, so the spare is nought and the
539/// allocation is the length, and those are true statements rather than
540/// placeholders.
541///
542/// An integer encoded string is refused, which is the reference's answer too and
543/// is for the same reason: there is no string there to measure, only the number
544/// it was read as.
545fn sdslen(server: &Server, session: &Session, key: &[u8], out: &mut Out) -> Result<()> {
546    let mut held = server.dbs[session.db].hold(key);
547    let Some(encoding) = held.encoding_name(key) else {
548        return Err(Error::new(Code::Invalid, NO_SUCH_KEY));
549    };
550    if !matches!(encoding, "raw" | "embstr") {
551        return Err(Error::new(Code::Invalid, "Not an sds encoded string."));
552    }
553    let _quiet = lookups::quiet();
554    let len = held.strlen(key).unwrap_or(0);
555    drop(held);
556
557    let mut line = String::with_capacity(128);
558    yo_alloc::allow(|| {
559        // The space after each `zmalloc:` and after nothing else is the
560        // reference's, and a suite reading the line by column would notice.
561        let _ = write!(
562            line,
563            "key_sds_len:{}, key_sds_avail:0, key_zmalloc: {}, \
564             val_sds_len:{len}, val_sds_avail:0, val_zmalloc: {len}",
565            key.len(),
566            key.len(),
567        );
568    });
569    out.simple(line.as_bytes());
570    Ok(())
571}
572
573/// Which of the two structure dumps was asked for.
574#[derive(Clone, Copy)]
575enum Packing {
576    Listpack,
577    Quicklist,
578}
579
580impl Packing {
581    /// The word for it, which is also the encoding a value has to be in.
582    const fn word(self) -> &'static str {
583        match self {
584            Packing::Listpack => "LISTPACK",
585            Packing::Quicklist => "QUICKLIST",
586        }
587    }
588
589    /// The encoding this dump is about.
590    const fn encoding(self) -> &'static str {
591        match self {
592            Packing::Listpack => "listpack",
593            Packing::Quicklist => "quicklist",
594        }
595    }
596
597    /// The sentence the client gets, which says where the real answer went.
598    const fn said(self) -> &'static [u8] {
599        match self {
600            Packing::Listpack => b"Listpack structure printed on stdout",
601            Packing::Quicklist => b"Quicklist structure printed on stdout",
602        }
603    }
604
605    /// The refusal for a value that is not in that representation.
606    const fn refusal(self) -> &'static str {
607        match self {
608            Packing::Listpack => "Not a listpack encoded object.",
609            Packing::Quicklist => "Not a quicklist encoded object.",
610        }
611    }
612}
613
614/// `DEBUG LISTPACK <key>` and `DEBUG QUICKLIST <key> [<level>]`.
615///
616/// Both of them write to the server's own output and answer the client a
617/// sentence saying so, which is what makes them usable at all: the structure of
618/// a listpack is pages of entry headers and nobody wants it on a socket. So the
619/// reply is fixed and the interesting part goes where the log goes.
620///
621/// The level argument on `QUICKLIST` is read and dropped, and a level that is not
622/// a number is accepted rather than refused, both of which are the reference's
623/// behaviour. It reads the word with `atoi` and prints more or less depending on
624/// what came back, and there is one amount of detail here.
625///
626/// A listpack is any value whose encoding is `listpack`, whatever type it is on,
627/// so a small list, hash, set and sorted set all answer. An `intset` does not,
628/// which is the one that reads like an exception and is not: an intset is a
629/// different packing with a different header.
630fn packing(
631    server: &Server,
632    session: &Session,
633    key: &[u8],
634    which: Packing,
635    out: &mut Out,
636) -> Result<()> {
637    let mut held = server.dbs[session.db].hold(key);
638    let Some(encoding) = held.encoding_name(key) else {
639        return Err(Error::new(Code::Invalid, NO_SUCH_KEY));
640    };
641    if encoding != which.encoding() {
642        return Err(Error::new(Code::Invalid, which.refusal()));
643    }
644    let _quiet = lookups::quiet();
645    let kind = held.type_name(key).unwrap_or("none");
646    let shape = held.list_shape(key);
647    let serialized = held
648        .dump(key)
649        .map_or(0, |payload| payload.len() - DUMP_AROUND);
650    drop(held);
651
652    yo_alloc::allow(|| {
653        let name = String::from_utf8_lossy(key);
654        let mut line = format!(
655            "yodb: DEBUG {}: {name}: {kind}, {serialized} byte(s)",
656            which.word()
657        );
658        if let Some((nodes, bytes)) = shape {
659            let _ = write!(line, ", {nodes} node(s) holding {bytes}");
660        }
661        println!("{line}");
662    });
663    out.simple(which.said());
664    Ok(())
665}
666
667/// `DEBUG DIGEST`, the forty characters that stand for everything in the server.
668///
669/// This is the check the Redis suite runs after nearly every interesting thing
670/// it does. Load a file and digest, promote a replica and digest, rewrite the
671/// log and digest, and the assertion is that the number did not move. It is the
672/// only affordable way to say two servers hold the same million keys, and it
673/// only works because both of them compute it the same way, which is why
674/// [`yo_kv::digest`] copies the recipe rather than choosing a better hash.
675///
676/// Every database in order, the empty ones passed over, the number of each one
677/// folded in before its keys. Inside a database the keys are order free, which
678/// is what lets this walk the stripes one at a time and what lets a server with
679/// four stripes agree with a server with sixty four.
680///
681/// Reading a key here is not using it. A suite that digests between every step
682/// would otherwise be rewriting the working set it is testing, so the whole walk
683/// runs with the lookup counters held quiet. `DEBUG DIGEST-VALUE` does count,
684/// because a real server counts there and not here.
685fn whole_digest(server: &Server, out: &mut Out) {
686    let _quiet = lookups::quiet();
687    let mut whole = digest::EMPTY;
688    for (i, db) in server.dbs.iter().enumerate() {
689        // An empty database is passed over entirely rather than folded in as an
690        // empty one, so a server with one key in database nine answers the same
691        // as a server with sixteen databases and the same one key.
692        if db.is_empty() {
693            continue;
694        }
695        digest::number(&mut whole, i as u32);
696        db.digest(&mut whole);
697    }
698    out.simple(&digest::hex(&whole));
699}
700
701/// `DEBUG DIGEST-VALUE <key> [<key> ...]`, the same thing for one value at a
702/// time.
703///
704/// One simple string per key, in the order they were asked for. The key name is
705/// not folded in, which is what makes this the digest of a value rather than of
706/// an entry: the same list under two names answers the same forty characters,
707/// and that is the point, since the usual use is checking that a key survived a
708/// rename or arrived on a replica under a different name.
709///
710/// A key that is not there is forty zeros rather than an error, so a client can
711/// ask about several keys without having to know first which of them exist.
712/// That also means a key holding nothing and a key holding a value that happens
713/// to digest to zero are told apart by `EXISTS` and not by this, which is a
714/// theoretical complaint about a hash nobody is going to hit.
715fn value_digests(server: &Server, session: &Session, args: Args<'_>, out: &mut Out) {
716    out.array(args.len() - 2);
717    for i in 2..args.len() {
718        let key = args.get(i);
719        let mut one = digest::EMPTY;
720        // Left at nothing when the key is not there, which is the forty zeros.
721        server.dbs[session.db].hold(key).digest_value(key, &mut one);
722        out.simple(&digest::hex(&one));
723    }
724}
725
726/// `DEBUG POPULATE <count> [<prefix> [<size>]]`.
727///
728/// Keys are `<prefix>:<n>` counting from nought, with `key` as the prefix if
729/// none was given, and each value is `value:<n>`. A size pads that with zero
730/// bytes to exactly that many, or cuts it short, and a size of nought means the
731/// value is left as it is rather than made empty.
732///
733/// A key that is already there is left alone, value and deadline both, which is
734/// the surprising half and is what makes this safe to run twice. A real server
735/// checks the dictionary and skips, and it does that because the whole point of
736/// the subcommand is filling a database quickly, and quickly means not paying
737/// for a delete of something it is about to write over. Here that falls out of
738/// asking for the write the way `SET key value NX` asks for it.
739///
740/// Nothing is told about the keys this writes: no keyspace notification, no
741/// index update. The notifications are the reference's choice, since it adds the
742/// keys to the dictionary directly and never goes near the event code. The
743/// indexes are this build's, and they are safe to leave out rather than merely
744/// cheap: an index follows hashes or JSON documents and every key here is a
745/// string, and a key that was already a document is one of the keys this skips.
746fn populate(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Result<()> {
747    let count = positive(args.get(2))?;
748    let prefix = if args.len() >= 4 { args.get(3) } else { b"key" };
749    let size = if args.len() == 5 {
750        positive(args.get(4))? as usize
751    } else {
752        0
753    };
754    // Two buffers reused across the whole run rather than a pair of allocations
755    // per key, since the count a suite passes here is routinely a hundred
756    // thousand and every one of those is the same two shapes with a different
757    // number on the end.
758    let mut key = Vec::with_capacity(prefix.len() + 24);
759    let mut value = Vec::with_capacity(size.max(32));
760    let db = &server.dbs[session.db];
761    for n in 0..count {
762        key.clear();
763        key.extend_from_slice(prefix);
764        key.push(b':');
765        push_int(&mut key, n);
766        value.clear();
767        value.extend_from_slice(b"value:");
768        push_int(&mut value, n);
769        if size != 0 {
770            // Shorter than the name is a cut and longer is zero bytes on the
771            // end, which is what the reference's `sdsgrowzero` does and is why
772            // a size of five gives `value` and not `value:0` cut to five.
773            value.resize(size, 0);
774        }
775        // One stripe held per key rather than one for the run, because the keys
776        // are spread across every stripe by design and holding them all would
777        // be holding the whole database against every other thread for as long
778        // as the fill takes.
779        db.hold(&key)
780            .set(&key, &value, SetOptions::PLAIN.if_missing())?;
781    }
782    out.ok();
783    Ok(())
784}
785
786/// A count argument, which has to be a whole number that is not negative.
787///
788/// The reference reads both of `POPULATE`'s numbers with the same call and says
789/// the same thing about both, so a size that is not a number complains about a
790/// range rather than about not being a number.
791fn positive(value: &[u8]) -> Result<i64> {
792    parse_i64(value)
793        .filter(|&n| n >= 0)
794        .ok_or_else(|| Error::new(Code::Invalid, "value is out of range, must be positive"))
795}
796
797/// A whole number, appended.
798fn push_int(out: &mut Vec<u8>, mut n: i64) {
799    let start = out.len();
800    if n == 0 {
801        out.push(b'0');
802        return;
803    }
804    while n > 0 {
805        out.push(b'0' + (n % 10) as u8);
806        n /= 10;
807    }
808    out[start..].reverse();
809}
810
811/// `DEBUG PROTOCOL <type>`, which is one reply of each type RESP3 has.
812///
813/// This is the command a client library's own test suite points at itself to
814/// find out whether it decodes the protocol, so every one of these was read off
815/// the wire of an 8.10.1 rather than off the documentation, on both protocols.
816/// Two of them are worth spelling out.
817///
818/// `attrib` on RESP3 sends an attribute and then a real reply behind it, and on
819/// RESP2 sends only the reply, because RESP2 has no way to carry the attribute
820/// and dropping it is what the other side does. `push` is the other way round:
821/// on RESP3 the real reply goes out first and the push follows it, and on RESP2
822/// the whole subcommand is an error, because a push on RESP2 would be an
823/// ordinary array and a client would read it as the reply.
824// The double the reference sends is 3.141, which is close enough to pi for the
825// lint to think somebody meant pi and typed it badly. Nobody did: it is a test
826// value chosen to have three decimal places, and rounding it to the real
827// constant would change the bytes on the wire, which are the whole point.
828#[allow(clippy::approx_constant)]
829fn protocol(kind: &[u8], out: &mut Out) -> Result<()> {
830    if is(kind, b"string") {
831        out.bulk(b"Hello World");
832    } else if is(kind, b"integer") {
833        out.int(12345);
834    } else if is(kind, b"double") {
835        out.double(3.141);
836    } else if is(kind, b"bignum") {
837        out.big_number(b"1234567999999999999999999999999999999");
838    } else if is(kind, b"null") {
839        out.nil();
840    } else if is(kind, b"array") {
841        out.array(3);
842        for n in 0..3 {
843            out.int(n);
844        }
845    } else if is(kind, b"set") {
846        out.set(3);
847        for n in 0..3 {
848            out.int(n);
849        }
850    } else if is(kind, b"map") {
851        // The keys are numbers and the values are booleans, so a RESP2 client
852        // sees three pairs flattened with the booleans as `:0` and `:1`, which
853        // is the shape a RESP2 client already gets from every map here.
854        out.map(3);
855        for n in 0..3 {
856            out.int(n);
857            out.bool(n == 1);
858        }
859    } else if is(kind, b"attrib") {
860        if out.proto().is_resp3() {
861            out.attribute(1);
862            out.bulk(b"key-popularity");
863            out.array(2);
864            out.bulk(b"key:123");
865            out.int(90);
866        }
867        out.bulk(b"Some real reply following the attribute");
868    } else if is(kind, b"push") {
869        if !out.proto().is_resp3() {
870            return Err(Error::new(
871                Code::Invalid,
872                "RESP2 is not supported by this command",
873            ));
874        }
875        out.bulk(b"Some real reply following the push reply");
876        out.push(2);
877        out.bulk(b"server-cpu-usage");
878        out.int(42);
879    } else if is(kind, b"verbatim") {
880        out.verbatim(b"txt", b"This is a verbatim\nstring");
881    } else if is(kind, b"true") {
882        out.bool(true);
883    } else if is(kind, b"false") {
884        out.bool(false);
885    } else {
886        return Err(Error::new(
887            Code::Invalid,
888            "Wrong protocol type name. Please use one of the following: string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false",
889        ));
890    }
891    Ok(())
892}
893
894/// What `DEBUG HELP` says, which is what is here and not what Redis has.
895const HELP: &[&str] = &[
896    "DEBUG <subcommand> [<arg> [value] [opt] ...]. Subcommands are:",
897    "CHANGE-REPL-ID",
898    "    Change the replication IDs of the server. Useful for testing the",
899    "    replication sub system.",
900    "DICT-RESIZING <0|1>",
901    "    Enable or disable the background reclaim of room the store no longer",
902    "    needs.",
903    "DIGEST",
904    "    Output a hex signature representing the current DB content.",
905    "DIGEST-VALUE <key> [<key> ...]",
906    "    Output a hex signature of the values of all the specified keys.",
907    "ERROR <string>",
908    "    Return a Redis protocol error with <string> as message. Useful for",
909    "    clients unit tests to simulate Redis errors.",
910    "INTERNAL_SECRET",
911    "    Return the cluster internal secret (hashed with crc16) or error if not in cluster mode.",
912    "LISTPACK <key>",
913    "    Show low level info about the listpack encoding of <key>.",
914    "LOG <message>",
915    "    Write <message> to the server log.",
916    "MARK-INTERNAL-CLIENT [UNMARK]",
917    "    Promote the current connection to an internal connection.",
918    "OBJECT <key>",
919    "    Show low level info about `key` and associated value.",
920    "PAUSE-CRON <0|1>",
921    "    Stop periodic cron job processing.",
922    "POPULATE <count> [<prefix>] [<size>]",
923    "    Create <count> string keys named key:<num>. If <prefix> is specified",
924    "    then it is used instead of the 'key' prefix. A key that already exists",
925    "    is left alone.",
926    "PROTOCOL <type>",
927    "    Reply with a test value of the specified type. <type> can be: string,",
928    "    integer, double, bignum, null, array, set, map, attrib, push, verbatim,",
929    "    true, false.",
930    "QUICKLIST <key> [<0|1>]",
931    "    Show low level info about the quicklist encoding of <key>.",
932    "    The optional argument (0 by default) sets the level of detail",
933    "QUICKLIST-PACKED-THRESHOLD <size>",
934    "    Sets the threshold for elements to be inserted as plain vs packed nodes",
935    "    Default value is 1GB, allows values up to 4GB. Setting to 0 restores to default.",
936    "RELOAD [MERGE] [NOFLUSH] [NOSAVE]",
937    "    Save the dataset to the RDB file and load it back. NOSAVE reads the file",
938    "    that is already there, NOFLUSH keeps what is in memory and lets the file",
939    "    land on top of it, and MERGE is accepted and does nothing.",
940    "SDSLEN <key>",
941    "    Show low level SDS string info representing `key` and value.",
942    "SET-ACTIVE-EXPIRE <0|1>",
943    "    Setting it to 0 disables expiring keys in background when they are not",
944    "    accessed (otherwise the Redis behavior). Setting it to 1 reenables back",
945    "    the default.",
946    "SET-SKIP-CHECKSUM-VALIDATION <0|1>",
947    "    Enables or disables checksum checks for RESTORE's payload.",
948    "SLEEP <seconds>",
949    "    Stop the server for <seconds>. Decimals allowed.",
950    "HELP",
951    "    Print this help.",
952];
953
954#[cfg(test)]
955mod tests {
956    use super::{flag, leading_double};
957
958    /// The flag reads what C reads out of the same bytes.
959    #[test]
960    fn a_flag_is_atoi_and_anything_unreadable_is_off() {
961        for (text, want) in [
962            (&b"0"[..], 0),
963            (b"1", 1),
964            (b"00", 0),
965            (b"01", 1),
966            (b"2", 1),
967            (b"-1", 1),
968            (b"-0", 0),
969            (b"x", 0),
970            (b"", 0),
971            (b"1x", 1),
972            (b"true", 0),
973            (b"18446744073709551617", 1),
974        ] {
975            assert_eq!(flag(text), want, "{}", String::from_utf8_lossy(text));
976        }
977    }
978
979    /// A sleep argument reads as much of itself as is a number.
980    #[test]
981    fn a_sleep_reads_the_longest_number_at_the_front() {
982        for (text, want) in [
983            ("0", 0.0),
984            ("0.05", 0.05),
985            ("-1", -1.0),
986            ("abc", 0.0),
987            ("", 0.0),
988            ("1.5s", 1.5),
989            ("2x3", 2.0),
990        ] {
991            assert!(
992                (leading_double(text) - want).abs() < 1e-9,
993                "{text} read as {}",
994                leading_double(text)
995            );
996        }
997    }
998}