Skip to main content

yo_resp/dispatch/
cluster.rs

1//! Cluster mode: the 16384 slots, who owns them, and what a client is told
2//! when it asks the wrong node.
3//!
4//! A Redis cluster has no coordinator and no lookup service. Every key belongs
5//! to one of 16384 slots, worked out from the key name alone, and every node
6//! knows which node owns which slot. A client that guesses wrong is not proxied,
7//! it is told where to go, and it remembers. So the whole of routing is a
8//! function from a key to a number and a table from that number to a node, and
9//! both of them are here.
10//!
11//! # The slot
12//!
13//! `CRC16(key) mod 16384`, with one rule on top: if the key contains a `{`
14//! followed later by a `}` with at least one byte between them, only the bytes
15//! between them are hashed. That is the hash tag, and it is the only way a
16//! client has of making two keys land together, which is the only way a command
17//! that names two keys can be run at all. `{user1000}.following` and
18//! `{user1000}.followers` are one slot and one node.
19//!
20//! # The table
21//!
22//! 16384 entries, each naming the node that owns that slot or nobody. A slot
23//! nobody owns is a hole, and a cluster with a hole in it is down: rather than
24//! guess, every node refuses every key in the hole and, with
25//! `cluster-require-full-coverage` left on, refuses every key at all. That is
26//! severe on purpose. A cluster that answers for the slots it happens to have is
27//! a cluster that silently loses the rest.
28//!
29//! Two more tables sit beside it for the slots that are moving. A slot this node
30//! owns and is sending away is migrating, and a key in it that is already gone
31//! is answered with `ASK`, which is a redirection for one command rather than
32//! for the slot. A slot this node does not own and is receiving is importing, and
33//! a command lands on it only if the connection said `ASKING` first. The pair is
34//! what makes a slot move without a window where a key is on neither node.
35//!
36//! # What is here and what is not
37//!
38//! The slots, the table, the whole `CLUSTER` container, the redirections and the
39//! configuration file that carries all of it across a restart. The bus, which is
40//! the binary protocol nodes talk to each other over and the thing that actually
41//! fills the table, is next door in [`bus`]. Nothing in this file dials anybody:
42//! it reads the table and edits it, and a change an operator makes here is
43//! announced by asking the bus to send a packet once the lock is gone.
44//!
45//! What is not here is the failover vote, so a replica of a node that dies is
46//! listed and gossiped correctly and will not promote itself, which is D-152.
47
48use core::fmt::Write as _;
49use std::sync::atomic::Ordering::Relaxed;
50use std::sync::atomic::{AtomicBool, AtomicU64};
51
52use yo_common::lock::Lock;
53use yo_common::{Code, Error, Result};
54
55use crate::reply::Out;
56
57use super::args::{self, Args};
58use super::{Server, Session};
59
60mod asm;
61mod bus;
62use super::keyspec;
63use super::table::Spec;
64pub(super) use asm::Migration;
65
66/// How many slots a cluster has, which is Redis's number and is not a setting.
67///
68/// It is 16384 rather than a round number because the bus gossips a bitmap of
69/// every slot in every heartbeat, and 16384 bits is two kilobytes, which is
70/// what the authors were willing to spend on a packet sent every second.
71pub const SLOTS: usize = 16384;
72
73/// How long a cluster has to be fully covered before it says it is up.
74///
75/// Redis waits before turning `cluster_state` from fail to ok, so a node that
76/// has just been given its slots does not announce itself ready in the same
77/// instant and then take it back when the next heartbeat disagrees. The number
78/// is Redis's `CLUSTER_WRITABLE_DELAY`.
79const WRITABLE_DELAY_MS: u64 = 2000;
80
81/// How long a node that has been down waits before saying it is up again.
82///
83/// The two delays are different because they are about different things. The
84/// first is about a node that has only just started and has never been anything
85/// but ready. The second is about a node that was in the minority and has just
86/// seen the coverage come back, which is the case where being hasty is how a
87/// healed partition serves a key that has already been elected away somewhere
88/// else. Redis works it out as the node timeout clamped between 500ms and 5s,
89/// and with the default fifteen second timeout that is five seconds every time.
90const REJOIN_DELAY_MS: u64 = 5000;
91
92/// What the bus port is, which is the client port plus this.
93const BUS_OFFSET: u16 = 10000;
94
95/// How long a node id is, in hex characters.
96const ID_LEN: usize = 40;
97
98// ----------------------------------------------------------------- the slot
99
100/// The CRC16 table Redis hashes keys with, which is CCITT with the XMODEM
101/// parameters: polynomial 0x1021, no reflection, no initial value and no final
102/// exclusive or.
103///
104/// Written out rather than computed at startup because it is what every key on
105/// the command path goes through, and a table in the binary is a table that is
106/// already in cache by the time the first command arrives.
107#[rustfmt::skip]
108const CRC16: [u16; 256] = [
109    0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
110    0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
111    0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
112    0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
113    0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
114    0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
115    0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
116    0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
117    0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
118    0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
119    0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
120    0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
121    0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
122    0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
123    0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
124    0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
125    0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
126    0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
127    0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
128    0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
129    0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
130    0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
131    0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
132    0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
133    0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
134    0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
135    0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
136    0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
137    0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
138    0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
139    0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
140    0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0,
141];
142
143/// The CRC16 of `data`, which is only ever used to pick a slot.
144#[must_use]
145fn crc16(data: &[u8]) -> u16 {
146    let mut crc: u16 = 0;
147    for &byte in data {
148        let at = ((crc >> 8) ^ u16::from(byte)) & 0xff;
149        crc = (crc << 8) ^ CRC16[at as usize];
150    }
151    crc
152}
153
154/// Which slot `key` belongs to.
155///
156/// The hash tag rule is Redis's `keyHashSlot` and its edge cases are worth being
157/// exact about, because a client library implements the same rule and the two
158/// have to agree on every key or the client routes to a node that will not serve
159/// it. A `{` with no `}` after it hashes the whole key. A `{}` with nothing
160/// between hashes the whole key. Only the first `}` after the first `{` counts,
161/// so `{a}{b}` hashes `a`. And a key with no braces at all hashes whole, which
162/// is nearly every key there has ever been.
163#[must_use]
164pub fn key_slot(key: &[u8]) -> u16 {
165    let tagged = match key.iter().position(|&b| b == b'{') {
166        Some(open) => match key[open + 1..].iter().position(|&b| b == b'}') {
167            // The empty tag is not a tag, which is the `if (e == s+1)` arm.
168            Some(0) | None => key,
169            Some(len) => &key[open + 1..open + 1 + len],
170        },
171        None => key,
172    };
173    crc16(tagged) % SLOTS as u16
174}
175
176// ---------------------------------------------------------------- the table
177
178/// The reference's own node flag bits.
179///
180/// Kept as its bits rather than as a set of booleans because they go out on the
181/// bus in exactly this shape and come back in it, so anything else would be two
182/// translations for no gain.
183pub(crate) const FLAG_MASTER: u16 = 1;
184pub(crate) const FLAG_SLAVE: u16 = 2;
185pub(crate) const FLAG_PFAIL: u16 = 4;
186pub(crate) const FLAG_FAIL: u16 = 8;
187pub(crate) const FLAG_MYSELF: u16 = 16;
188pub(crate) const FLAG_HANDSHAKE: u16 = 32;
189pub(crate) const FLAG_NOADDR: u16 = 64;
190pub(crate) const FLAG_MEET: u16 = 128;
191pub(crate) const FLAG_MIGRATE_TO: u16 = 256;
192pub(crate) const FLAG_NOFAILOVER: u16 = 512;
193pub(crate) const FLAG_EXTENSIONS: u16 = 1024;
194
195/// One node in the cluster, which is this one and everybody the bus has found.
196#[derive(Clone)]
197struct Node {
198    /// The forty hex characters that name it, which never change while it lives.
199    id: String,
200    /// Where a client reaches it. Empty for this node, which is what a real
201    /// server reports for itself until something tells it its own address.
202    host: String,
203    /// The port a client reaches it on.
204    port: u16,
205    /// The port the bus reaches it on, which is nearly always the client port
206    /// plus ten thousand but is carried separately because a node behind a port
207    /// map announces something else.
208    bus: u16,
209    /// Which shard it belongs to, which is a master and its replicas.
210    shard: String,
211    /// The config epoch it claims its slots under.
212    epoch: u64,
213    /// What it is and how it is doing, in the reference's bits.
214    flags: u16,
215    /// The master it follows, as an index into the same table.
216    master: Option<u16>,
217    /// When a ping went out to it with no pong back yet, or nought for none
218    /// outstanding, which is the field the timeout is measured from.
219    ping_sent: u64,
220    /// When the last pong came back.
221    pong_recv: u64,
222    /// When anything at all last arrived from it. A node under a heavy pub/sub
223    /// load can be slow with its pong and perfectly alive, so traffic of any
224    /// kind counts as proof it is there.
225    data_recv: u64,
226    /// When this node decided it had failed.
227    fail_time: u64,
228    /// The replication offset it last put in a packet.
229    offset: u64,
230    /// Whether there is an outbound link to it at the moment.
231    linked: bool,
232    /// Who has said it is unreachable and when they last said so, which is the
233    /// weak quorum the FAIL flag needs.
234    reports: Vec<(String, u64)>,
235}
236
237impl Node {
238    /// A node with nothing known about it but where it is.
239    fn new(id: String, host: String, port: u16, bus: u16, flags: u16, now: u64) -> Node {
240        Node {
241            id,
242            host,
243            port,
244            bus,
245            shard: String::from_utf8_lossy(&new_id()).into_owned(),
246            epoch: 0,
247            flags,
248            master: None,
249            ping_sent: 0,
250            // Both stamps start at zero and not at now, which is the reference's
251            // own rule and is what makes the `myself` line read `0 0`. Nothing
252            // has been heard from a node that was made a moment ago, and saying
253            // otherwise would also hold off the first ping for a second.
254            pong_recv: 0,
255            // This one does start at now, because it stands in for the creation
256            // time as well and the handshake timeout is measured against it.
257            data_recv: now,
258            fail_time: 0,
259            offset: 0,
260            linked: false,
261            reports: Vec::new(),
262        }
263    }
264
265    /// Whether it is a master, which is the reference's rule that anything not
266    /// flagged a replica counts as one.
267    fn is_master(&self) -> bool {
268        self.flags & FLAG_SLAVE == 0
269    }
270
271    /// Whether it is down as far as anybody can tell.
272    fn down(&self) -> bool {
273        self.flags & (FLAG_PFAIL | FLAG_FAIL) != 0
274    }
275
276    /// The comma separated flag list a `CLUSTER NODES` line carries, in the
277    /// reference's own order, with `noflags` for a node with none of them.
278    fn flag_names(&self, into: &mut String) {
279        const NAMES: [(u16, &str); 8] = [
280            (FLAG_MYSELF, "myself"),
281            (FLAG_MASTER, "master"),
282            (FLAG_SLAVE, "slave"),
283            (FLAG_PFAIL, "fail?"),
284            (FLAG_FAIL, "fail"),
285            (FLAG_HANDSHAKE, "handshake"),
286            (FLAG_NOADDR, "noaddr"),
287            (FLAG_NOFAILOVER, "nofailover"),
288        ];
289        let mut first = true;
290        for (bit, name) in NAMES {
291            if self.flags & bit == 0 {
292                continue;
293            }
294            if !first {
295                into.push(',');
296            }
297            into.push_str(name);
298            first = false;
299        }
300        if first {
301            into.push_str("noflags");
302        }
303    }
304
305    /// The `ip:port@bus` field of a `CLUSTER NODES` line.
306    fn address(&self, into: &mut String) {
307        let _ = write!(into, "{}:{}@{}", self.host, self.port, self.bus);
308    }
309
310    /// The same field with the aux fields on the end of it, which is what goes in
311    /// the config file and not what goes on the wire. The reference keeps them
312    /// off `CLUSTER NODES` on purpose, because a client that learned to parse
313    /// them would break the next time somebody added one.
314    fn address_on_disk(&self, into: &mut String) {
315        self.address(into);
316        let _ = write!(into, ",,tls-port=0,shard-id={}", self.shard);
317    }
318}
319
320/// Who owns what, and what is moving.
321///
322/// One lock over all three tables rather than one each, because every reader
323/// wants a consistent answer across them: a slot is owned, or migrating, or
324/// importing, and a reader that saw two of those from different instants would
325/// route a client to a node that had already handed the slot on.
326struct Map {
327    /// Every node this one knows about, with this one first.
328    nodes: Vec<Node>,
329    /// Which node owns each slot, as an index into `nodes`, or `None`.
330    owner: Vec<Option<u16>>,
331    /// The slots this node owns and is sending away, and to whom.
332    migrating: Vec<Option<u16>>,
333    /// The slots this node is receiving, and from whom.
334    importing: Vec<Option<u16>>,
335}
336
337impl Map {
338    /// An empty table with room for every slot.
339    fn new(me: Node) -> Map {
340        Map {
341            nodes: vec![me],
342            owner: vec![None; SLOTS],
343            migrating: vec![None; SLOTS],
344            importing: vec![None; SLOTS],
345        }
346    }
347
348    /// The index of the node with this id, or `None` for one nobody knows.
349    fn find(&self, id: &[u8]) -> Option<u16> {
350        self.nodes
351            .iter()
352            .position(|n| n.id.as_bytes() == id)
353            .map(|at| at as u16)
354    }
355
356    /// Drop a node and everything that pointed at it.
357    ///
358    /// Every other table here holds an index into `nodes`, so dropping one out
359    /// of the middle means walking them and shifting anything above it down.
360    /// That is fine because a node leaves a cluster about as often as an
361    /// operator types, and holding ids instead of indices everywhere would cost
362    /// a string compare on the routing path, which is the one path that has to
363    /// be fast.
364    fn forget(&mut self, at: u16) {
365        self.nodes.remove(usize::from(at));
366        let shift = |slot: &mut Option<u16>| match *slot {
367            Some(node) if node == at => *slot = None,
368            Some(node) if node > at => *slot = Some(node - 1),
369            _ => {}
370        };
371        for slot in 0..SLOTS {
372            shift(&mut self.owner[slot]);
373            shift(&mut self.migrating[slot]);
374            shift(&mut self.importing[slot]);
375        }
376        for node in &mut self.nodes {
377            shift(&mut node.master);
378        }
379    }
380
381    /// How many masters are serving at least one slot and have not failed, which
382    /// is the electorate a FAIL vote is counted against.
383    fn voters(&self) -> usize {
384        let mut seen = vec![false; self.nodes.len()];
385        for owner in self.owner.iter().flatten() {
386            seen[*owner as usize] = true;
387        }
388        seen.iter().filter(|s| **s).count()
389    }
390
391    /// Whether this node owns `slot`, which is index nought owning it.
392    fn mine(&self, slot: u16) -> bool {
393        self.owner[slot as usize] == Some(0)
394    }
395
396    /// How many slots have an owner.
397    fn assigned(&self) -> usize {
398        self.owner.iter().filter(|o| o.is_some()).count()
399    }
400
401    /// How many nodes are serving at least one slot, which is Redis's
402    /// `cluster_size` and is not the same as how many nodes there are.
403    fn size(&self) -> usize {
404        let mut seen = vec![false; self.nodes.len()];
405        for owner in self.owner.iter().flatten() {
406            seen[*owner as usize] = true;
407        }
408        seen.iter().filter(|s| **s).count()
409    }
410
411    /// The runs of consecutive slots one node owns, in order.
412    fn runs(&self, node: u16) -> Vec<(u16, u16)> {
413        let mut runs: Vec<(u16, u16)> = Vec::new();
414        for slot in 0..SLOTS as u16 {
415            if self.owner[slot as usize] != Some(node) {
416                continue;
417            }
418            match runs.last_mut() {
419                Some(last) if last.1 + 1 == slot => last.1 = slot,
420                _ => runs.push((slot, slot)),
421            }
422        }
423        runs
424    }
425}
426
427// -------------------------------------------------------------- the settings
428
429/// Everything about cluster mode, all of it idle on a server that was not
430/// started with it on, which is nearly every server.
431pub(crate) struct Cluster {
432    /// Whether `--cluster-enabled yes` was given. Immutable for the life of the
433    /// process, which is Redis's rule and the only sane one: a server that could
434    /// be turned into a cluster node while it was holding keys would be a server
435    /// whose keys were suddenly in slots it did not own.
436    on: bool,
437    /// The tables, empty on a server that is not a cluster node.
438    map: Lock<Map>,
439    /// Redis's `currentEpoch`, which is the highest epoch anybody has claimed.
440    epoch: AtomicU64,
441    /// When the coverage last became complete, for the delay before this node
442    /// says the cluster is up. Nought means it is not covered.
443    covered_at: AtomicU64,
444    /// When cluster mode was turned on, which is the other end of the shorter
445    /// delay.
446    booted_at: AtomicU64,
447    /// Whether this node has been uncovered at any point since it started, which
448    /// is what picks between the two delays.
449    was_down: AtomicBool,
450    /// Whether every slot has to have an owner before anything is served, which
451    /// is Redis's `cluster-require-full-coverage` and is on by default.
452    full_coverage: AtomicBool,
453    /// Whether a read is still served while the cluster is down, which is
454    /// Redis's `cluster-allow-reads-when-down` and is off by default.
455    ///
456    /// Off is the safe answer and it is the one a real server ships with. A node
457    /// that cannot see the whole cluster does not know whether the part it
458    /// cannot see has already elected somebody else, so a read it served could
459    /// be a read of a key that has moved on. An operator who would rather have
460    /// stale reads than no reads turns it on and knows what they bought.
461    reads_when_down: AtomicBool,
462    /// Where the table is written, under the server's `dir`.
463    file: Lock<String>,
464    /// The links, the blacklist and the secret, none of which exist until the
465    /// bus is started and none of which a command reads on the hot path.
466    bus: bus::Bus,
467    /// The slot migration this node is in, and the ones it has been in.
468    asm: asm::Asm,
469}
470
471impl Default for Cluster {
472    fn default() -> Cluster {
473        Cluster {
474            on: false,
475            map: Lock::new(Map {
476                nodes: Vec::new(),
477                owner: Vec::new(),
478                migrating: Vec::new(),
479                importing: Vec::new(),
480            }),
481            epoch: AtomicU64::new(0),
482            covered_at: AtomicU64::new(0),
483            booted_at: AtomicU64::new(0),
484            was_down: AtomicBool::new(false),
485            full_coverage: AtomicBool::new(true),
486            reads_when_down: AtomicBool::new(false),
487            file: Lock::new(String::new()),
488            bus: bus::Bus::default(),
489            asm: asm::Asm::default(),
490        }
491    }
492}
493
494impl Server {
495    /// Whether this server is a cluster node, which is one relaxed read of a
496    /// field that is false on nearly every server there is.
497    #[must_use]
498    pub fn cluster_enabled(&self) -> bool {
499        self.cluster.on
500    }
501
502    /// Turn cluster mode on, which only whoever built the server may do.
503    ///
504    /// Called before the first connection is accepted and never again, which is
505    /// why it takes `&mut self`: there is no lock here because there is nobody
506    /// to race with.
507    pub fn enable_cluster(&mut self, file: &str, port: u16) {
508        self.cluster.on = true;
509        self.cluster.booted_at.store(self.now_ms(), Relaxed);
510        let now = self.now_ms();
511        let me = yo_alloc::allow(|| {
512            Node::new(
513                String::from_utf8_lossy(&new_id()).into_owned(),
514                String::new(),
515                port,
516                port + BUS_OFFSET,
517                FLAG_MYSELF | FLAG_MASTER,
518                now,
519            )
520        });
521        // Under the server's directory when the name is a bare one, which is
522        // what a real server does with `cluster-config-file`: it takes the
523        // directory first and then opens everything relative to it. No name at
524        // all is a node that keeps nothing across a restart, which is not
525        // something a real server offers and is what a test wants.
526        let path = yo_alloc::allow(|| {
527            if file.is_empty() {
528                String::new()
529            } else {
530                self.dir().join(file).to_string_lossy().into_owned()
531            }
532        });
533        yo_alloc::allow(|| {
534            *self.cluster.map.lock() = Map::new(me);
535            *self.cluster.file.lock() = path;
536            // The secret nodes recognise each other by is made here rather than
537            // when the bus starts, because a node with no bus still has to have
538            // one: the whole cluster ends up on the smallest secret anybody
539            // started with, and a node holding an empty string would win that
540            // and leave everybody with no secret at all.
541            *self.cluster.bus.secret.lock() = String::from_utf8_lossy(&new_id()).into_owned();
542        });
543        // Whatever was written last time this node ran, if anything was. A node
544        // that comes back without its slots is a node that has silently given
545        // its half of the keyspace away, so a file that is there and unreadable
546        // is worth more noise than a file that is not there at all.
547        if let Err(e) = self.reload_cluster() {
548            eprintln!("cluster config file could not be read: {e}");
549        }
550        self.recount_coverage();
551    }
552
553    /// The secret one node authenticates to another node's client port with.
554    ///
555    /// Forty hex characters on a cluster node and empty on anything else, which
556    /// is what makes `AUTH "internal connection"` impossible to get past on a
557    /// server that is not in a cluster: there is no secret to guess.
558    ///
559    /// Handed out as a copy because it is read on `AUTH` and nowhere else, so
560    /// one allocation on a command that runs once a connection is cheaper than
561    /// keeping the lock held across a comparison.
562    pub(crate) fn cluster_secret(&self) -> String {
563        let held = self.cluster.bus.secret.lock();
564        yo_alloc::allow(|| held.clone())
565    }
566
567    /// Whether every slot has to be covered before anything is served.
568    pub(crate) fn cluster_full_coverage(&self) -> bool {
569        self.cluster.full_coverage.load(Relaxed)
570    }
571
572    /// Whether a read is served while the cluster is down.
573    pub(crate) fn cluster_reads_when_down(&self) -> bool {
574        self.cluster.reads_when_down.load(Relaxed)
575    }
576
577    /// Set either of the two, which `CONFIG SET` does and which really move: a
578    /// cluster with a hole in it starts answering for the slots it does have.
579    pub(crate) fn set_cluster_coverage(&self, full: bool, reads_when_down: bool) {
580        self.cluster.full_coverage.store(full, Relaxed);
581        self.cluster.reads_when_down.store(reads_when_down, Relaxed);
582        self.recount_coverage();
583    }
584
585    /// Where the table is written, which `CONFIG GET` reports.
586    pub(crate) fn cluster_file(&self) -> String {
587        let file = self.cluster.file.lock();
588        yo_alloc::allow(|| file.clone())
589    }
590
591    /// This node's id, which is what `CLUSTER MYID` answers.
592    pub(crate) fn cluster_id(&self) -> String {
593        let map = self.cluster.map.lock();
594        yo_alloc::allow(|| map.nodes.first().map_or_else(String::new, |n| n.id.clone()))
595    }
596
597    /// Whether the cluster is up, which is every slot covered for long enough.
598    ///
599    /// A hole makes it down, and so does having just been covered, for the delay
600    /// a real server waits. `cluster-require-full-coverage no` turns the first
601    /// half off, and then a node with any slots at all says it is up and refuses
602    /// only the keys in the hole.
603    pub(crate) fn cluster_up(&self) -> bool {
604        let at = self.cluster.covered_at.load(Relaxed);
605        if at == 0 {
606            return false;
607        }
608        let (since, wait) = if self.cluster.was_down.load(Relaxed) {
609            (at, REJOIN_DELAY_MS)
610        } else {
611            (self.cluster.booted_at.load(Relaxed), WRITABLE_DELAY_MS)
612        };
613        self.now_ms().saturating_sub(since) >= wait
614    }
615
616    /// Look at the coverage again and start or stop the clock on it.
617    ///
618    /// Called after anything that moves a slot. Cheap enough to do on the spot
619    /// rather than in a cron, and doing it on the spot is what makes
620    /// `CLUSTER ADDSLOTS` followed by `CLUSTER INFO` agree.
621    fn recount_coverage(&self) {
622        let covered = {
623            let map = self.cluster.map.lock();
624            let assigned = map.assigned();
625            if self.cluster_full_coverage() {
626                assigned == SLOTS
627            } else {
628                assigned > 0
629            }
630        };
631        if covered {
632            let _ =
633                self.cluster
634                    .covered_at
635                    .compare_exchange(0, self.now_ms().max(1), Relaxed, Relaxed);
636        } else {
637            self.cluster.covered_at.store(0, Relaxed);
638            self.cluster.was_down.store(true, Relaxed);
639        }
640    }
641}
642
643/// Forty hex characters from the same entropy the replication id comes from.
644fn new_id() -> [u8; ID_LEN] {
645    const HEX: &[u8; 16] = b"0123456789abcdef";
646    let mut raw = [0u8; ID_LEN / 2];
647    yo_common::entropy::fill(&mut raw);
648    let mut id = [0u8; ID_LEN];
649    for (i, byte) in raw.iter().enumerate() {
650        id[i * 2] = HEX[usize::from(byte >> 4)];
651        id[i * 2 + 1] = HEX[usize::from(byte & 15)];
652    }
653    id
654}
655
656// ------------------------------------------------------------ the redirects
657
658/// Whether the command carries its own `ASKING` rather than needing one sent.
659///
660/// One command does, `RESTORE-ASKING`, and the reference spells that as a flag on
661/// the command rather than as a name the routing gate knows, so this reads the
662/// flag. It is what makes a slot migration work at all: the node being sent the
663/// keys does not own the slot yet, and a client that had to send `ASKING` in
664/// front of every key would double the round trips for no reason.
665pub(super) fn asks(spec: &Spec) -> bool {
666    spec.flags.contains(&"asking")
667}
668
669/// Where a command's keys say it should run, or `None` for run it here.
670///
671/// Called for every command on a cluster node and for none at all on a server
672/// that is not one, which is the reason the enabled flag is read first and is a
673/// plain field rather than anything atomic. A command that names no key never
674/// redirects, whatever the state of the cluster, which is what lets a client
675/// send `PING` and `SUBSCRIBE` to a node that is not serving anything.
676///
677/// The order the checks go in is `getNodeByQuery`'s, and it is not the order
678/// anybody would guess, so it is worth writing down. The first key decides the
679/// slot and the node, and a first key in a slot nobody owns is
680/// `CLUSTERDOWN Hash slot not served` before the rest of the keys have even been
681/// looked at, so a command whose keys are in two slots and whose first slot is a
682/// hole is told about the hole and not about the two slots. Then the rest of the
683/// keys, and one in another slot is `CROSSSLOT`. Then the health of the cluster
684/// as a whole. Then the slot being handed over, which is where `ASK` and
685/// `TRYAGAIN` come from. And `MOVED` last, because it is the answer left when
686/// nothing else applies and the node is not this one.
687pub(super) fn gate(
688    server: &Server,
689    db: usize,
690    asking: bool,
691    spec: &Spec,
692    args: Args<'_>,
693) -> Option<Error> {
694    // Nothing to route. Cheap and first, because a container command and every
695    // connection command land here too.
696    if !keyspec::takes_keys(spec, args, 0) {
697        return None;
698    }
699    let mut slot: Option<u16> = None;
700    let mut crossed = false;
701    let mut keys = 0usize;
702    let mut present = 0usize;
703    let mut missing = 0usize;
704    let (owner, migrating, importing, here) = {
705        let map = server.cluster.map.lock();
706        // The slot of the first key, and then whether everything else agrees.
707        keyspec::find(spec, args, 0, &mut |run| {
708            for i in 0..run.count {
709                let at = run.first + i * run.step;
710                if at >= args.len() {
711                    continue;
712                }
713                let this = key_slot(args.get(at));
714                keys += 1;
715                match slot {
716                    None => slot = Some(this),
717                    Some(first) if first != this => crossed = true,
718                    Some(_) => {}
719                }
720            }
721        });
722        let at = usize::from(slot?);
723        (
724            map.owner[at],
725            map.migrating[at].map(|to| node_at(&map, to)),
726            map.importing[at].is_some(),
727            map.owner[at] == Some(0),
728        )
729    };
730    let slot = slot?;
731    // The first key's slot having no owner beats everything, including a command
732    // whose keys are in two slots.
733    let Some(owner) = owner else {
734        return Some(Error::new(
735            Code::Invalid,
736            "CLUSTERDOWN Hash slot not served",
737        ));
738    };
739    if crossed {
740        return Some(Error::new(
741            Code::Invalid,
742            "CROSSSLOT Keys in request don't hash to the same slot",
743        ));
744    }
745    // A cluster with a hole in it somewhere else. Reads are still served when
746    // the operator asked for that, writes never are, because a write to a
747    // cluster that cannot see all of itself is a write that may be about to be
748    // written somewhere else as well.
749    if !server.cluster_up() {
750        if !server.cluster.reads_when_down.load(Relaxed) {
751            return Some(Error::new(Code::Invalid, "CLUSTERDOWN The cluster is down"));
752        }
753        if spec.flags.contains(&"write") {
754            return Some(Error::new(
755                Code::Invalid,
756                "CLUSTERDOWN The cluster is down and only accepts read commands",
757            ));
758        }
759    }
760    // The slot is moving, so which keys are still here decides the answer. This
761    // is the only part of the gate that touches the keyspace, and it only runs
762    // for a slot that somebody is in the middle of handing over.
763    if migrating.is_some() || importing {
764        let held = &server.dbs[db];
765        keyspec::find(spec, args, 0, &mut |run| {
766            for i in 0..run.count {
767                let at = run.first + i * run.step;
768                if at >= args.len() {
769                    continue;
770                }
771                let key = args.get(at);
772                let mut stripe = held.hold(key);
773                if stripe.exists(key) {
774                    present += 1;
775                } else {
776                    missing += 1;
777                }
778            }
779        });
780    }
781    if let Some(to) = migrating
782        && missing > 0
783    {
784        // Some of them are here and some have gone, so there is no node that can
785        // answer the whole command and the client is told to come back.
786        if present > 0 {
787            return Some(Error::new(
788                Code::Invalid,
789                "TRYAGAIN Multiple keys request during rehashing of slot",
790            ));
791        }
792        return Some(redirect("ASK", slot, &to));
793    }
794    if importing && asking {
795        if keys > 1 && missing > 0 {
796            return Some(Error::new(
797                Code::Invalid,
798                "TRYAGAIN Multiple keys request during rehashing of slot",
799            ));
800        }
801        return None;
802    }
803    if here {
804        return None;
805    }
806    let (host, port) = {
807        let map = server.cluster.map.lock();
808        node_at(&map, owner)
809    };
810    Some(redirect("MOVED", slot, &(host, port)))
811}
812
813/// Where a node is reachable, copied out while the lock is held.
814fn node_at(map: &Map, at: u16) -> (String, u16) {
815    let node = &map.nodes[at as usize];
816    (yo_alloc::allow(|| node.host.clone()), node.port)
817}
818
819/// A `MOVED` or an `ASK` line, which are the same shape with a different word.
820///
821/// A node with no address of its own is written as the loopback, because a
822/// redirection has to name somewhere a client can connect to and an empty host
823/// is not one. A real server does the same when it has nothing better.
824fn redirect(word: &str, slot: u16, node: &(String, u16)) -> Error {
825    let host = if node.0.is_empty() {
826        "127.0.0.1"
827    } else {
828        node.0.as_str()
829    };
830    Error::fmt(
831        Code::Invalid,
832        format_args!("{word} {slot} {host}:{}", node.1),
833    )
834}
835
836// ------------------------------------------------------------- the container
837
838/// `CLUSTER <subcommand> ...`.
839pub(super) fn execute(
840    server: &Server,
841    session: &mut Session,
842    args: Args<'_>,
843    out: &mut Out,
844) -> Result<()> {
845    let session_db = session.db;
846    let sub = args.get(1);
847    // Every subcommand but `HELP` and the two that are only about arguments is
848    // refused outright on a server that was not started as a cluster node, which
849    // is Redis's rule and covers the whole container including `HELP` itself.
850    if !server.cluster_enabled() {
851        return match arity_of(sub) {
852            Some(n) if !arity_ok(n, args.len()) => Err(wrong_sub_arity(sub)),
853            Some(_) => Err(disabled()),
854            None => Err(args::unknown_subcommand(sub, "CLUSTER")),
855        };
856    }
857    let Some(n) = arity_of(sub) else {
858        return Err(args::unknown_subcommand(sub, "CLUSTER"));
859    };
860    if !arity_ok(n, args.len()) {
861        return Err(wrong_sub_arity(sub));
862    }
863    match () {
864        () if args::is(sub, b"myid") => out.bulk(server.cluster_id().as_bytes()),
865        () if args::is(sub, b"myshardid") => {
866            let map = server.cluster.map.lock();
867            out.bulk(map.nodes[0].shard.as_bytes());
868        }
869        () if args::is(sub, b"keyslot") => out.int(i64::from(key_slot(args.get(2)))),
870        () if args::is(sub, b"info") => info(server, out),
871        () if args::is(sub, b"nodes") => nodes(server, out),
872        () if args::is(sub, b"slots") => reply_slots(server, out),
873        () if args::is(sub, b"shards") => shards(server, out),
874        () if args::is(sub, b"links") => server.cluster_links(out),
875        () if args::is(sub, b"slaves") || args::is(sub, b"replicas") => {
876            replicas(server, args.get(2), out)?;
877        }
878        () if args::is(sub, b"count-failure-reports") => {
879            let map = server.cluster.map.lock();
880            let Some(at) = map.find(args.get(2)) else {
881                return Err(unknown_node(args.get(2)));
882            };
883            out.int(map.nodes[usize::from(at)].reports.len() as i64);
884        }
885        () if args::is(sub, b"countkeysinslot") => count_keys(server, session_db, args, out)?,
886        () if args::is(sub, b"getkeysinslot") => get_keys(server, session_db, args, out)?,
887        () if args::is(sub, b"addslots") => {
888            add_or_del(server, args, true, false)?;
889            out.ok();
890        }
891        () if args::is(sub, b"delslots") => {
892            add_or_del(server, args, false, false)?;
893            out.ok();
894        }
895        () if args::is(sub, b"addslotsrange") => {
896            add_or_del(server, args, true, true)?;
897            out.ok();
898        }
899        () if args::is(sub, b"delslotsrange") => {
900            add_or_del(server, args, false, true)?;
901            out.ok();
902        }
903        () if args::is(sub, b"setslot") => setslot(server, session_db, args, out)?,
904        () if args::is(sub, b"flushslots") => flushslots(server, out)?,
905        () if args::is(sub, b"bumpepoch") => bumpepoch(server, out)?,
906        () if args::is(sub, b"set-config-epoch") => set_config_epoch(server, args, out)?,
907        () if args::is(sub, b"reset") => {
908            if args.len() > 3 {
909                return Err(sub_syntax(sub));
910            }
911            reset(server, args, out)?;
912        }
913        () if args::is(sub, b"slot-stats") => slot_stats(server, session_db, args, out)?,
914        () if args::is(sub, b"migration") => migration(server, args, out)?,
915        () if args::is(sub, b"syncslots") => syncslots(server, session, args, out)?,
916        () if args::is(sub, b"saveconfig") => {
917            save(server)?;
918            out.ok();
919        }
920        () if args::is(sub, b"forget") => {
921            forget(server, args.get(2))?;
922            out.ok();
923        }
924        () if args::is(sub, b"replicate") => {
925            replicate(server, session_db, args.get(2))?;
926            out.ok();
927        }
928        () if args::is(sub, b"failover") => {
929            if args.len() > 3 {
930                return Err(sub_syntax(sub));
931            }
932            if args.len() == 3
933                && !args::is(args.get(2), b"force")
934                && !args::is(args.get(2), b"takeover")
935            {
936                return Err(args::syntax());
937            }
938            return Err(Error::new(
939                Code::Invalid,
940                "You should send CLUSTER FAILOVER to a replica",
941            ));
942        }
943        () if args::is(sub, b"meet") => {
944            if args.len() > 5 {
945                return Err(sub_syntax(sub));
946            }
947            let (host, port, bus) = meet(&args)?;
948            server.cluster_meet(&host, port, bus);
949            out.ok();
950        }
951        () if args::is(sub, b"help") => help(out),
952        _ => return Err(args::unknown_subcommand(sub, "CLUSTER")),
953    }
954    Ok(())
955}
956
957/// What every subcommand answers on a server that is not a cluster node.
958pub(super) fn disabled() -> Error {
959    Error::new(Code::Invalid, "This instance has cluster support disabled")
960}
961
962/// What atomic slot migration answers until it is in, which is D-149.
963///
964/// The sentence names the other way of moving a slot because there is one and it
965/// works, so an operator who reads this is one command away from getting on with
966/// it rather than stuck.
967fn not_yet(what: &str) -> Error {
968    Error::fmt(
969        Code::Invalid,
970        format_args!("{what} is not implemented yet, move the slot with SETSLOT and MIGRATE"),
971    )
972}
973
974/// The reference's sentence for a node id nobody has heard of.
975fn unknown_node(id: &[u8]) -> Error {
976    Error::fmt(
977        Code::Invalid,
978        format_args!("Unknown node {}", String::from_utf8_lossy(id)),
979    )
980}
981
982/// The same, in the spelling `CLUSTER SETSLOT` uses, which is a different
983/// sentence for the same thing and is the reference's.
984fn dont_know(id: &[u8]) -> Error {
985    Error::fmt(
986        Code::Invalid,
987        format_args!("I don't know about node {}", String::from_utf8_lossy(id)),
988    )
989}
990
991/// The arity of each subcommand, taken from the reference's own table, or `None`
992/// for a word that is not a subcommand.
993///
994/// A hand written list for the same reason `CONTAINERS` is one: the command
995/// table has a row for the container and none for what is behind it. It goes
996/// away with D-114.
997fn arity_of(sub: &[u8]) -> Option<i32> {
998    const TABLE: &[(&str, i32)] = &[
999        ("addslots", -3),
1000        ("addslotsrange", -4),
1001        ("bumpepoch", 2),
1002        ("count-failure-reports", 3),
1003        ("countkeysinslot", 3),
1004        ("delslots", -3),
1005        ("delslotsrange", -4),
1006        ("failover", -2),
1007        ("flushslots", 2),
1008        ("forget", 3),
1009        ("getkeysinslot", 4),
1010        ("help", 2),
1011        ("info", 2),
1012        ("keyslot", 3),
1013        ("links", 2),
1014        ("meet", -4),
1015        ("migration", -4),
1016        ("myid", 2),
1017        ("myshardid", 2),
1018        ("nodes", 2),
1019        ("replicas", 3),
1020        ("replicate", 3),
1021        ("reset", -2),
1022        ("saveconfig", 2),
1023        ("set-config-epoch", 3),
1024        ("setslot", -4),
1025        ("shards", 2),
1026        ("slaves", 3),
1027        ("slot-stats", -4),
1028        ("slots", 2),
1029        ("syncslots", -3),
1030    ];
1031    TABLE
1032        .iter()
1033        .find(|(name, _)| args::is(sub, name.as_bytes()))
1034        .map(|(_, arity)| *arity)
1035}
1036
1037/// Redis's arity rule: a positive number is exact and a negative one is a floor.
1038fn arity_ok(arity: i32, len: usize) -> bool {
1039    let len = len as i32;
1040    if arity >= 0 {
1041        len == arity
1042    } else {
1043        len >= -arity
1044    }
1045}
1046
1047/// What a subcommand says when its own parsing gave up.
1048///
1049/// The container's arity is a floor, so a subcommand that takes a fixed number
1050/// of words past that floor has to check the count itself, and when it fails the
1051/// reference does not say the arity was wrong, it says this. The subcommand is
1052/// echoed back as the caller typed it and the container is upper cased, which is
1053/// `addReplySubcommandSyntaxError` word for word.
1054fn sub_syntax(sub: &[u8]) -> Error {
1055    Error::fmt(
1056        Code::Unsupported,
1057        format_args!(
1058            "unknown subcommand or wrong number of arguments for '{}'. Try CLUSTER HELP.",
1059            String::from_utf8_lossy(sub)
1060        ),
1061    )
1062}
1063
1064/// The wrong arity complaint, which names the subcommand and not the container.
1065fn wrong_sub_arity(sub: &[u8]) -> Error {
1066    Error::fmt(
1067        Code::Invalid,
1068        format_args!(
1069            "wrong number of arguments for 'cluster|{}' command",
1070            String::from_utf8_lossy(sub).to_lowercase()
1071        ),
1072    )
1073}
1074
1075// -------------------------------------------------------------- the reports
1076
1077/// `CLUSTER INFO`, which is the same field names in the same order a real server
1078/// prints, as one bulk string rather than as a map.
1079fn info(server: &Server, out: &mut Out) {
1080    let (assigned, size, known, my_epoch) = {
1081        let map = server.cluster.map.lock();
1082        (
1083            map.assigned(),
1084            map.size(),
1085            map.nodes.len(),
1086            map.nodes[0].epoch,
1087        )
1088    };
1089    let state = if server.cluster_up() { "ok" } else { "fail" };
1090    let text = yo_alloc::allow(|| {
1091        let mut s = String::with_capacity(512);
1092        let _ = write!(
1093            s,
1094            "cluster_state:{state}\r\ncluster_slots_assigned:{assigned}\r\n\
1095             cluster_slots_ok:{assigned}\r\ncluster_slots_pfail:0\r\ncluster_slots_fail:0\r\n\
1096             cluster_known_nodes:{known}\r\ncluster_size:{size}\r\n\
1097             cluster_current_epoch:{}\r\ncluster_my_epoch:{my_epoch}\r\n\
1098             cluster_stats_messages_sent:0\r\ncluster_stats_messages_received:0\r\n\
1099             total_cluster_links_buffer_limit_exceeded:0\r\n\
1100             cluster_slot_migration_active_tasks:0\r\n\
1101             cluster_slot_migration_active_trim_running:0\r\n\
1102             cluster_slot_migration_active_trim_current_job_keys:0\r\n\
1103             cluster_slot_migration_active_trim_current_job_trimmed:0\r\n\
1104             cluster_slot_migration_stats_active_trim_started:0\r\n\
1105             cluster_slot_migration_stats_active_trim_completed:0\r\n\
1106             cluster_slot_migration_stats_active_trim_cancelled:0\r\n",
1107            server.cluster.epoch.load(Relaxed),
1108        );
1109        s
1110    });
1111    out.verbatim(b"txt", text.as_bytes());
1112}
1113
1114/// `CLUSTER NODES`, which is the same text this node writes to its config file.
1115fn nodes(server: &Server, out: &mut Out) {
1116    let text = yo_alloc::allow(|| lines(server, false));
1117    out.verbatim(b"txt", text.as_bytes());
1118}
1119
1120/// One line per node, in the reference's field order.
1121///
1122/// `<id> <ip:port@bus,aux> <flags> <master> <ping-sent> <pong-recv> <epoch>
1123/// <link-state> <slot> ...`, with the migrating and importing slots on the end
1124/// of the owner's line in square brackets.
1125fn lines(server: &Server, on_disk: bool) -> String {
1126    let map = server.cluster.map.lock();
1127    let mut s = String::with_capacity(256);
1128    for at in 0..map.nodes.len() as u16 {
1129        describe(&map, at, on_disk, &mut s);
1130        s.push('\n');
1131    }
1132    s
1133}
1134
1135/// One node's line, without the newline, which `CLUSTER REPLICAS` wants one at a
1136/// time and `CLUSTER NODES` wants all of.
1137fn describe(map: &Map, at: u16, on_disk: bool, s: &mut String) {
1138    let node = &map.nodes[usize::from(at)];
1139    s.push_str(&node.id);
1140    s.push(' ');
1141    if on_disk {
1142        node.address_on_disk(s);
1143    } else {
1144        node.address(s);
1145    }
1146    s.push(' ');
1147    node.flag_names(s);
1148    s.push(' ');
1149    match node.master.and_then(|m| map.nodes.get(usize::from(m))) {
1150        Some(master) => s.push_str(&master.id),
1151        None => s.push('-'),
1152    }
1153    // A replica reports its master's epoch rather than its own, which is what a
1154    // client reading the line is actually asking about.
1155    let epoch = match node.master.and_then(|m| map.nodes.get(usize::from(m))) {
1156        Some(master) => master.epoch,
1157        None => node.epoch,
1158    };
1159    let link = if node.linked || at == 0 {
1160        "connected"
1161    } else {
1162        "disconnected"
1163    };
1164    let _ = write!(s, " {} {} {epoch} {link}", node.ping_sent, node.pong_recv);
1165    for (from, to) in map.runs(at) {
1166        if from == to {
1167            let _ = write!(s, " {from}");
1168        } else {
1169            let _ = write!(s, " {from}-{to}");
1170        }
1171    }
1172    if at == 0 {
1173        for slot in 0..SLOTS {
1174            if let Some(to) = map.migrating[slot] {
1175                let _ = write!(s, " [{slot}->-{}]", map.nodes[to as usize].id);
1176            }
1177            if let Some(from) = map.importing[slot] {
1178                let _ = write!(s, " [{slot}-<-{}]", map.nodes[from as usize].id);
1179            }
1180        }
1181    }
1182}
1183
1184/// `CLUSTER SLOTS`, which is one entry per run of slots one node owns.
1185///
1186/// The runs come out in slot order and not in node order, because that is the
1187/// order the reference walks and a client that caches this reply by position
1188/// will notice the difference.
1189fn reply_slots(server: &Server, out: &mut Out) {
1190    let mine = server.repl_offset();
1191    let map = server.cluster.map.lock();
1192    let at = out.len();
1193    let mut n = 0;
1194    let mut run: Option<(u16, u16)> = None;
1195    for slot in 0..=SLOTS as u16 {
1196        let owner = if slot as usize == SLOTS {
1197            None
1198        } else {
1199            map.owner[slot as usize]
1200        };
1201        match run {
1202            Some((node, _)) if owner == Some(node) => {}
1203            Some((node, from)) => {
1204                slot_run(&map, node, from, slot - 1, mine, out);
1205                n += 1;
1206                run = owner.map(|node| (node, slot));
1207            }
1208            None => run = owner.map(|node| (node, slot)),
1209        }
1210    }
1211    out.close_array(at, n);
1212}
1213
1214/// One `CLUSTER SLOTS` entry: the run, its owner, then the owner's replicas.
1215///
1216/// A replica everybody agrees is gone is left out, and so is one whose
1217/// replication offset is still zero, which is the reference's own filter and is
1218/// its way of saying this replica has never caught up with anything and is not
1219/// somewhere to send a reader yet. This node's own offset does not come off the
1220/// table, since nothing gossips a node its own offset, so it is passed in.
1221fn slot_run(map: &Map, node: u16, from: u16, to: u16, mine: u64, out: &mut Out) {
1222    let replicas: Vec<&Node> = map
1223        .nodes
1224        .iter()
1225        .enumerate()
1226        .filter(|(at, n)| {
1227            let offset = if *at == 0 { mine } else { n.offset };
1228            n.master == Some(node) && n.flags & FLAG_FAIL == 0 && offset != 0
1229        })
1230        .map(|(_, n)| n)
1231        .collect();
1232    out.array(3 + replicas.len());
1233    out.int(i64::from(from));
1234    out.int(i64::from(to));
1235    let held = &map.nodes[node as usize];
1236    for held in std::iter::once(held).chain(replicas.iter().copied()) {
1237        out.array(4);
1238        out.bulk(held.host.as_bytes());
1239        out.int(i64::from(held.port));
1240        out.bulk(held.id.as_bytes());
1241        out.array(0);
1242    }
1243}
1244
1245/// `CLUSTER SHARDS`, which is the same information grouped by shard rather than
1246/// by run, and is what a client library reads to find the replicas of a master.
1247fn shards(server: &Server, out: &mut Out) {
1248    let map = server.cluster.map.lock();
1249    let at = out.len();
1250    let mut n = 0;
1251    let mut done: Vec<&str> = Vec::new();
1252    for node in 0..map.nodes.len() {
1253        let shard = map.nodes[node].shard.as_str();
1254        if done.contains(&shard) {
1255            continue;
1256        }
1257        done.push(shard);
1258        let members: Vec<usize> = (0..map.nodes.len())
1259            .filter(|other| map.nodes[*other].shard == shard)
1260            .collect();
1261        // The slots of a shard are the slots of whichever member is holding
1262        // them, which is the master of it except for the moment after a
1263        // failover when two members still think they are.
1264        let runs: Vec<(u16, u16)> = members
1265            .iter()
1266            .flat_map(|member| map.runs(*member as u16))
1267            .collect();
1268        out.map(2);
1269        out.bulk(b"slots");
1270        out.array(runs.len() * 2);
1271        for (from, to) in runs {
1272            out.int(i64::from(from));
1273            out.int(i64::from(to));
1274        }
1275        out.bulk(b"nodes");
1276        out.array(members.len());
1277        for member in members {
1278            let held = &map.nodes[member];
1279            out.map(7);
1280            out.bulk(b"id");
1281            out.bulk(held.id.as_bytes());
1282            out.bulk(b"port");
1283            out.int(i64::from(held.port));
1284            out.bulk(b"ip");
1285            out.bulk(held.host.as_bytes());
1286            out.bulk(b"endpoint");
1287            out.bulk(held.host.as_bytes());
1288            out.bulk(b"role");
1289            out.bulk(if held.is_master() {
1290                b"master".as_slice()
1291            } else {
1292                b"replica".as_slice()
1293            });
1294            out.bulk(b"replication-offset");
1295            out.int(if member == 0 {
1296                server.repl_offset() as i64
1297            } else {
1298                held.offset as i64
1299            });
1300            out.bulk(b"health");
1301            out.bulk(match held.flags {
1302                f if f & FLAG_FAIL != 0 => b"fail".as_slice(),
1303                f if f & FLAG_PFAIL != 0 => b"loading".as_slice(),
1304                _ => b"online".as_slice(),
1305            });
1306        }
1307        n += 1;
1308    }
1309    out.close_array(at, n);
1310}
1311
1312/// The help text, word for word from the reference.
1313fn help(out: &mut Out) {
1314    const LINES: &[&str] = &[
1315        "CLUSTER <subcommand> [<arg> [value] [opt] ...]. Subcommands are:",
1316        "COUNTKEYSINSLOT <slot>",
1317        "    Return the number of keys in <slot>.",
1318        "GETKEYSINSLOT <slot> <count>",
1319        "    Return key names stored by current node in a slot.",
1320        "INFO",
1321        "    Return information about the cluster.",
1322        "KEYSLOT <key>",
1323        "    Return the hash slot for <key>.",
1324        "MYID",
1325        "    Return the node id.",
1326        "MYSHARDID",
1327        "    Return the node's shard id.",
1328        "NODES",
1329        "    Return cluster configuration seen by node. Output format:",
1330        "    <id> <ip:port@bus-port[,hostname]> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ...",
1331        "REPLICAS <node-id>",
1332        "    Return <node-id> replicas.",
1333        "SLOTS",
1334        "    Return information about slots range mappings. Each range is made of:",
1335        "    start, end, master and replicas IP addresses, ports and ids",
1336        "SLOT-STATS",
1337        "    Return an array of slot usage statistics for slots assigned to the current node.",
1338        "SHARDS",
1339        "    Return information about slot range mappings and the nodes associated with them.",
1340        "ADDSLOTS <slot> [<slot> ...]",
1341        "    Assign slots to current node.",
1342        "ADDSLOTSRANGE <start slot> <end slot> [<start slot> <end slot> ...]",
1343        "    Assign slots which are between <start-slot> and <end-slot> to current node.",
1344        "BUMPEPOCH",
1345        "    Advance the cluster config epoch.",
1346        "COUNT-FAILURE-REPORTS <node-id>",
1347        "    Return number of failure reports for <node-id>.",
1348        "DELSLOTS <slot> [<slot> ...]",
1349        "    Delete slots information from current node.",
1350        "DELSLOTSRANGE <start slot> <end slot> [<start slot> <end slot> ...]",
1351        "    Delete slots information which are between <start-slot> and <end-slot> from current node.",
1352        "FAILOVER [FORCE|TAKEOVER]",
1353        "    Promote current replica node to being a master.",
1354        "FORGET <node-id>",
1355        "    Remove a node from the cluster.",
1356        "FLUSHSLOTS",
1357        "    Delete current node own slots information.",
1358        "MEET <ip> <port> [<bus-port>]",
1359        "    Connect nodes into a working cluster.",
1360        "REPLICATE <node-id>",
1361        "    Configure current node as replica to <node-id>.",
1362        "RESET [HARD|SOFT]",
1363        "    Reset current node (default: soft).",
1364        "SET-CONFIG-EPOCH <epoch>",
1365        "    Set config epoch of current node.",
1366        "SETSLOT <slot> (IMPORTING <node-id>|MIGRATING <node-id>|STABLE|NODE <node-id>)",
1367        "    Set slot state.",
1368        "SAVECONFIG",
1369        "    Force saving cluster configuration on disk.",
1370        "LINKS",
1371        "    Return information about all network links between this node and its peers.",
1372        "    Output format is an array where each array element is a map containing attributes of a link",
1373        "MIGRATION IMPORT <start-slot end-slot [start-slot end-slot ...]> |",
1374        "          STATUS [ID <task-id> | ALL] | CANCEL [ID <task-id> | ALL]",
1375        "    Start, monitor and cancel slot migration.",
1376        "HELP",
1377        "    Print this help.",
1378    ];
1379    out.array(LINES.len());
1380    for line in LINES {
1381        out.simple(line.as_bytes());
1382    }
1383}
1384
1385// ------------------------------------------------------------ the slot moves
1386
1387/// One slot number from an argument, refused the way the reference refuses one.
1388///
1389/// One sentence covers both ways of getting it wrong, which is the reference's
1390/// `getSlotOrReply` and is not the same as what `COUNTKEYSINSLOT` says: a word
1391/// that is not a number and a number that is not a slot both come back as
1392/// `Invalid or out of range slot` here.
1393fn slot_arg(args: &Args<'_>, at: usize) -> Result<u16> {
1394    args.int(at)
1395        .ok()
1396        .and_then(|n| u16::try_from(n).ok())
1397        .filter(|s| usize::from(*s) < SLOTS)
1398        .ok_or_else(|| Error::new(Code::Invalid, "Invalid or out of range slot"))
1399}
1400
1401/// `CLUSTER ADDSLOTS`, `DELSLOTS`, `ADDSLOTSRANGE` and `DELSLOTSRANGE`.
1402///
1403/// All four in one body because all four are the same walk with a different
1404/// stride and a different word in the complaint. Every slot is checked before
1405/// any of them is moved, which is the reference's behaviour and is what makes a
1406/// list with one bad number in it change nothing at all.
1407fn add_or_del(server: &Server, args: Args<'_>, add: bool, ranged: bool) -> Result<()> {
1408    let stride = if ranged { 2 } else { 1 };
1409    if ranged && !(args.len() - 2).is_multiple_of(2) {
1410        return Err(wrong_sub_arity(args.get(1)));
1411    }
1412    let mut wanted = Vec::new();
1413    let mut at = 2;
1414    while at < args.len() {
1415        let from = slot_arg(&args, at)?;
1416        let to = if ranged {
1417            slot_arg(&args, at + 1)?
1418        } else {
1419            from
1420        };
1421        if from > to {
1422            return Err(Error::fmt(
1423                Code::Invalid,
1424                format_args!("start slot number {from} is greater than end slot number {to}"),
1425            ));
1426        }
1427        for slot in from..=to {
1428            wanted.push(slot);
1429        }
1430        at += stride;
1431    }
1432    {
1433        let mut map = server.cluster.map.lock();
1434        let mut seen = vec![false; SLOTS];
1435        for slot in &wanted {
1436            let slot = usize::from(*slot);
1437            if seen[slot] {
1438                return Err(Error::fmt(
1439                    Code::Invalid,
1440                    format_args!("Slot {slot} specified multiple times"),
1441                ));
1442            }
1443            seen[slot] = true;
1444            let busy = map.owner[slot].is_some();
1445            if add && busy {
1446                return Err(Error::fmt(
1447                    Code::Invalid,
1448                    format_args!("Slot {slot} is already busy"),
1449                ));
1450            }
1451            if !add && !busy {
1452                return Err(Error::fmt(
1453                    Code::Invalid,
1454                    format_args!("Slot {slot} is already unassigned"),
1455                ));
1456            }
1457        }
1458        for slot in &wanted {
1459            let slot = usize::from(*slot);
1460            map.owner[slot] = if add { Some(0) } else { None };
1461            map.migrating[slot] = None;
1462            map.importing[slot] = None;
1463        }
1464    }
1465    server.recount_coverage();
1466    save(server)
1467}
1468
1469/// The address `CLUSTER MEET` was given, checked the way the reference checks it.
1470///
1471/// The port is reported back as the caller typed it rather than as it parsed,
1472/// which is the reference's wording and matters for a port that parsed fine and
1473/// was out of range.
1474fn meet(args: &Args<'_>) -> Result<(String, u16, u16)> {
1475    let host = String::from_utf8_lossy(args.get(2));
1476    let typed = String::from_utf8_lossy(args.get(3));
1477    let port = args.int(3).map_err(|_| {
1478        Error::fmt(
1479            Code::Invalid,
1480            format_args!("Invalid base port specified: {typed}"),
1481        )
1482    })?;
1483    let bus = match args.opt(4) {
1484        None => port + i64::from(BUS_OFFSET),
1485        Some(word) => args.int(4).map_err(|_| {
1486            Error::fmt(
1487                Code::Invalid,
1488                format_args!(
1489                    "Invalid bus port specified: {}",
1490                    String::from_utf8_lossy(word)
1491                ),
1492            )
1493        })?,
1494    };
1495    if !(1..=65535).contains(&port) || !(0..=65535).contains(&bus) {
1496        return Err(Error::fmt(
1497            Code::Invalid,
1498            format_args!("Invalid node address specified: {host}:{typed}"),
1499        ));
1500    }
1501    let bus = if bus == 0 {
1502        port + i64::from(BUS_OFFSET)
1503    } else {
1504        bus
1505    };
1506    let host = yo_alloc::allow(|| host.into_owned());
1507    Ok((host, port as u16, bus as u16))
1508}
1509
1510/// `CLUSTER REPLICAS`, which is one `CLUSTER NODES` line per replica of a node.
1511fn replicas(server: &Server, id: &[u8], out: &mut Out) -> Result<()> {
1512    let map = server.cluster.map.lock();
1513    let Some(at) = map.find(id) else {
1514        return Err(unknown_node(id));
1515    };
1516    if !map.nodes[usize::from(at)].is_master() {
1517        return Err(Error::new(
1518            Code::Invalid,
1519            "The specified node is not a master",
1520        ));
1521    }
1522    let start = out.len();
1523    let mut n = 0;
1524    for other in 0..map.nodes.len() as u16 {
1525        if map.nodes[usize::from(other)].master != Some(at) {
1526            continue;
1527        }
1528        let line = yo_alloc::allow(|| {
1529            let mut s = String::with_capacity(256);
1530            describe(&map, other, false, &mut s);
1531            s
1532        });
1533        out.bulk(line.as_bytes());
1534        n += 1;
1535    }
1536    out.close_array(start, n);
1537    Ok(())
1538}
1539
1540/// `CLUSTER FORGET`, which drops a node and makes the drop stick.
1541///
1542/// An id nobody has heard of is an error unless it is one this node forgot on
1543/// purpose a moment ago, in which case the answer is OK, because a tool that
1544/// sends the same forget to every node in the cluster should not hear an error
1545/// from the ones that had already been told by gossip.
1546fn forget(server: &Server, id: &[u8]) -> Result<()> {
1547    let at = {
1548        let map = server.cluster.map.lock();
1549        match map.find(id) {
1550            Some(0) => {
1551                return Err(Error::new(
1552                    Code::Invalid,
1553                    "I tried hard but I can't forget myself...",
1554                ));
1555            }
1556            Some(at) if map.nodes[0].master == Some(at) => {
1557                return Err(Error::new(Code::Invalid, "Can't forget my master!"));
1558            }
1559            Some(at) => at,
1560            None => {
1561                let name = String::from_utf8_lossy(id);
1562                if server.cluster_blacklisted(&name) {
1563                    return Ok(());
1564                }
1565                return Err(unknown_node(id));
1566            }
1567        }
1568    };
1569    server.cluster_forget(at);
1570    Ok(())
1571}
1572
1573/// `CLUSTER REPLICATE`, which makes this node a replica of another.
1574///
1575/// A master with slots or keys refuses, which is the reference's rule and is the
1576/// right one: the slots would be given away silently and the keys in them would
1577/// be answered for by two nodes at once.
1578fn replicate(server: &Server, db: usize, id: &[u8]) -> Result<()> {
1579    let at = {
1580        let map = server.cluster.map.lock();
1581        match map.find(id) {
1582            None => return Err(unknown_node(id)),
1583            Some(0) => return Err(Error::new(Code::Invalid, "Can't replicate myself")),
1584            Some(at) if !map.nodes[usize::from(at)].is_master() => {
1585                return Err(Error::new(
1586                    Code::Invalid,
1587                    "I can only replicate a master, not a replica.",
1588                ));
1589            }
1590            Some(at) => {
1591                if map.nodes[0].is_master()
1592                    && (!map.runs(0).is_empty() || !server.dbs[db].is_empty())
1593                {
1594                    return Err(Error::new(
1595                        Code::Invalid,
1596                        "To set a master the node must be empty and without assigned slots.",
1597                    ));
1598                }
1599                at
1600            }
1601        }
1602    };
1603    let Some(shared) = server.myself() else {
1604        return Err(Error::new(
1605            Code::Invalid,
1606            "CLUSTER REPLICATE is not available on an embedded server",
1607        ));
1608    };
1609    shared.cluster_replicate(at);
1610    Ok(())
1611}
1612
1613/// `CLUSTER SLOT-STATS`, which is how many keys each slot this node owns holds.
1614///
1615/// A real server keeps more metrics than this one and only when
1616/// `cluster-slot-stats-enabled` is on, so the default answer is the key count on
1617/// its own and that is what is here. The count is one walk of the keyspace with a
1618/// tally per slot rather than one walk per slot, which is the same D-150 cost as
1619/// `COUNTKEYSINSLOT` paid once instead of sixteen thousand times.
1620fn slot_stats(server: &Server, db: usize, args: Args<'_>, out: &mut Out) -> Result<()> {
1621    let sub = args.get(1);
1622    let mut wanted: Vec<u16> = Vec::new();
1623    let mut limit = SLOTS;
1624    let mut ascending = false;
1625    let ordered = args::is(args.get(2), b"orderby");
1626    if args::is(args.get(2), b"slotsrange") {
1627        if args.len() != 5 {
1628            return Err(sub_syntax(sub));
1629        }
1630        let from = slot_arg(&args, 3)?;
1631        let to = slot_arg(&args, 4)?;
1632        if from > to {
1633            return Err(Error::fmt(
1634                Code::Invalid,
1635                format_args!("Start slot number {from} is greater than end slot number {to}"),
1636            ));
1637        }
1638        wanted.extend(from..=to);
1639    } else if ordered {
1640        if !args::is(args.get(3), b"key-count") {
1641            return Err(Error::new(
1642                Code::Invalid,
1643                "Unrecognized sort metric for ORDERBY.",
1644            ));
1645        }
1646        let bad_limit = || {
1647            Error::new(
1648                Code::Invalid,
1649                "Limit has to lie in between 1 and 16384 (maximum number of slots).",
1650            )
1651        };
1652        let mut at = 4;
1653        while at < args.len() {
1654            let word = args.get(at);
1655            if args::is(word, b"limit") && at + 1 < args.len() {
1656                let n = args.int(at + 1).map_err(|_| bad_limit())?;
1657                if !(1..=SLOTS as i64).contains(&n) {
1658                    return Err(bad_limit());
1659                }
1660                limit = n as usize;
1661                at += 2;
1662            } else if args::is(word, b"asc") {
1663                ascending = true;
1664                at += 1;
1665            } else if args::is(word, b"desc") {
1666                at += 1;
1667            } else {
1668                return Err(args::syntax());
1669            }
1670        }
1671        wanted.extend(0..SLOTS as u16);
1672    } else {
1673        return Err(sub_syntax(sub));
1674    }
1675    let mut counts = vec![0i64; SLOTS];
1676    server.dbs[db].keys(|key| counts[key_slot(key) as usize] += 1);
1677    let map = server.cluster.map.lock();
1678    wanted.retain(|slot| map.mine(*slot));
1679    drop(map);
1680    if ordered {
1681        // The tie is broken by slot number ascending either way round, which is
1682        // what a real server does with a table nothing has been written to yet.
1683        if ascending {
1684            wanted.sort_by_key(|slot| (counts[*slot as usize], *slot));
1685        } else {
1686            wanted.sort_by_key(|slot| (-counts[*slot as usize], *slot));
1687        }
1688        wanted.truncate(limit);
1689    }
1690    out.array(wanted.len());
1691    for slot in wanted {
1692        out.array(2);
1693        out.int(i64::from(slot));
1694        out.map(1);
1695        out.bulk(b"key-count");
1696        out.int(counts[slot as usize]);
1697    }
1698    Ok(())
1699}
1700
1701/// `CLUSTER MIGRATION IMPORT|STATUS|CANCEL`, which is the newer way of moving a
1702/// slot range: the importing node drives the whole move rather than a tool
1703/// stepping it key by key.
1704///
1705/// `STATUS` and `CANCEL` report and cancel what is really running, which on this
1706/// node is a migration another node started against it. Starting one from here,
1707/// which is what `IMPORT` is, is the other side of the protocol and is D-149.
1708fn migration(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
1709    let sub = args.get(1);
1710    let action = args.get(2);
1711    if args::is(action, b"status") || args::is(action, b"cancel") {
1712        let by_id = args::is(args.get(3), b"id");
1713        if by_id && args.len() != 5 {
1714            return Err(wrong_sub_arity(sub));
1715        }
1716        if !by_id && !args::is(args.get(3), b"all") {
1717            return Err(Error::new(Code::Invalid, "unknown argument"));
1718        }
1719        if !by_id && args.len() != 4 {
1720            return Err(wrong_sub_arity(sub));
1721        }
1722        let id = by_id.then(|| args.get(4));
1723        if args::is(action, b"status") {
1724            match id {
1725                Some(id) => server.cluster.asm.report_one(id, out),
1726                None => server.cluster.asm.report_all(out),
1727            }
1728        } else {
1729            out.int(server.cluster.asm.cancel(id, server.now_ms() as i64));
1730            // A cancelled handoff is not one whose write pause anybody should
1731            // have to wait out.
1732            server.asm_relax();
1733        }
1734        return Ok(());
1735    }
1736    if !args::is(action, b"import") {
1737        return Err(Error::new(Code::Invalid, "unknown argument"));
1738    }
1739    let ranges = slot_ranges(&args, 3)?;
1740    let map = server.cluster.map.lock();
1741    if ranges
1742        .iter()
1743        .flat_map(|(from, to)| *from..=*to)
1744        .all(|slot| map.mine(slot))
1745    {
1746        return Err(Error::new(
1747            Code::Invalid,
1748            "this node is already the owner of the slot range",
1749        ));
1750    }
1751    drop(map);
1752    Err(not_yet("CLUSTER MIGRATION IMPORT"))
1753}
1754
1755/// The list of slot ranges the tail of an argument list spells out, which is the
1756/// reference's `parseSlotRangesOrReply` and the validation behind it.
1757///
1758/// Sorted and with the ranges that touch joined up, because that is what the
1759/// reference hands back and the two checks after it are made against the joined
1760/// up list rather than what the caller typed. Ranges that touch join and ranges
1761/// that overlap do not, which is what makes `1 2 2 3` a slot named twice while
1762/// `1 2 3 4` is one range of four.
1763fn slot_ranges(args: &Args<'_>, from: usize) -> Result<Vec<(u16, u16)>> {
1764    let count = args.len().saturating_sub(from);
1765    if count < 2 || !count.is_multiple_of(2) {
1766        return Err(wrong_sub_arity(args.get(1)));
1767    }
1768    if count / 2 >= SLOTS {
1769        return Err(Error::fmt(
1770            Code::Invalid,
1771            format_args!("invalid number of slot ranges: {}", count / 2),
1772        ));
1773    }
1774    let mut ranges: Vec<(u16, u16)> = Vec::with_capacity(count / 2);
1775    let mut at = from;
1776    while at < args.len() {
1777        ranges.push((slot_arg(args, at)?, slot_arg(args, at + 1)?));
1778        at += 2;
1779    }
1780    ranges.sort_unstable();
1781    let mut joined: Vec<(u16, u16)> = Vec::with_capacity(ranges.len());
1782    for range in ranges {
1783        match joined.last_mut() {
1784            Some(last) if u32::from(last.1) + 1 == u32::from(range.0) => last.1 = range.1,
1785            _ => joined.push(range),
1786        }
1787    }
1788    let mut seen = vec![false; SLOTS];
1789    for &(start, end) in &joined {
1790        if start > end {
1791            return Err(Error::fmt(
1792                Code::Invalid,
1793                format_args!("start slot number {start} is greater than end slot number {end}"),
1794            ));
1795        }
1796        for slot in start..=end {
1797            if core::mem::replace(&mut seen[usize::from(slot)], true) {
1798                return Err(Error::fmt(
1799                    Code::Invalid,
1800                    format_args!("Slot {slot} specified multiple times"),
1801                ));
1802            }
1803        }
1804    }
1805    Ok(joined)
1806}
1807
1808/// `CLUSTER SYNCSLOTS`, which is what two nodes say to each other while a slot
1809/// range moves from one to the other under atomic slot migration.
1810///
1811/// Not a command an operator ever types. The node taking the slots opens an
1812/// ordinary client connection to the node giving them up, logs in as the cluster
1813/// rather than as a user, and drives the whole move over that connection and one
1814/// more like it. So every arm here answers another node running the same code,
1815/// which is why the reference does not defend the state machine against being
1816/// driven out of order and why it hangs up on anybody who is not a node rather
1817/// than just saying no. Getting to the point of being able to send this is the
1818/// hard part and the refusal makes it worthless.
1819///
1820/// What is in so far is the gate, the argument checking and the two arms that
1821/// have a real answer on a node with no migration running, which is every node
1822/// today. Starting a migration is D-149.
1823fn syncslots(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Result<()> {
1824    if !session.internal() {
1825        // The hang up is the reference's and it is worth copying. The sentence on
1826        // its own would be enough to be correct, and dropping the connection is
1827        // what makes sitting there guessing at the protocol expensive.
1828        session.hang_up();
1829        return Err(Error::new(
1830            Code::Invalid,
1831            "CLUSTER SYNCSLOTS subcommands are only allowed for internal clients",
1832        ));
1833    }
1834    let action = args.get(2);
1835    if !server.cluster.map.lock().nodes[0].is_master() {
1836        // A replica owns nothing, so there is nothing anybody could be asking it
1837        // to hand over. The one thing it does take is `CONF`, and only from its
1838        // own master, which is how it hears about the migration its master is in
1839        // the middle of. Anything else from the master is dropped in silence
1840        // rather than refused, because an error written into the replication
1841        // stream is an error nobody reads.
1842        if !session.serving_master() {
1843            session.hang_up();
1844            return Err(Error::new(
1845                Code::Invalid,
1846                "CLUSTER SYNCSLOTS subcommands are only allowed for master",
1847            ));
1848        }
1849        if !args::is(action, b"conf") {
1850            return Ok(());
1851        }
1852    }
1853    if args::is(action, b"sync") && args.len() >= 6 {
1854        return sync(server, session, args, out);
1855    }
1856    if args::is(action, b"rdbchannel") && args.len() == 4 {
1857        return rdbchannel(server, session, args, out);
1858    }
1859    if (args::is(action, b"snapshot-eof") || args::is(action, b"stream-eof")) && args.len() == 3 {
1860        // Both of these say a transfer has ended, and on a node with no transfer
1861        // running the reference logs that it was not expecting them and drops the
1862        // connection without writing anything back.
1863        session.hang_up();
1864        return Ok(());
1865    }
1866    if args::is(action, b"ack") && args.len() == 5 {
1867        // The arm that answers nothing. This connection's other direction is the
1868        // change stream, and anything written back on it would be read as a
1869        // command, so a bad state word or a number that is not one is dropped in
1870        // silence rather than refused.
1871        if let Some(offset) = yo_common::num::parse_i64(args.get(4))
1872            && offset >= 0
1873        {
1874            server.asm_ack(session.row().id, args.get(3), offset as u64);
1875        }
1876        return Ok(());
1877    }
1878    if args::is(action, b"fail") && args.len() == 4 {
1879        // The other arm that never answers. `FAIL` does nothing at all on the
1880        // reference either: it is there so that the far side has something to
1881        // send that will not come back as a syntax error.
1882        return Ok(());
1883    }
1884    if args::is(action, b"conf") && args.len() >= 5 {
1885        return conf(server, session, args, out);
1886    }
1887    Err(args::syntax())
1888}
1889
1890/// `CLUSTER SYNCSLOTS SYNC <task-id> <start> <end> [<start> <end> ...]`, which is
1891/// the node taking the slots asking for them.
1892///
1893/// Every check the reference makes before it starts is made here, in its order,
1894/// because they are what a caller reads to find out it asked the wrong node.
1895///
1896/// What comes back on the way through is `+RDBCHANNELSYNCSLOTS`, which is the far
1897/// side being told to open the second connection the snapshot will come down.
1898/// Nothing else is written to this connection yet: it is the one the changes
1899/// since the snapshot would go down, and those are D-149.
1900fn sync(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Result<()> {
1901    if !args.len().is_multiple_of(2) {
1902        return Err(wrong_sub_arity(args.get(1)));
1903    }
1904    let ranges = slot_ranges(&args, 4)?;
1905    {
1906        let map = server.cluster.map.lock();
1907        // The two ways of moving a slot do not mix. A slot that is half way
1908        // through the old one has keys on two nodes at once and a snapshot of it
1909        // would be a snapshot of half the data.
1910        if (0..SLOTS).any(|at| map.migrating[at].is_some() || map.importing[at].is_some()) {
1911            return Err(Error::new(
1912                Code::Invalid,
1913                "all slot states must be STABLE to start a slot migration task.",
1914            ));
1915        }
1916        let mut source = None;
1917        for slot in ranges.iter().flat_map(|(from, to)| *from..=*to) {
1918            let Some(owner) = map.owner[usize::from(slot)] else {
1919                return Err(Error::fmt(
1920                    Code::Invalid,
1921                    format_args!("slot has no owner: {slot}"),
1922                ));
1923            };
1924            if *source.get_or_insert(owner) != owner {
1925                return Err(Error::new(
1926                    Code::Invalid,
1927                    "slots belong to different source nodes",
1928                ));
1929            }
1930        }
1931        if source != Some(0) {
1932            return Err(Error::new(
1933                Code::Invalid,
1934                "This node is not the owner of the slots",
1935            ));
1936        }
1937        // A node that is not in the table, or that is somebody's replica, has
1938        // nowhere to put a slot. The far side only says who it is when it sent
1939        // `CONF NODE-ID` first, and the reference lets a connection that did not
1940        // through, so this is checked only when there is something to check.
1941        let dest = session.node_id().to_vec();
1942        if !dest.is_empty()
1943            && !map
1944                .find(&dest)
1945                .is_some_and(|at| map.nodes[at as usize].is_master())
1946        {
1947            return Err(Error::fmt(
1948                Code::Invalid,
1949                format_args!(
1950                    "Destination node {} is not a master",
1951                    String::from_utf8_lossy(&dest)
1952                ),
1953            ));
1954        }
1955    }
1956    server.asm_begin_migrate(args.get(3), session.node_id(), ranges, session.row())?;
1957    out.simple(b"RDBCHANNELSYNCSLOTS");
1958    Ok(())
1959}
1960
1961/// `CLUSTER SYNCSLOTS RDBCHANNEL <task-id>`, which is the second connection of a
1962/// migration arriving to be handed the snapshot.
1963///
1964/// The reference answers `+SLOTSSNAPSHOT` and then forks, and the snapshot goes
1965/// out of the child while the parent carries on serving. There is no fork here,
1966/// so the snapshot is built with every write on the server held off and then
1967/// written, which is the same trade a full resync makes and is why the snapshot
1968/// is one slot range rather than the whole keyspace.
1969fn rdbchannel(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Result<()> {
1970    let id = args.get(3);
1971    if id.len() != ID_LEN {
1972        return Err(Error::new(Code::Invalid, "Invalid task id"));
1973    }
1974    let ranges = server.asm_take_rdb_channel(id, session.row().id)?;
1975    out.simple(b"SLOTSSNAPSHOT");
1976    out.raw(&server.asm_snapshot(&ranges));
1977    Ok(())
1978}
1979
1980/// `CLUSTER SYNCSLOTS CONF <option> <value> [<option> <value> ...]`, which is
1981/// each side telling the other something it will need in a moment.
1982///
1983/// Three of the four options are the node that is talking, a hint about how big a
1984/// slot is so the receiving side can size its tables once instead of growing them
1985/// all the way up, and a capability word that is ignored on purpose so that a
1986/// newer node can say something an older one has never heard of. The fourth is a
1987/// master handing its own replicas the state of the migration it is in.
1988///
1989/// Every complaint here is written rather than returned, because the reference
1990/// keeps going after an option it did not understand and still says `OK` at the
1991/// end, so one command can answer with both an error line and an `OK`. Returning
1992/// would throw the first of those away.
1993fn conf(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Result<()> {
1994    let mut at = 3;
1995    while at < args.len() {
1996        if at + 1 >= args.len() {
1997            super::write_error(out, &wrong_sub_arity(args.get(1)));
1998            return Ok(());
1999        }
2000        let name = args.get(at);
2001        let value = args.get(at + 1);
2002        if args::is(name, b"node-id") {
2003            // Who is on the other end of this connection, which is what the
2004            // `SYNC` after it is checked against and what the task it starts
2005            // records as the node the slots are going to.
2006            if value.len() != ID_LEN {
2007                let len = value.len();
2008                super::write_error(
2009                    out,
2010                    &Error::fmt(Code::Invalid, format_args!("Invalid node id length {len}")),
2011                );
2012                return Ok(());
2013            }
2014            if server.cluster.map.lock().find(value).is_none() {
2015                super::write_error(
2016                    out,
2017                    &Error::fmt(
2018                        Code::Invalid,
2019                        format_args!(
2020                            "Node {} not found in cluster",
2021                            String::from_utf8_lossy(value)
2022                        ),
2023                    ),
2024                );
2025                return Ok(());
2026            }
2027            session.set_node_id(value);
2028        } else if args::is(name, b"slot-info") {
2029            if !slot_info(value) {
2030                super::write_error(
2031                    out,
2032                    &Error::fmt(
2033                        Code::Invalid,
2034                        format_args!("Invalid slot info: {}", String::from_utf8_lossy(value)),
2035                    ),
2036                );
2037                return Ok(());
2038            }
2039        } else if args::is(name, b"asm-task") {
2040            if server.cluster.map.lock().nodes[0].is_master() {
2041                super::write_error(
2042                    out,
2043                    &Error::new(
2044                        Code::Invalid,
2045                        "CLUSTER SYNCSLOTS CONF ASM-TASK only allowed on replica",
2046                    ),
2047                );
2048                return Ok(());
2049            }
2050            // A replica that hears this is being told to follow along with a
2051            // migration its master is running, and there is nothing here to
2052            // follow along with yet, so it says so and carries on with the rest
2053            // of the options exactly as the reference does.
2054            super::write_error(
2055                out,
2056                &Error::fmt(
2057                    Code::Invalid,
2058                    format_args!(
2059                        "Failed to handle master task: {}",
2060                        String::from_utf8_lossy(value)
2061                    ),
2062                ),
2063            );
2064        } else if !args::is(name, b"capa") {
2065            super::write_error(
2066                out,
2067                &Error::fmt(
2068                    Code::Invalid,
2069                    format_args!("Unknown option {}", String::from_utf8_lossy(name)),
2070                ),
2071            );
2072        }
2073        at += 2;
2074    }
2075    out.ok();
2076    Ok(())
2077}
2078
2079/// Whether a `slot-info` value is one, which is `slot:key-count:expire-count`
2080/// with all three of them numbers and the first of them a slot.
2081///
2082/// What it is for is sizing: the node about to receive a slot is being told how
2083/// many keys are coming so that it can make room for them in one go. Nothing is
2084/// sized on it here, because the tables this engine keeps a slot's keys in grow
2085/// without the rehash a size hint exists to avoid, so the value is only checked.
2086fn slot_info(value: &[u8]) -> bool {
2087    let mut parts = value.split(|b| *b == b':');
2088    let Some(slot) = parts.next().and_then(yo_common::num::parse_i64) else {
2089        return false;
2090    };
2091    let Some(keys) = parts.next().and_then(yo_common::num::parse_i64) else {
2092        return false;
2093    };
2094    let Some(expires) = parts.next().and_then(yo_common::num::parse_i64) else {
2095        return false;
2096    };
2097    parts.next().is_none() && (0..SLOTS as i64).contains(&slot) && keys >= 0 && expires >= 0
2098}
2099
2100/// `CLUSTER SETSLOT <slot> IMPORTING|MIGRATING|STABLE|NODE`.
2101///
2102/// The four arms and the order of their checks are the reference's, which is
2103/// worth being exact about because a resharding tool drives this and reads the
2104/// sentences it gets back.
2105///
2106/// `NODE` is where a slot migration ends and it is the one arm that does more
2107/// than move a field. When the node being named is this one and this node was
2108/// importing the slot, the config epoch goes up and the whole cluster is told
2109/// straight away, because until that happens every other node is still pointing
2110/// clients at the node the slot came from and the higher epoch is the only thing
2111/// that makes them stop.
2112fn setslot(server: &Server, session_db: usize, args: Args<'_>, out: &mut Out) -> Result<()> {
2113    // A replica has no say in who owns what, so this is refused before the slot
2114    // number is even looked at.
2115    if !server.cluster.map.lock().nodes[0].is_master() {
2116        return Err(Error::new(
2117            Code::Invalid,
2118            "Please use SETSLOT only with masters.",
2119        ));
2120    }
2121    let slot = slot_arg(&args, 2)?;
2122    let action = args.get(3);
2123    let wrong = || {
2124        Error::new(
2125            Code::Invalid,
2126            "Invalid CLUSTER SETSLOT action or number of arguments. Try CLUSTER HELP",
2127        )
2128    };
2129    // Set when the `NODE` arm decides the rest of the cluster has to be told
2130    // now rather than at the next ping. Done once the lock is gone, since
2131    // building the packet takes the same lock.
2132    let mut announce = false;
2133    // And when this node has just given its last slot away and should follow
2134    // whoever took it. The same, and for the same reason.
2135    let mut follow: Option<u16> = None;
2136    {
2137        let mut map = server.cluster.map.lock();
2138        let at = usize::from(slot);
2139        if args::is(action, b"migrating") && args.len() == 5 {
2140            if !map.mine(slot) {
2141                return Err(Error::fmt(
2142                    Code::Invalid,
2143                    format_args!("I'm not the owner of hash slot {slot}"),
2144                ));
2145            }
2146            let Some(to) = map.find(args.get(4)) else {
2147                return Err(dont_know(args.get(4)));
2148            };
2149            map.migrating[at] = Some(to);
2150        } else if args::is(action, b"importing") && args.len() == 5 {
2151            if map.mine(slot) {
2152                return Err(Error::fmt(
2153                    Code::Invalid,
2154                    format_args!("I'm already the owner of hash slot {slot}"),
2155                ));
2156            }
2157            let Some(from) = map.find(args.get(4)) else {
2158                return Err(dont_know(args.get(4)));
2159            };
2160            map.importing[at] = Some(from);
2161        } else if args::is(action, b"stable") && args.len() == 4 {
2162            map.migrating[at] = None;
2163            map.importing[at] = None;
2164        } else if args::is(action, b"node") && args.len() == 5 {
2165            let Some(to) = map.find(args.get(4)) else {
2166                return Err(unknown_node(args.get(4)));
2167            };
2168            if !map.nodes[usize::from(to)].is_master() {
2169                return Err(Error::new(Code::Invalid, "Target node is not a master"));
2170            }
2171            let was_mine = map.owner[at] == Some(0);
2172            // The keys are counted twice here rather than once, which reads
2173            // oddly and is the reference's own shape. The first count refuses
2174            // handing the slot to somebody else while this node is still
2175            // holding keys for it, since that would leave two nodes answering
2176            // for the same data. The second clears the migrating mark, and it
2177            // is a separate question because a slot can be empty and marked
2178            // whether or not it was ever this node's.
2179            let held = keys_in_slot(server, session_db, slot);
2180            if was_mine && to != 0 && held != 0 {
2181                return Err(Error::fmt(
2182                    Code::Invalid,
2183                    format_args!(
2184                        "Can't assign hashslot {slot} to a different node while I still hold keys for this hash slot."
2185                    ),
2186                ));
2187            }
2188            if held == 0 {
2189                map.migrating[at] = None;
2190            }
2191            map.owner[at] = Some(to);
2192            // A master that has just handed over its last slot follows whoever
2193            // took it, which is what stops a resharded cluster being left with
2194            // a node that owns nothing and serves nobody.
2195            if was_mine && to != 0 && map.runs(0).is_empty() {
2196                follow = Some(to);
2197            }
2198            // The import is finished, so the epoch goes up and everybody is
2199            // told. Nothing else in this function moves the epoch, because
2200            // nothing else is this node claiming something that used to be
2201            // somebody else's.
2202            if to == 0 && map.importing[at].is_some() {
2203                bump_without_consensus(server, &mut map);
2204                map.importing[at] = None;
2205                announce = true;
2206            }
2207        } else {
2208            return Err(wrong());
2209        }
2210    }
2211    server.recount_coverage();
2212    save(server)?;
2213    // Only on a real server. An embedded one has no bus and nobody to follow,
2214    // and the slot still changes hands the same way.
2215    if let Some(to) = follow
2216        && let Some(shared) = server.myself()
2217    {
2218        shared.cluster_replicate(to);
2219    }
2220    if announce {
2221        server.cluster_broadcast_pong();
2222    }
2223    out.ok();
2224    Ok(())
2225}
2226
2227/// How many keys of a slot are here, which two of `SETSLOT NODE`'s refusals turn
2228/// on and which `CLUSTER COUNTKEYSINSLOT` answers.
2229fn keys_in_slot(server: &Server, at: usize, slot: u16) -> usize {
2230    let mut found = 0;
2231    server.dbs[at].keys(|key| {
2232        if key_slot(key) == slot {
2233            found += 1;
2234        }
2235    });
2236    found
2237}
2238
2239/// The reference's `clusterBumpConfigEpochWithoutConsensus`.
2240///
2241/// A node that has just taken a slot off somebody needs an epoch higher than
2242/// theirs, or every other node will keep the old owner: a higher epoch is the
2243/// whole of how the cluster decides which of two claims on a slot is the newer
2244/// one. Without consensus means exactly that, nobody is asked, and the comment
2245/// in the reference is worth repeating: two nodes can end up on the same epoch
2246/// this way, and the collision rule sorts that out afterwards rather than the
2247/// bump trying to avoid it.
2248///
2249/// It does nothing at all when this node already holds the largest epoch
2250/// anybody has, since there is nothing left to outrank. `false` is that case,
2251/// and it is what `CLUSTER BUMPEPOCH` answers `STILL` for.
2252fn bump_without_consensus(server: &Server, map: &mut Map) -> bool {
2253    let highest = map
2254        .nodes
2255        .iter()
2256        .map(|n| n.epoch)
2257        .max()
2258        .unwrap_or(0)
2259        .max(server.cluster.epoch.load(Relaxed));
2260    let mine = map.nodes[0].epoch;
2261    if mine != 0 && mine == highest {
2262        return false;
2263    }
2264    map.nodes[0].epoch = server.cluster.epoch.fetch_add(1, Relaxed) + 1;
2265    true
2266}
2267
2268/// `CLUSTER FLUSHSLOTS`, which drops every slot this node claims.
2269fn flushslots(server: &Server, out: &mut Out) -> Result<()> {
2270    if server.dbs.iter().any(|db| !db.is_empty()) {
2271        return Err(Error::new(
2272            Code::Invalid,
2273            "DB must be empty to perform CLUSTER FLUSHSLOTS.",
2274        ));
2275    }
2276    {
2277        let mut map = server.cluster.map.lock();
2278        for slot in 0..SLOTS {
2279            if map.owner[slot] == Some(0) {
2280                map.owner[slot] = None;
2281            }
2282            map.migrating[slot] = None;
2283            map.importing[slot] = None;
2284        }
2285    }
2286    server.recount_coverage();
2287    save(server)?;
2288    out.ok();
2289    Ok(())
2290}
2291
2292/// `CLUSTER BUMPEPOCH`, which takes an epoch above everybody else's.
2293///
2294/// Answers `BUMPED` and the new epoch when it moved and `STILL` and the current
2295/// one when there was nothing to outrank, which is what a second call in a row
2296/// gets. See [`bump_without_consensus`] for why doing nothing is the right
2297/// answer rather than a wasted epoch.
2298fn bumpepoch(server: &Server, out: &mut Out) -> Result<()> {
2299    let (moved, epoch) = {
2300        let mut map = server.cluster.map.lock();
2301        let moved = bump_without_consensus(server, &mut map);
2302        (moved, map.nodes[0].epoch)
2303    };
2304    if moved {
2305        save(server)?;
2306        server.cluster_broadcast_pong();
2307    }
2308    let word = if moved { "BUMPED" } else { "STILL" };
2309    let text = yo_alloc::allow(|| format!("{word} {epoch}"));
2310    out.simple(text.as_bytes());
2311    Ok(())
2312}
2313
2314/// `CLUSTER SET-CONFIG-EPOCH`, which is how a brand new cluster gives each node
2315/// a different epoch before anything has been agreed.
2316fn set_config_epoch(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
2317    let epoch = args.int(2)?;
2318    if epoch < 0 {
2319        return Err(Error::fmt(
2320            Code::Invalid,
2321            format_args!("Invalid config epoch specified: {epoch}"),
2322        ));
2323    }
2324    {
2325        let mut map = server.cluster.map.lock();
2326        if map.nodes[0].epoch != 0 {
2327            return Err(Error::new(
2328                Code::Invalid,
2329                "Node config epoch is already non-zero",
2330            ));
2331        }
2332        map.nodes[0].epoch = epoch as u64;
2333    }
2334    let epoch = epoch as u64;
2335    server.cluster.epoch.fetch_max(epoch, Relaxed);
2336    save(server)?;
2337    out.ok();
2338    Ok(())
2339}
2340
2341/// `CLUSTER RESET [HARD|SOFT]`, which gives up every slot and, when hard, takes
2342/// a new identity as well.
2343fn reset(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
2344    let hard = match args.opt(2) {
2345        None => false,
2346        Some(word) if args::is(word, b"hard") => true,
2347        Some(word) if args::is(word, b"soft") => false,
2348        Some(_) => return Err(args::syntax()),
2349    };
2350    if server.dbs.iter().any(|db| !db.is_empty()) {
2351        return Err(Error::new(
2352            Code::Invalid,
2353            "CLUSTER RESET can't be called with master nodes containing keys",
2354        ));
2355    }
2356    {
2357        let mut map = server.cluster.map.lock();
2358        for slot in 0..SLOTS {
2359            map.owner[slot] = None;
2360            map.migrating[slot] = None;
2361            map.importing[slot] = None;
2362        }
2363        map.nodes.truncate(1);
2364        map.nodes[0].epoch = 0;
2365        // A reset makes this node a master of nothing again, which is the half
2366        // of it a soft reset does as well: it stops following anybody and it
2367        // forgets everybody, it just keeps its name.
2368        map.nodes[0].flags = FLAG_MYSELF | FLAG_MASTER;
2369        map.nodes[0].master = None;
2370        if hard {
2371            yo_alloc::allow(|| {
2372                map.nodes[0].id = String::from_utf8_lossy(&new_id()).into_owned();
2373                map.nodes[0].shard = String::from_utf8_lossy(&new_id()).into_owned();
2374            });
2375        }
2376    }
2377    if hard {
2378        server.cluster.epoch.store(0, Relaxed);
2379    }
2380    server.recount_coverage();
2381    save(server)?;
2382    out.ok();
2383    Ok(())
2384}
2385
2386// -------------------------------------------------------------- the key sets
2387
2388/// `CLUSTER COUNTKEYSINSLOT`.
2389fn count_keys(server: &Server, at: usize, args: Args<'_>, out: &mut Out) -> Result<()> {
2390    let n = args.int(2)?;
2391    let slot = u16::try_from(n)
2392        .ok()
2393        .filter(|s| usize::from(*s) < SLOTS)
2394        .ok_or_else(|| Error::new(Code::Invalid, "Invalid slot"))?;
2395    let mut found = 0i64;
2396    server.dbs[at].keys(|key| {
2397        if key_slot(key) == slot {
2398            found += 1;
2399        }
2400    });
2401    out.int(found);
2402    Ok(())
2403}
2404
2405/// `CLUSTER GETKEYSINSLOT`.
2406fn get_keys(server: &Server, at: usize, args: Args<'_>, out: &mut Out) -> Result<()> {
2407    let slot = args.int(2)?;
2408    let count = args.int(3)?;
2409    let bad = || Error::new(Code::Invalid, "Invalid slot or number of keys");
2410    if !(0..SLOTS as i64).contains(&slot) || count < 0 {
2411        return Err(bad());
2412    }
2413    let slot = slot as u16;
2414    let want = count as usize;
2415    let start = out.len();
2416    let mut n = 0;
2417    server.dbs[at].keys(|key| {
2418        if n < want && key_slot(key) == slot {
2419            out.bulk(key);
2420            n += 1;
2421        }
2422    });
2423    out.close_array(start, n);
2424    Ok(())
2425}
2426
2427// ------------------------------------------------------------- the disk copy
2428
2429/// Write the table where a restart will find it.
2430///
2431/// The format is the reference's, line for line, so a node's file can be read by
2432/// a real server and the other way round. The write goes to a temporary name and
2433/// is renamed over the real one, the same way the snapshot writer does it, so a
2434/// reader never sees half a file.
2435fn save(server: &Server) -> Result<()> {
2436    let path = {
2437        let file = server.cluster.file.lock();
2438        if file.is_empty() {
2439            return Ok(());
2440        }
2441        yo_alloc::allow(|| file.clone())
2442    };
2443    let epoch = server.cluster.epoch.load(Relaxed);
2444    let text = yo_alloc::allow(|| {
2445        let mut s = lines(server, true);
2446        let _ = writeln!(s, "vars currentEpoch {epoch} lastVoteEpoch 0");
2447        s
2448    });
2449    yo_alloc::allow(|| {
2450        let temp = format!("{path}.tmp");
2451        let wrote =
2452            std::fs::write(&temp, text.as_bytes()).and_then(|()| std::fs::rename(&temp, &path));
2453        match wrote {
2454            Ok(()) => Ok(()),
2455            Err(e) => Err(Error::fmt(
2456                Code::Invalid,
2457                format_args!("cluster config file could not be written: {e}"),
2458            )),
2459        }
2460    })
2461}
2462
2463impl Server {
2464    /// Read the table back off disk, which is what makes a restart a restart
2465    /// rather than a node that has forgotten which half of the keyspace is its.
2466    ///
2467    /// A file that is not there is not an error, because the first start of a
2468    /// brand new node has none. A file that is there and is nonsense is, because
2469    /// the alternative is a node that comes up owning nothing and starts
2470    /// answering `CLUSTERDOWN` for keys it has on disk.
2471    fn reload_cluster(&mut self) -> Result<()> {
2472        let path = self.cluster_file();
2473        if path.is_empty() {
2474            return Ok(());
2475        }
2476        let text = match yo_alloc::allow(|| std::fs::read_to_string(&path)) {
2477            Ok(text) => text,
2478            Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()),
2479            Err(e) => {
2480                return Err(Error::fmt(Code::Invalid, format_args!("{path}: {e}")));
2481            }
2482        };
2483        yo_alloc::allow(|| self.absorb_cluster(&text))
2484    }
2485
2486    /// The parse, split out so that a test can hand it text without a file.
2487    fn absorb_cluster(&mut self, text: &str) -> Result<()> {
2488        let bad = |what: &str| Error::fmt(Code::Invalid, format_args!("{what} in cluster config"));
2489        let now = self.now_ms();
2490        let mut nodes: Vec<Node> = Vec::new();
2491        // Slot runs are kept against the owner's id and not against its position
2492        // in the list, because this node moves to the front of the list when its
2493        // own line turns up and every position recorded before that moves down.
2494        let mut owned: Vec<(String, u16, u16)> = Vec::new();
2495        // The master field names a node that may not have been read yet, so
2496        // both ends are kept as ids and resolved once every line is in.
2497        let mut follows: Vec<(String, String)> = Vec::new();
2498        for line in text.lines() {
2499            let line = line.trim();
2500            if line.is_empty() {
2501                continue;
2502            }
2503            let mut words = line.split(' ');
2504            let first = words.next().unwrap_or_default();
2505            if first == "vars" {
2506                // `vars currentEpoch <n> lastVoteEpoch <n>`, read as pairs so
2507                // that a build which adds a third one does not break the parse.
2508                let rest: Vec<&str> = words.collect();
2509                for pair in rest.chunks(2) {
2510                    if pair.len() == 2 && pair[0] == "currentEpoch" {
2511                        let epoch = pair[1].parse::<u64>().map_err(|_| bad("bad epoch"))?;
2512                        self.cluster.epoch.store(epoch, Relaxed);
2513                    }
2514                }
2515                continue;
2516            }
2517            if first.len() != ID_LEN {
2518                return Err(bad("bad node id"));
2519            }
2520            let address = words.next().ok_or_else(|| bad("missing address"))?;
2521            let named = words.next().ok_or_else(|| bad("missing flags"))?;
2522            let master = words.next().ok_or_else(|| bad("missing master"))?;
2523            let ping = words.next().ok_or_else(|| bad("missing ping time"))?;
2524            let pong = words.next().ok_or_else(|| bad("missing pong time"))?;
2525            let epoch = words
2526                .next()
2527                .and_then(|w| w.parse::<u64>().ok())
2528                .ok_or_else(|| bad("bad config epoch"))?;
2529            // The link state field, which is read past rather than read.
2530            let _link = words.next();
2531            let (host, port, bus, shard) =
2532                split_address(address).ok_or_else(|| bad("bad address"))?;
2533            for word in words {
2534                // The migrating and importing markers are re-read from the
2535                // brackets below rather than here, since they name a node that
2536                // may not have been read yet.
2537                if word.starts_with('[') {
2538                    continue;
2539                }
2540                let (from, to) = match word.split_once('-') {
2541                    Some((a, b)) => (
2542                        a.parse::<u16>().map_err(|_| bad("bad slot"))?,
2543                        b.parse::<u16>().map_err(|_| bad("bad slot"))?,
2544                    ),
2545                    None => {
2546                        let one = word.parse::<u16>().map_err(|_| bad("bad slot"))?;
2547                        (one, one)
2548                    }
2549                };
2550                if usize::from(from) >= SLOTS || usize::from(to) >= SLOTS || from > to {
2551                    return Err(bad("slot out of range"));
2552                }
2553                owned.push((first.to_owned(), from, to));
2554            }
2555            let mut flags = 0u16;
2556            for name in named.split(',') {
2557                flags |= match name {
2558                    "myself" => FLAG_MYSELF,
2559                    "master" => FLAG_MASTER,
2560                    "slave" => FLAG_SLAVE,
2561                    "fail?" => FLAG_PFAIL,
2562                    "fail" => FLAG_FAIL,
2563                    "handshake" => FLAG_HANDSHAKE,
2564                    "noaddr" => FLAG_NOADDR,
2565                    "nofailover" => FLAG_NOFAILOVER,
2566                    _ => 0,
2567                };
2568            }
2569            if master != "-" {
2570                if master.len() != ID_LEN {
2571                    return Err(bad("bad master id"));
2572                }
2573                follows.push((first.to_owned(), master.to_owned()));
2574            }
2575            let node = Node {
2576                id: first.to_owned(),
2577                host,
2578                port,
2579                bus,
2580                shard,
2581                epoch,
2582                flags,
2583                master: None,
2584                // The two timestamps come back as now rather than as what the
2585                // file says, because a file written an hour ago would otherwise
2586                // put every node in it straight past the failure timeout. All
2587                // the file is really recording is whether a ping was in flight.
2588                ping_sent: stamp(ping, now),
2589                pong_recv: stamp(pong, now),
2590                data_recv: now,
2591                fail_time: 0,
2592                offset: 0,
2593                // The link state in the file is written for a human reading it
2594                // and is not read back. There is no link to anybody at startup,
2595                // and saying otherwise would stop the bus ever dialling out.
2596                linked: false,
2597                reports: Vec::new(),
2598            };
2599            if named.split(',').any(|f| f == "myself") {
2600                // This node goes first, and everything already read moves down.
2601                nodes.insert(0, node);
2602            } else {
2603                nodes.push(node);
2604            }
2605        }
2606        if nodes.is_empty() {
2607            return Ok(());
2608        }
2609        let mut map = Map::new(nodes.remove(0));
2610        map.nodes.append(&mut nodes);
2611        for (id, from, to) in owned {
2612            let Some(at) = map.find(id.as_bytes()) else {
2613                return Err(bad("slots for a node nobody knows"));
2614            };
2615            for slot in from..=to {
2616                map.owner[usize::from(slot)] = Some(at);
2617            }
2618        }
2619        for (who, whose) in follows {
2620            let (Some(who), Some(whose)) = (map.find(who.as_bytes()), map.find(whose.as_bytes()))
2621            else {
2622                return Err(bad("master id nobody knows"));
2623            };
2624            map.nodes[usize::from(who)].master = Some(whose);
2625            // A replica's config epoch is its master's business, so the file's
2626            // value is dropped rather than believed. Keeping it would let a
2627            // replica that was a master an hour ago outvote the node that took
2628            // its slots the moment it came back.
2629            map.nodes[usize::from(who)].epoch = 0;
2630        }
2631        *self.cluster.map.lock() = map;
2632        Ok(())
2633    }
2634}
2635
2636/// One of the two timestamps out of the node table, read as a yes or a no.
2637///
2638/// The reference does the same thing for the same reason. The number in the
2639/// file was taken from a clock that has since moved on, and all the reader can
2640/// usefully learn from it is whether there was a ping in flight when the file
2641/// was written, so a number that is not zero comes back as the time now.
2642fn stamp(field: &str, now: u64) -> u64 {
2643    match field.parse::<u64>() {
2644        Ok(0) | Err(_) => 0,
2645        Ok(_) => now,
2646    }
2647}
2648
2649/// Pull the host, the port and the shard id out of one `ip:port@bus,aux` field.
2650///
2651/// The aux fields after the first comma are a list of `name=value` pairs with an
2652/// optional hostname in front of them, and a build that adds one has to leave a
2653/// build that does not able to read the file, so everything but the shard id is
2654/// skipped rather than being counted.
2655fn split_address(field: &str) -> Option<(String, u16, u16, String)> {
2656    let (address, aux) = match field.split_once(',') {
2657        Some((address, aux)) => (address, aux),
2658        None => (field, ""),
2659    };
2660    let (host, ports) = address.rsplit_once(':')?;
2661    let (client, bus) = match ports.split_once('@') {
2662        Some((client, bus)) => (client, bus.parse::<u16>().ok()?),
2663        None => (ports, 0),
2664    };
2665    let port = client.parse::<u16>().ok()?;
2666    let bus = if bus == 0 { port + BUS_OFFSET } else { bus };
2667    let shard = aux
2668        .split(',')
2669        .find_map(|pair| pair.strip_prefix("shard-id="))
2670        .map_or_else(
2671            || String::from_utf8_lossy(&new_id()).into_owned(),
2672            str::to_owned,
2673        );
2674    Some((host.to_owned(), port, bus, shard))
2675}
2676
2677// ------------------------------------------------------- the hand filled map
2678
2679/// What a test uses to fill the table the bus will fill later.
2680///
2681/// A single node cannot make a redirection happen: it owns every slot or it
2682/// owns none, and either way there is nowhere to send anybody. So the tests for
2683/// `MOVED`, `ASK` and `TRYAGAIN` put a second node in the table by hand, which
2684/// is exactly the state the bus will produce and is the state the config file
2685/// parser already reads.
2686#[cfg(test)]
2687impl Server {
2688    /// Take every slot, and be up about it straight away.
2689    pub(super) fn cluster_own_everything(&self) {
2690        {
2691            let mut map = self.cluster.map.lock();
2692            for slot in 0..SLOTS {
2693                map.owner[slot] = Some(0);
2694            }
2695        }
2696        self.recount_coverage();
2697        self.cluster.was_down.store(false, Relaxed);
2698        self.cluster.booted_at.store(0, Relaxed);
2699    }
2700
2701    /// Put a node in the table that nobody has met, and answer its index.
2702    pub(super) fn cluster_pretend_node(&self, id: &str, host: &str, port: u16) -> u16 {
2703        let mut map = self.cluster.map.lock();
2704        let mut node = Node::new(
2705            id.to_owned(),
2706            host.to_owned(),
2707            port,
2708            port + BUS_OFFSET,
2709            FLAG_MASTER,
2710            0,
2711        );
2712        node.shard = id.to_owned();
2713        node.linked = true;
2714        map.nodes.push(node);
2715        (map.nodes.len() - 1) as u16
2716    }
2717
2718    /// Hand one slot to another node.
2719    pub(super) fn cluster_hand_over(&self, slot: u16, node: u16) {
2720        let mut map = self.cluster.map.lock();
2721        map.owner[usize::from(slot)] = Some(node);
2722    }
2723
2724    /// Make this node a replica of another one in the table.
2725    ///
2726    /// What `CLUSTER REPLICATE` does to the table, without the half of it that
2727    /// starts replicating, which an embedded server has no way of doing and
2728    /// which none of the tests that want a replica in the table care about.
2729    pub(super) fn cluster_pretend_follower(&self, of: u16) {
2730        let mut map = self.cluster.map.lock();
2731        map.nodes[0].flags &= !FLAG_MASTER;
2732        map.nodes[0].flags |= FLAG_SLAVE;
2733        map.nodes[0].master = Some(of);
2734    }
2735
2736    /// Mark one slot as on its way out to, or in from, another node.
2737    pub(super) fn cluster_moving(&self, slot: u16, to: Option<u16>, from: Option<u16>) {
2738        let mut map = self.cluster.map.lock();
2739        map.migrating[usize::from(slot)] = to;
2740        map.importing[usize::from(slot)] = from;
2741    }
2742}
2743
2744// ---------------------------------------------------------------- the tests
2745
2746#[cfg(test)]
2747mod tests {
2748    use super::{SLOTS, key_slot};
2749
2750    /// The slots the reference answered for these keys, read off a running
2751    /// 8.10.1 with cluster mode on rather than worked out from the algorithm.
2752    #[test]
2753    fn a_key_lands_in_the_slot_a_real_server_puts_it_in() {
2754        assert_eq!(key_slot(b"foo"), 12182);
2755        assert_eq!(key_slot(b"1234"), 6025);
2756        assert_eq!(key_slot(b""), 0);
2757        assert_eq!(key_slot(b"{user1000}.following"), 3443);
2758    }
2759
2760    /// The hash tag is the whole reason a command can name two keys at all, and
2761    /// its edge cases are the ones a client library gets wrong.
2762    #[test]
2763    fn the_hash_tag_rules_are_the_reference_rules() {
2764        // Two keys with the same tag are one slot, whatever else they say.
2765        assert_eq!(
2766            key_slot(b"{user1000}.following"),
2767            key_slot(b"{user1000}.followers")
2768        );
2769        // A tag with nothing in it is not a tag.
2770        assert_eq!(key_slot(b"{}foo"), key_slot(b"{}foo"));
2771        assert_ne!(key_slot(b"{}foo"), key_slot(b"foo"));
2772        // An opening brace with no closing one is not a tag either.
2773        assert_ne!(key_slot(b"{foo"), key_slot(b"foo"));
2774        // Only the first closing brace after the first opening one counts.
2775        assert_eq!(key_slot(b"{a}{b}"), key_slot(b"a"));
2776        // A tag can hold a brace, since the search is for the first close.
2777        assert_eq!(key_slot(b"foo{{bar}}zap"), key_slot(b"{bar"));
2778    }
2779
2780    /// Every slot is reachable and none is out of range, which is the only
2781    /// property the command path actually leans on.
2782    #[test]
2783    fn every_slot_is_in_range() {
2784        let mut seen = vec![false; SLOTS];
2785        for i in 0..200_000u32 {
2786            let key = i.to_string();
2787            let slot = key_slot(key.as_bytes());
2788            assert!(usize::from(slot) < SLOTS);
2789            seen[usize::from(slot)] = true;
2790        }
2791        assert!(seen.iter().all(|s| *s), "200k keys reach all 16384 slots");
2792    }
2793
2794    /// Reading the node table back has to put every run of slots on the node
2795    /// that owns it, including when this node's own line is not the first one.
2796    ///
2797    /// It is worth a test of its own because the failure is silent and only
2798    /// shows up on a cluster with three nodes in it: this node moves to the
2799    /// front of the list when its line is read, everything read before it moves
2800    /// down one, and a run recorded against a position rather than against an
2801    /// id ends up on the wrong node.
2802    #[test]
2803    fn a_config_file_puts_every_run_on_the_node_that_owns_it() {
2804        let mut server = super::Server::new();
2805        server.enable_cluster("", 7355);
2806        let text = "\
28073b80b05445f38bc7214f083696a2bbf90e3f30e3 127.0.0.1:7356@17356 master - 0 0 0 connected 10923-16383
280830d0651b0ec5e178e082634c44fb9adcc6e4021b 127.0.0.1:7355@17355 myself,master - 0 0 1 connected 5461-10922
280919a9e69b8b66016ac43c55ccdeed0283e0148e17 127.0.0.1:7354@17354 master - 0 0 2 connected 0-5460
2810vars currentEpoch 2 lastVoteEpoch 0
2811";
2812        server.absorb_cluster(text).expect("the file parses");
2813        let map = server.cluster.map.lock();
2814        let at = |id: &str| map.find(id.as_bytes()).expect("the node is in the table");
2815        assert_eq!(at("30d0651b0ec5e178e082634c44fb9adcc6e4021b"), 0, "myself");
2816        for (id, from, to) in [
2817            ("19a9e69b8b66016ac43c55ccdeed0283e0148e17", 0, 5460),
2818            ("30d0651b0ec5e178e082634c44fb9adcc6e4021b", 5461, 10922),
2819            ("3b80b05445f38bc7214f083696a2bbf90e3f30e3", 10923, 16383),
2820        ] {
2821            let owner = Some(at(id));
2822            for slot in from..=to {
2823                assert_eq!(map.owner[slot], owner, "slot {slot} belongs to {id}");
2824            }
2825        }
2826    }
2827
2828    /// Three fields in the node table say what was true when it was written and
2829    /// not what is true now, and reading them back as if they were live state
2830    /// is what stops a restarted node ever dialling anybody again.
2831    #[test]
2832    fn a_config_file_is_not_read_back_as_live_state() {
2833        let mut server = super::Server::new();
2834        server.enable_cluster("", 7357);
2835        let text = "\
28369fcbb7624dedbb2fd0020dd2fbf86a5eb8cec31b 127.0.0.1:7355@17355 master - 0 1789005531306 3 connected 5461-10922
2837ac6dd51a69741dc5130637c594866c9b7e0cfc4e 127.0.0.1:7357@17357 myself,slave 9fcbb7624dedbb2fd0020dd2fbf86a5eb8cec31b 1789005400000 1789005532315 3 connected
2838";
2839        server.absorb_cluster(text).expect("the file parses");
2840        let now = server.now_ms();
2841        let map = server.cluster.map.lock();
2842        for node in &map.nodes {
2843            assert!(!node.linked, "nothing is linked before the bus dials out");
2844        }
2845        // A ping that was in flight comes back as one sent now, and a ping that
2846        // was not stays at zero, which is the difference the cron reads.
2847        assert_eq!(map.nodes[0].ping_sent, now, "myself had a ping in flight");
2848        assert_eq!(map.nodes[1].ping_sent, 0, "the master did not");
2849        assert_eq!(map.nodes[0].pong_recv, now);
2850        // And a replica's config epoch belongs to its master, not to the file.
2851        assert_eq!(map.nodes[0].epoch, 0, "the replica's epoch is dropped");
2852        assert_eq!(map.nodes[1].epoch, 3, "the master's is kept");
2853    }
2854}