yo_resp/dispatch/follow.rs
1//! Being a replica: the link out to a master and the stream that comes back.
2//!
3//! The other half of `repl`. That module is what a master does to feed somebody
4//! else, and this is what a server does to be fed. The two never run against
5//! each other on one server unless somebody has built a chain, and a chain is
6//! the case this file is careful about rather than the case it is written for.
7//!
8//! # What the link is
9//!
10//! One thread, one socket, and a loop that never ends until somebody says
11//! `REPLICAOF NO ONE` or the server stops. It dials the master, walks the
12//! handshake, takes the snapshot, and then reads commands off the socket and
13//! runs them against this server's own keyspace forever. A link that breaks is
14//! not an error to report to anybody, because there is nobody to report it to:
15//! the client that said `REPLICAOF` was answered `OK` the moment the intent was
16//! recorded, which is what a real server does and is the only thing it can do
17//! when the dial has not happened yet. So a broken link waits a second and dials
18//! again, and `INFO` is where an operator finds out.
19//!
20//! A thread of its own rather than a slot on the reactor. The reactor's threads
21//! are woken by clients and this has no client, the handshake is a handful of
22//! blocking round trips and the snapshot is one very large read, and all three
23//! of those are the wrong shape for an event loop that is measured in
24//! nanoseconds per command. One thread that is asleep on a socket for most of
25//! its life costs a stack.
26//!
27//! # Why the commands go through the front door
28//!
29//! What arrives is a stream of ordinary commands, so what runs them is the
30//! ordinary dispatcher, through a [`Session`] the link owns. That is not a
31//! shortcut, it is the point: a replica that applied writes through some second
32//! path would be a second implementation of every command, and the first bug in
33//! it would be a replica that quietly disagrees with its master. Going through
34//! the front door also means keyspace notifications fire on the replica, the
35//! search indexes are kept up, and `WATCH` on the replica notices, all of which
36//! a real replica does and none of which had to be written twice.
37//!
38//! Three things about that session are not ordinary. It is past the password and
39//! past the access control list, because the master is not a user and there is
40//! nobody to authenticate. It is exempt from the read only refusal, which is the
41//! whole point of the refusal. And it is exempt from `CLIENT PAUSE`, because a
42//! pause is a thing an operator does to clients and a master is not one, and a
43//! paused replica that stopped reading its socket would make the master's output
44//! buffer grow until the master dropped the link.
45//!
46//! # The offset
47//!
48//! The replica counts the bytes it has applied and tells the master about them,
49//! and the master compares that number with its own to answer `WAIT` and to fill
50//! in the lag in `INFO`. So the count has to be of the bytes as they arrived and
51//! not of anything this server decided: what is added is exactly what the
52//! decoder said it consumed, including the commands that did nothing, including
53//! the `PING`s the master sends to keep the link warm, and including the
54//! `REPLCONF GETACK` that asks for the number itself, which is why the answer to
55//! a `GETACK` is sent after its own bytes have been counted.
56//!
57//! # A replica with replicas
58//!
59//! A chain works by passing the bytes on rather than by propagating what the
60//! commands did. The two are not the same thing and only one of them can be:
61//! the offset a sub-replica acknowledges has to be a position in the master's
62//! stream, and a middle server that made up its own stream would be handing out
63//! positions in a history nobody else is writing. So while this link is
64//! applying, the ordinary propagation is turned off for the thread and the bytes
65//! that arrived are put on this server's stream unchanged, under the master's
66//! own replication id and at the master's own offsets.
67
68use core::cell::Cell;
69use std::io::{ErrorKind, Read, Write};
70use std::net::{TcpStream, ToSocketAddrs};
71use std::sync::Arc;
72use std::sync::atomic::Ordering::{Relaxed, Release};
73use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU64};
74use std::time::{Duration, Instant};
75
76use yo_common::lock::Lock;
77use yo_common::{Code, Error, Result};
78
79use crate::proto::{Limits, Proto};
80use crate::reply::Out;
81use crate::request::{Argv, Step};
82
83use super::args::{self, Args};
84use super::repl::{self, ID_LEN};
85use super::{Flow, Server, Session};
86
87/// How long a dial is given before it is called a failure.
88const DIAL_TIMEOUT: Duration = Duration::from_secs(5);
89
90/// How long a read waits before the loop looks around at other things.
91///
92/// Short, because this is also how often the link notices it has been called off
93/// and how close to the second an acknowledgement lands. A tenth of a second on
94/// a socket that is usually idle is a syscall ten times a second, which is
95/// nothing next to a thread that is otherwise asleep.
96const POLL: Duration = Duration::from_millis(100);
97
98/// How often the replica tells the master where it has got to.
99///
100/// A second, which is Redis's `REPLCONF ACK` period. The master turns the gap
101/// between acknowledgements into the lag it reports, so a longer period would
102/// make every replica look worse than it is.
103const ACK_EVERY: Duration = Duration::from_secs(1);
104
105/// How long to wait before dialling again after a link went down.
106const RETRY: Duration = Duration::from_millis(500);
107
108/// How long the whole handshake is given, per attempt.
109const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(10);
110
111/// How long a master is given to start answering a `PSYNC`, and how long a gap
112/// in the snapshot is allowed to be before the link is called broken.
113///
114/// Much longer than the handshake, because what happens between the `PSYNC` and
115/// the first byte of the snapshot is a fork and a save on a machine holding a
116/// dataset this one has not seen yet. Redis's own replica gives it `repl-timeout`
117/// and that is a minute by default, so this is a minute.
118const SYNC_TIMEOUT: Duration = Duration::from_secs(60);
119
120/// The longest line the handshake will read before calling the peer broken.
121///
122/// A handshake reply is a word and at most an id and a number. Anything past
123/// this is a peer that is not answering the question that was asked, which is
124/// the same rule and the same reasoning `MIGRATE` uses.
125const LINE_MAX: usize = 1024;
126
127/// What `master_link_status` says, which is also what the link is doing.
128///
129/// Redis reports two words where there are four states, so `Connect` and `Sync`
130/// both read as `down`, and the one that tells them apart is
131/// `master_sync_in_progress` beside it.
132#[derive(Clone, Copy, PartialEq, Eq)]
133#[repr(u8)]
134enum State {
135 /// Not following anybody. This server is a master.
136 None = 0,
137 /// Following somebody and not currently connected to them.
138 Connect = 1,
139 /// Connected and loading the snapshot.
140 Sync = 2,
141 /// Connected and applying the stream.
142 Up = 3,
143}
144
145impl State {
146 fn from(n: u8) -> State {
147 match n {
148 1 => State::Connect,
149 2 => State::Sync,
150 3 => State::Up,
151 _ => State::None,
152 }
153 }
154}
155
156/// Where this server has been told to follow.
157#[derive(Clone)]
158struct Upstream {
159 host: String,
160 port: u16,
161}
162
163/// Everything about being a replica, all of it idle on a server that is nobody's.
164pub(crate) struct Follower {
165 /// Who this server follows, and none when it is a master.
166 ///
167 /// Written by `REPLICAOF` and read by the link thread, by `INFO` and by
168 /// `ROLE`. A lock and not an atomic because it is a host name, and it is
169 /// touched once per link rather than once per command.
170 upstream: Lock<Option<Upstream>>,
171 /// Which link is the current one.
172 ///
173 /// Every `REPLICAOF` bumps this and the thread it starts carries the number
174 /// it was started with. A thread whose number is no longer the current one
175 /// has been replaced and lets itself go at the next thing it does, which is
176 /// how a link is called off without anything having to reach into a blocking
177 /// socket read.
178 epoch: AtomicU64,
179 /// What the link is doing, one of [`State`].
180 state: AtomicU8,
181 /// Whether an ordinary client's write is refused, which is Redis's
182 /// `replica-read-only` and is on by default.
183 ///
184 /// Read once per write command on a server that is not a replica, next to
185 /// the pause check and the freeze check that are already there, and the load
186 /// it reads is of a bool that is false.
187 read_only: AtomicBool,
188 /// Whether this server is following anybody at all, so the read only check
189 /// above is one load rather than a lock.
190 on: AtomicBool,
191 /// The user and password the link authenticates with, both empty when the
192 /// master wants neither.
193 auth: Lock<(Vec<u8>, Vec<u8>)>,
194 /// The port to tell the master this server listens on, which is what the
195 /// master reports in its own `INFO` and in `ROLE`.
196 ///
197 /// Zero when nobody has said, which is every embedded caller and every test,
198 /// and is what a master shows for a replica that did not say either.
199 port: AtomicU64,
200 /// When the link last had anything out of the master, for
201 /// `master_last_io_seconds_ago`.
202 last_io_ms: AtomicU64,
203 /// When the link last went down, for `master_link_down_since_seconds`.
204 down_ms: AtomicU64,
205 /// Whether this server's replication id and offset came from a master, so
206 /// the next `PSYNC` may ask to carry on rather than starting again.
207 ///
208 /// The position itself is not kept here on purpose. It is the server's own
209 /// replication offset, which `adopt` sets to the master's and which every
210 /// applied byte moves along, so there is one number rather than two that
211 /// could disagree. A second copy updated at the end of the stream loop would
212 /// be a copy that is behind by whatever the link died in the middle of, and
213 /// asking to carry on from behind is asking for the same bytes twice.
214 ///
215 /// Kept across a broken link, which is the whole reason a partial resync is
216 /// possible at all, and kept across a change of master too, which is what
217 /// lets a replica be handed to a promoted one without a snapshot. Thrown
218 /// away by `REPLICAOF NO ONE`, because that takes a new id and asking a
219 /// master about a history it has never heard of is a full resync with an
220 /// extra round trip in front of it.
221 resume: AtomicBool,
222}
223
224impl Default for Follower {
225 fn default() -> Follower {
226 Follower {
227 upstream: Lock::new(None),
228 epoch: AtomicU64::new(0),
229 state: AtomicU8::new(State::None as u8),
230 read_only: AtomicBool::new(true),
231 on: AtomicBool::new(false),
232 auth: Lock::new((Vec::new(), Vec::new())),
233 port: AtomicU64::new(0),
234 last_io_ms: AtomicU64::new(0),
235 down_ms: AtomicU64::new(0),
236 resume: AtomicBool::new(false),
237 }
238 }
239}
240
241thread_local! {
242 /// Whether this thread is applying a master's stream rather than running a
243 /// client's command.
244 ///
245 /// Read by the propagation site, which has no other way to tell the two
246 /// apart and has to, because what a replica passes on is the bytes it was
247 /// given and not what running them turned out to do. See the module header.
248 static APPLYING: Cell<bool> = const { Cell::new(false) };
249}
250
251/// Whether what is running arrived from a master.
252#[must_use]
253pub(crate) fn applying() -> bool {
254 APPLYING.get()
255}
256
257impl Server {
258 /// Whether this server is following somebody, which is the whole cost of
259 /// this file on a server that is not.
260 #[must_use]
261 pub(crate) fn following(&self) -> bool {
262 self.follow.on.load(Relaxed)
263 }
264
265 /// Whether an ordinary client's write is refused here.
266 ///
267 /// Both halves, because a server that is a replica and has been told it is
268 /// writable takes writes, and a server that is not a replica at all is not
269 /// made read only by the setting sitting there at its default.
270 #[must_use]
271 pub(crate) fn read_only_replica(&self) -> bool {
272 self.follow.on.load(Relaxed) && self.follow.read_only.load(Relaxed)
273 }
274
275 /// Say what port to announce to a master, which is what it reports back.
276 ///
277 /// Called once by whoever bound the socket, which is the only place that
278 /// knows. A server nobody tells announces nothing, which is what a real
279 /// master shows for a replica that did not say.
280 pub fn announce_port(&self, port: u16) {
281 self.follow.port.store(u64::from(port), Relaxed);
282 }
283
284 /// The port whoever bound the socket said this server is on, which `INFO`
285 /// reports and a cluster node writes into its config file.
286 ///
287 /// Nought on an embedded caller that never opened a socket, which is what a
288 /// reader should see rather than a guess.
289 #[must_use]
290 pub(crate) fn announced_port(&self) -> u16 {
291 self.follow.port.load(Relaxed) as u16
292 }
293
294 /// Say what the link should authenticate with, which is Redis's
295 /// `masteruser` and `masterauth`.
296 pub fn master_auth(&self, user: &[u8], pass: &[u8]) {
297 let mut auth = self.follow.auth.lock();
298 yo_alloc::allow(|| *auth = (user.to_vec(), pass.to_vec()));
299 }
300
301 /// Read back what the link would authenticate with, for `CONFIG GET`.
302 pub(crate) fn with_master_auth<T>(&self, each: impl FnOnce(&[u8], &[u8]) -> T) -> T {
303 let auth = self.follow.auth.lock();
304 each(&auth.0, &auth.1)
305 }
306
307 /// Whether an ordinary client's write is refused while this server is a
308 /// replica, which is Redis's `replica-read-only`.
309 ///
310 /// Writable on a running server, and a change takes effect on the next
311 /// command rather than on the next link, because it is a rule about clients
312 /// and not about the master.
313 pub fn set_replica_read_only(&self, yes: bool) {
314 self.follow.read_only.store(yes, Relaxed);
315 }
316
317 /// The setting on its own, which is what `CONFIG GET` answers whether or not
318 /// this server is a replica.
319 pub(crate) fn replica_read_only_setting(&self) -> bool {
320 self.follow.read_only.load(Relaxed)
321 }
322
323 /// Start following a master, for a server that was told to at startup.
324 ///
325 /// The same thing `REPLICAOF host port` does, reachable before anything has
326 /// connected. It is a separate entry point rather than a command run against
327 /// the server, because the caller has the `Arc` in its hand and a command
328 /// body does not.
329 pub fn follow_master(self: &Arc<Server>, host: &str, port: u16) {
330 self.is_behind();
331 let host = yo_alloc::allow(|| host.to_owned());
332 self.follow_now(Some(Upstream { host, port }));
333 }
334
335 /// Whether the link to the master is up and applying, which is the question
336 /// a `PSYNC` from somebody else asks before it trusts what we would send.
337 #[must_use]
338 pub(crate) fn master_link_up(&self) -> bool {
339 State::from(self.follow.state.load(Relaxed)) == State::Up
340 }
341
342 /// Stop following anybody, which is `REPLICAOF NO ONE` and the two ways a
343 /// failover ends up back where it started.
344 ///
345 /// The promotion is the part that matters: the history that was being
346 /// followed is kept as the second id and a new one is taken, so a replica
347 /// that was following this server through the old master can be handed over
348 /// without a snapshot. See `repl::promote`.
349 pub(super) fn stop_following(self: &Arc<Server>) {
350 if self.following() {
351 self.promote();
352 }
353 self.follow_now(None);
354 }
355
356 /// Start following the server a failover picked.
357 ///
358 /// Two things make this different from `REPLICAOF host port`. There is no
359 /// promotion, because this server is handing its history over rather than
360 /// starting a new one, and the next `PSYNC` asks to carry on from where this
361 /// server has got to rather than starting again, because the target is
362 /// caught up to exactly there and a snapshot would be a copy of what it
363 /// already holds.
364 pub(super) fn follow_for_failover(self: &Arc<Server>, host: &str, port: u16) {
365 self.follow.resume.store(true, Relaxed);
366 let host = yo_alloc::allow(|| host.to_owned());
367 self.follow_now(Some(Upstream { host, port }));
368 }
369
370 /// Start following, or stop.
371 ///
372 /// The intent is recorded and a thread is started, and the answer goes back
373 /// before the dial has been tried, which is what a real server does: there
374 /// is no reply to hold open while a socket is opened to somewhere that might
375 /// not answer for five seconds.
376 fn follow_now(self: &Arc<Server>, to: Option<Upstream>) {
377 let epoch = self.follow.epoch.fetch_add(1, Relaxed) + 1;
378 {
379 let mut upstream = self.follow.upstream.lock();
380 yo_alloc::allow(|| *upstream = to.clone());
381 }
382 let Some(to) = to else {
383 self.follow.on.store(false, Release);
384 self.follow.state.store(State::None as u8, Relaxed);
385 self.follow.resume.store(false, Relaxed);
386 return;
387 };
388 self.follow.on.store(true, Release);
389 self.follow.state.store(State::Connect as u8, Relaxed);
390 self.follow.down_ms.store(self.clock.now_ms(), Relaxed);
391 let server = Arc::clone(self);
392 yo_alloc::allow(|| {
393 let _ = std::thread::Builder::new()
394 .name(String::from("yo-replica"))
395 .spawn(move || link(&server, epoch, &to));
396 });
397 }
398}
399
400#[cfg(test)]
401impl Server {
402 /// Say this server follows somebody, without a socket and without a thread.
403 ///
404 /// The same idea as `repl::pretend_replica` and for the same reason. What
405 /// the tests below look at is the refusal an ordinary client gets, what
406 /// `INFO` and `ROLE` say, and what a master's own session is let past, and
407 /// every one of those reads the flags rather than the link. So a server told
408 /// this is a replica in every way a test can see, and no port anywhere has
409 /// to be listening.
410 pub(super) fn pretend_following(&self, host: &str, port: u16, up: bool) {
411 {
412 let mut upstream = self.follow.upstream.lock();
413 *upstream = Some(Upstream {
414 host: host.to_owned(),
415 port,
416 });
417 }
418 self.follow.on.store(true, Release);
419 self.follow
420 .state
421 .store(if up { State::Up } else { State::Connect } as u8, Relaxed);
422 self.follow.last_io_ms.store(self.clock.now_ms(), Relaxed);
423 self.follow.down_ms.store(self.clock.now_ms(), Relaxed);
424 }
425
426 /// Stop pretending, without the promotion `REPLICAOF NO ONE` does.
427 pub(super) fn pretend_master(&self) {
428 self.follow.on.store(false, Release);
429 self.follow.state.store(State::None as u8, Relaxed);
430 }
431}
432
433// ------------------------------------------------------------- the command
434
435/// `REPLICAOF host port` and `REPLICAOF NO ONE`, and `SLAVEOF` for the same.
436///
437/// The two words are the same command under two names, which is Redis's own
438/// arrangement: `SLAVEOF` is what it was called and answering to both is what
439/// stops a decade of scripts breaking. Nothing here reads which name was used.
440pub(super) fn replicaof(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
441 let name = if args.name().eq_ignore_ascii_case(b"slaveof") {
442 "slaveof"
443 } else {
444 "replicaof"
445 };
446 if args.len() != 3 {
447 return Err(args::wrong_arity(name));
448 }
449 // A failover is already deciding who the master is, and two commands that
450 // both decide that are two commands that can disagree. `FAILOVER ABORT` is
451 // the way out, which is why it is the only one.
452 if server.failing_over() {
453 return Err(Error::new(
454 Code::Invalid,
455 "REPLICAOF not allowed while failing over.",
456 ));
457 }
458 let host = args.get(1);
459 let port = args.get(2);
460 // Both words are read before anything is looked up, so a caller who got the
461 // command wrong hears what was wrong with it rather than hearing about the
462 // server it was sent to.
463 let told = if host.eq_ignore_ascii_case(b"no") && port.eq_ignore_ascii_case(b"one") {
464 None
465 } else {
466 // The reference's own sentence, and it is the answer to a port that is
467 // not a number as well as to one that is out of range. So this reads the
468 // digits itself rather than going through `args.int`, whose sentence is
469 // about integers and is the wrong one here.
470 let Some(port) = core::str::from_utf8(port)
471 .ok()
472 .and_then(|w| w.parse::<u16>().ok())
473 else {
474 return Err(Error::new(Code::Invalid, "Invalid master port"));
475 };
476 Some(port)
477 };
478 let Some(shared) = server.myself() else {
479 return Err(Error::new(
480 Code::Invalid,
481 "REPLICAOF is not available on an embedded server",
482 ));
483 };
484 let Some(port) = told else {
485 shared.stop_following();
486 out.ok();
487 return Ok(());
488 };
489 let host = yo_alloc::allow(|| String::from_utf8_lossy(host).into_owned());
490 // Told to follow the master it is already following, and there is nothing to
491 // do. Starting a link anyway would drop the one that is up and take a
492 // snapshot of a server this one is already in step with, which is what a real
493 // server refuses to do and says so in the same words.
494 {
495 let upstream = server.follow.upstream.lock();
496 let same = upstream
497 .as_ref()
498 .is_some_and(|at| at.port == port && at.host == host);
499 if same && server.following() {
500 out.simple(
501 b"OK REPLICAOF would result into synchronization with the master we are already connected with. No operation performed.",
502 );
503 return Ok(());
504 }
505 }
506 shared.follow_now(Some(Upstream { host, port }));
507 out.ok();
508 Ok(())
509}
510
511/// The refusal an ordinary client's write gets on a read only replica.
512pub(super) const READONLY: &str = "READONLY You can't write against a read only replica.";
513
514// ------------------------------------------------------------- the reporting
515
516/// The replica's half of the `Replication` section of `INFO`.
517///
518/// Written between `connected_slaves` and the identity lines, which is where
519/// Redis puts it, so a tool that reads the section in order sees the same shape.
520pub(super) fn info(server: &Server, s: &mut String) {
521 use core::fmt::Write as _;
522 let state = State::from(server.follow.state.load(Relaxed));
523 if state == State::None {
524 return;
525 }
526 let (host, port) = {
527 let upstream = server.follow.upstream.lock();
528 match upstream.as_ref() {
529 Some(up) => (up.host.clone(), up.port),
530 None => (String::new(), 0),
531 }
532 };
533 let now = server.clock.now_ms();
534 let up = state == State::Up;
535 let last = now.saturating_sub(server.follow.last_io_ms.load(Relaxed)) / 1000;
536 let offset = server.repl_offset();
537 let _ = write!(
538 s,
539 "master_host:{host}\r\nmaster_port:{port}\r\n\
540 master_link_status:{}\r\nmaster_last_io_seconds_ago:{}\r\n\
541 master_sync_in_progress:{}\r\n\
542 slave_read_repl_offset:{offset}\r\nslave_repl_offset:{offset}\r\n",
543 if up { "up" } else { "down" },
544 if up { last as i64 } else { -1 },
545 usize::from(state == State::Sync),
546 );
547 if !up {
548 let down = now.saturating_sub(server.follow.down_ms.load(Relaxed)) / 1000;
549 let _ = write!(s, "master_link_down_since_seconds:{down}\r\n");
550 }
551 // Priority and announcement are settings a failover reads and nothing here
552 // acts on, so they are reported at the values a server that was never
553 // configured has. Read only is the one of the three that is real.
554 let _ = write!(
555 s,
556 "slave_priority:100\r\nslave_read_only:{}\r\nreplica_announced:1\r\n",
557 usize::from(server.follow.read_only.load(Relaxed)),
558 );
559}
560
561/// The word `INFO` and `ROLE` lead with, which is the only thing on this server
562/// that two clients could disagree about if they asked at the wrong moment.
563#[must_use]
564pub(super) fn role_word(server: &Server) -> &'static str {
565 if server.following() {
566 "slave"
567 } else {
568 "master"
569 }
570}
571
572/// What `ROLE` answers on a replica.
573///
574/// Five fields: the word, the master's host and port, the link state as one of
575/// Redis's five words, and how much of the stream has been applied. The state
576/// words are not the two `INFO` uses, which is not a tidy arrangement and is the
577/// one every client library already reads.
578pub(super) fn role(server: &Server, out: &mut Out) {
579 let (host, port) = {
580 let upstream = server.follow.upstream.lock();
581 match upstream.as_ref() {
582 Some(up) => (up.host.clone(), up.port),
583 None => (String::new(), 0),
584 }
585 };
586 out.array(5);
587 out.bulk(b"slave");
588 out.bulk(host.as_bytes());
589 out.int(i64::from(port));
590 out.bulk(match State::from(server.follow.state.load(Relaxed)) {
591 State::Up => b"connected".as_slice(),
592 State::Sync => b"sync".as_slice(),
593 _ => b"connect".as_slice(),
594 });
595 out.int(server.repl_offset() as i64);
596}
597
598// ---------------------------------------------------------------- the link
599
600/// The link thread: dial, hand shake, load, follow, and do it again.
601///
602/// Every failure lands in the same place, which is a wait and another dial. That
603/// is the right shape for this because there is nothing else it could do: the
604/// operator asked for this server to follow that one, and a master that is not
605/// answering yet is the ordinary case at startup rather than an error.
606fn link(server: &Arc<Server>, epoch: u64, to: &Upstream) {
607 while server.follow.epoch.load(Relaxed) == epoch && !server.stopping() {
608 let _ = once(server, epoch, to);
609 if server.follow.epoch.load(Relaxed) != epoch {
610 return;
611 }
612 if State::from(server.follow.state.load(Relaxed)) != State::Connect {
613 server.follow.state.store(State::Connect as u8, Relaxed);
614 server.follow.down_ms.store(server.clock.now_ms(), Relaxed);
615 }
616 std::thread::sleep(RETRY);
617 }
618}
619
620/// One attempt: connect, hand shake, take what is offered, follow until it
621/// breaks.
622fn once(server: &Arc<Server>, epoch: u64, to: &Upstream) -> std::io::Result<()> {
623 let mut wire = dial(to)?;
624 handshake(server, &mut wire)?;
625 // Written and not sent through `command`, because what comes back is not one
626 // line the way every other answer in the handshake is: a full resync answers
627 // with a line and then a snapshot, and reading the line here is the same read
628 // either way.
629 // A fourth word on a server that is handing its job over, which is what
630 // tells the other end to stop being a replica and start being the master.
631 // See the `failover` module.
632 let handing_over = server.failover_stage() == super::failover::Stage::InProgress;
633 if server.follow.resume.load(Relaxed) {
634 // The server's own id and offset, which are the master's, because the
635 // last thing applied moved them and nothing else writes them while a
636 // link is up. One past the end, because the number a master reads is the
637 // position of the first byte wanted counted from one. The other side of
638 // the step `repl::psync` takes coming the other way.
639 let id = server.repl_id();
640 let from = (server.repl_offset() + 1).to_string();
641 if handing_over {
642 wire.write(&[b"PSYNC", &id, from.as_bytes(), b"FAILOVER"])?;
643 } else {
644 wire.write(&[b"PSYNC", &id, from.as_bytes()])?;
645 }
646 } else {
647 wire.write(&[b"PSYNC", b"?", b"-1"])?;
648 }
649 // A master that has to fork before it can answer takes as long as the fork
650 // takes, so this is the long wait and not the handshake's short one.
651 let head = wire.line(SYNC_TIMEOUT)?;
652 if head.starts_with(b"+FULLRESYNC ") {
653 full_resync(server, &mut wire, &head[12..])?;
654 server.follow.state.store(State::Up as u8, Relaxed);
655 server.follow.resume.store(true, Relaxed);
656 } else if head.starts_with(b"+CONTINUE") {
657 // A master that has changed its id since we last spoke says the new one
658 // here, and everything from this point is under that id.
659 if let Some(id) = head.get(10..).and_then(fixed_id) {
660 server.adopt(id, server.repl_offset());
661 }
662 server.follow.state.store(State::Up as u8, Relaxed);
663 server.follow.resume.store(true, Relaxed);
664 } else {
665 // A target that would not take the handover, which is a failover that
666 // cannot happen. This server takes its job back and lets the writes it
667 // has been holding through, rather than sitting paused forever waiting
668 // for a server that has already said no.
669 if handing_over {
670 super::failover::abort(server);
671 }
672 return Err(broken("the master would not resynchronise"));
673 }
674 // Answered either way, so the handover is done and the pause lifts.
675 super::failover::landed(server);
676 stream(server, epoch, &mut wire)
677}
678
679/// Read the snapshot and become it.
680///
681/// The header names the history and the position this image is an image as of,
682/// and both are taken as ours: from here on this server's stream is the master's
683/// stream, at the master's offsets, which is what makes a chain underneath it
684/// hand out positions anybody else can honour.
685fn full_resync(server: &Arc<Server>, wire: &mut Link, head: &[u8]) -> std::io::Result<()> {
686 let mut words = head.split(|&b| b == b' ');
687 let id = words
688 .next()
689 .and_then(fixed_id)
690 .ok_or_else(|| broken("the master named no replication id"))?;
691 let offset = words
692 .next()
693 .and_then(|w| core::str::from_utf8(w).ok())
694 .and_then(|w| w.trim().parse::<u64>().ok())
695 .ok_or_else(|| broken("the master named no offset"))?;
696 server.follow.state.store(State::Sync as u8, Relaxed);
697 let image = wire.payload()?;
698 server
699 .load_image(&image, true)
700 .map_err(|e| broken(&format!("the snapshot would not load: {e}")))?;
701 server.adopt(id, offset);
702 Ok(())
703}
704
705/// Follow the stream until it breaks or the link is called off.
706///
707/// Everything the loop does other than run a command is on a clock: it looks at
708/// the epoch to find out whether it is still wanted and it sends an
709/// acknowledgement once a second. Both of those are why the read has a timeout
710/// on it rather than blocking forever on a socket that is quiet.
711fn stream(server: &Arc<Server>, epoch: u64, wire: &mut Link) -> std::io::Result<()> {
712 let mut session = Session::new(server.next_client());
713 session.admit(true);
714 session.serve_master(true);
715 let mut out = Out::new(Proto::Resp2);
716 let mut argv = Argv::new();
717 let limits = Limits::default();
718 let mut acked = Instant::now();
719 let mut sent = 0u64;
720 APPLYING.set(true);
721 let ended = loop {
722 if server.follow.epoch.load(Relaxed) != epoch || server.stopping() {
723 break Ok(());
724 }
725 match argv.decode(wire.held(), &limits) {
726 Err(_) => break Err(broken("the master sent something that is not a command")),
727 Ok(Step::Incomplete) => {
728 if let Err(e) = wire.fill(POLL) {
729 break Err(e);
730 }
731 }
732 Ok(Step::Command { consumed }) => {
733 let getack = is_getack(&argv, wire.held());
734 if !getack {
735 apply(server, &mut session, &argv, wire.held(), &mut out);
736 }
737 // Counted before the acknowledgement is written, because the
738 // number the master is asking about includes the question.
739 let bytes = wire.take(consumed);
740 repl::relayed(server, bytes, session.db());
741 server
742 .follow
743 .last_io_ms
744 .store(server.clock.now_ms(), Relaxed);
745 if getack {
746 acked = Instant::now();
747 sent = server.repl_offset();
748 if let Err(e) = wire.ack(sent) {
749 break Err(e);
750 }
751 }
752 continue;
753 }
754 }
755 let now = server.repl_offset();
756 if acked.elapsed() >= ACK_EVERY || now != sent {
757 acked = Instant::now();
758 sent = now;
759 if let Err(e) = wire.ack(now) {
760 break Err(e);
761 }
762 }
763 };
764 APPLYING.set(false);
765 super::forget_session(server, &mut session);
766 ended
767}
768
769/// Whether the command sitting at the front of the buffer is the master asking
770/// where we have got to, which is answered rather than run.
771fn is_getack(argv: &Argv, buf: &[u8]) -> bool {
772 argv.len() == 3
773 && argv
774 .arg(buf, 0)
775 .is_some_and(|w| w.eq_ignore_ascii_case(b"replconf"))
776 && argv
777 .arg(buf, 1)
778 .is_some_and(|w| w.eq_ignore_ascii_case(b"getack"))
779}
780
781/// Run one command from the master against this server.
782///
783/// A command the freeze is holding is run again rather than dropped, because a
784/// full resync for a sub-replica is a pause of this server and not a reason to
785/// lose a byte of the master's stream. It is the one place in the tree that
786/// spins, and what it spins on is a snapshot being built, which finishes.
787fn apply(server: &Server, session: &mut Session, argv: &Argv, buf: &[u8], out: &mut Out) {
788 loop {
789 out.clear();
790 let args = Args::new(argv, buf);
791 if super::execute(server, session, args, out) != Flow::Hold {
792 return;
793 }
794 std::thread::sleep(Duration::from_millis(1));
795 }
796}
797
798// --------------------------------------------------------------- the socket
799
800/// The socket and whatever has arrived on it that has not been used yet.
801struct Link {
802 sock: TcpStream,
803 buf: Vec<u8>,
804}
805
806impl Link {
807 /// What has arrived and not been used.
808 fn held(&self) -> &[u8] {
809 &self.buf
810 }
811
812 /// Take the first `n` bytes off the front and answer with them.
813 fn take(&mut self, n: usize) -> Vec<u8> {
814 self.buf.drain(..n).collect()
815 }
816
817 /// Read once, waiting at most `wait`.
818 ///
819 /// A timeout is not a failure and answers with nothing added, which is what
820 /// lets the caller look around between reads. End of file is a failure,
821 /// because a master that closed the socket is a link that has to be dialled
822 /// again.
823 fn fill(&mut self, wait: Duration) -> std::io::Result<()> {
824 self.sock.set_read_timeout(Some(wait))?;
825 let mut chunk = [0u8; 16 * 1024];
826 match self.sock.read(&mut chunk) {
827 Ok(0) => Err(broken("the master closed the link")),
828 Ok(n) => {
829 self.buf.extend_from_slice(&chunk[..n]);
830 Ok(())
831 }
832 Err(e) if soft(&e) => Ok(()),
833 Err(e) => Err(e),
834 }
835 }
836
837 /// One line, without its newline, waiting at most `wait` in total.
838 fn line(&mut self, wait: Duration) -> std::io::Result<Vec<u8>> {
839 let until = Instant::now() + wait;
840 loop {
841 if let Some(at) = self.buf.iter().position(|&b| b == b'\n') {
842 let mut line = self.take(at + 1);
843 while line.last().is_some_and(|&b| b == b'\n' || b == b'\r') {
844 line.pop();
845 }
846 // A master preparing a snapshot sends bare newlines to keep the
847 // link warm. They are not a reply and are not counted.
848 if line.is_empty() {
849 continue;
850 }
851 return Ok(line);
852 }
853 if self.buf.len() > LINE_MAX {
854 return Err(broken("the master sent a line with no end to it"));
855 }
856 if Instant::now() >= until {
857 return Err(broken("the master did not answer"));
858 }
859 self.fill(POLL)?;
860 }
861 }
862
863 /// Send a command and read the one line it is answered with.
864 fn command(&mut self, parts: &[&[u8]]) -> std::io::Result<Vec<u8>> {
865 self.write(parts)?;
866 self.line(HANDSHAKE_TIMEOUT)
867 }
868
869 /// Send a command and do not wait for anything.
870 fn write(&mut self, parts: &[&[u8]]) -> std::io::Result<()> {
871 let mut wire = Vec::with_capacity(32);
872 wire.extend_from_slice(b"*");
873 wire.extend_from_slice(parts.len().to_string().as_bytes());
874 wire.extend_from_slice(b"\r\n");
875 for part in parts {
876 wire.extend_from_slice(b"$");
877 wire.extend_from_slice(part.len().to_string().as_bytes());
878 wire.extend_from_slice(b"\r\n");
879 wire.extend_from_slice(part);
880 wire.extend_from_slice(b"\r\n");
881 }
882 self.sock.write_all(&wire)
883 }
884
885 /// Tell the master how far we have got.
886 fn ack(&mut self, offset: u64) -> std::io::Result<()> {
887 self.write(&[b"REPLCONF", b"ACK", offset.to_string().as_bytes()])
888 }
889
890 /// The snapshot, which is a bulk string with nothing after it.
891 ///
892 /// The one place in the protocol where a bulk string has no newline behind
893 /// it, because everything after the last byte of it is the stream. A master
894 /// that was given `capa eof` would send a different shape here, which is why
895 /// the handshake does not offer it.
896 fn payload(&mut self) -> std::io::Result<Vec<u8>> {
897 let head = self.line(SYNC_TIMEOUT)?;
898 let want = core::str::from_utf8(head.get(1..).unwrap_or_default())
899 .ok()
900 .and_then(|n| n.parse::<usize>().ok())
901 .filter(|_| head.first() == Some(&b'$'))
902 .ok_or_else(|| broken("the master did not say how long the snapshot is"))?;
903 // The clock is on the gap between reads and not on the whole transfer,
904 // because a snapshot that is genuinely large is a link that is working
905 // and a link that stopped mid snapshot is one nothing will ever finish.
906 let mut last = Instant::now();
907 while self.buf.len() < want {
908 let had = self.buf.len();
909 self.fill(POLL)?;
910 if self.buf.len() > had {
911 last = Instant::now();
912 } else if last.elapsed() >= SYNC_TIMEOUT {
913 return Err(broken("the master stopped part way through the snapshot"));
914 }
915 }
916 Ok(self.take(want))
917 }
918}
919
920/// Whether an error means nothing arrived rather than that the link is gone.
921fn soft(e: &std::io::Error) -> bool {
922 matches!(
923 e.kind(),
924 ErrorKind::WouldBlock | ErrorKind::TimedOut | ErrorKind::Interrupted
925 )
926}
927
928/// A link failure with a sentence on it, which nobody reads and which is worth
929/// writing anyway: the moment one of these needs a log line, the sentence is
930/// already there.
931fn broken(why: &str) -> std::io::Error {
932 std::io::Error::other(String::from(why))
933}
934
935/// Forty hex characters, or nothing.
936fn fixed_id(word: &[u8]) -> Option<[u8; ID_LEN]> {
937 let word = word.strip_suffix(b"\r").unwrap_or(word);
938 let word = word.get(..ID_LEN)?;
939 word.iter()
940 .all(u8::is_ascii_hexdigit)
941 .then(|| <[u8; ID_LEN]>::try_from(word).ok())
942 .flatten()
943}
944
945/// Open the socket.
946fn dial(to: &Upstream) -> std::io::Result<Link> {
947 let at = (to.host.as_str(), to.port)
948 .to_socket_addrs()?
949 .next()
950 .ok_or_else(|| broken("the master's address does not resolve"))?;
951 let sock = TcpStream::connect_timeout(&at, DIAL_TIMEOUT)?;
952 sock.set_nodelay(true)?;
953 Ok(Link {
954 sock,
955 buf: Vec::new(),
956 })
957}
958
959/// Everything before `PSYNC`.
960///
961/// The capabilities are the two a master has to be told about and no more.
962/// `psync2` says this replica understands a partial resync across a promotion,
963/// which it does. `eof` is deliberately not offered: it tells a master it may
964/// send the snapshot without knowing its length first, and the shape that
965/// arrives then is different enough that not asking for it is cheaper than
966/// reading it.
967fn handshake(server: &Server, wire: &mut Link) -> std::io::Result<()> {
968 wire.command(&[b"PING"])?;
969 let (user, pass) = {
970 let auth = server.follow.auth.lock();
971 auth.clone()
972 };
973 if !pass.is_empty() {
974 let said = if user.is_empty() {
975 wire.command(&[b"AUTH", &pass])?
976 } else {
977 wire.command(&[b"AUTH", &user, &pass])?
978 };
979 if said.first() == Some(&b'-') {
980 return Err(broken("the master would not take the password"));
981 }
982 }
983 let port = server.follow.port.load(Relaxed).to_string();
984 wire.command(&[b"REPLCONF", b"listening-port", port.as_bytes()])?;
985 wire.command(&[b"REPLCONF", b"capa", b"psync2"])?;
986 server
987 .follow
988 .last_io_ms
989 .store(server.clock.now_ms(), Relaxed);
990 Ok(())
991}
992
993#[cfg(test)]
994mod tests {
995 use super::{ID_LEN, State, fixed_id, soft};
996
997 #[test]
998 fn a_replication_id_is_forty_hex_characters_and_nothing_else() {
999 let good = b"0123456789abcdef0123456789abcdef01234567";
1000 assert_eq!(fixed_id(good).unwrap(), *good);
1001 // The trailing carriage return of the line it was read out of comes off,
1002 // because the caller splits on the space and not on the line ending.
1003 let mut with_cr = good.to_vec();
1004 with_cr.push(b'\r');
1005 assert_eq!(fixed_id(&with_cr).unwrap(), *good);
1006 // Anything longer is read as the first forty, which is what a master
1007 // that appends something we do not know about would send.
1008 let mut longer = good.to_vec();
1009 longer.extend_from_slice(b"more");
1010 assert_eq!(fixed_id(&longer).unwrap(), *good);
1011 // Too short is nothing, and so is the right length with a character in
1012 // it that is not hex.
1013 assert!(fixed_id(&good[..ID_LEN - 1]).is_none());
1014 let mut wrong = good.to_vec();
1015 wrong[7] = b'z';
1016 assert!(fixed_id(&wrong).is_none());
1017 assert!(fixed_id(b"").is_none());
1018 }
1019
1020 #[test]
1021 fn a_state_that_is_not_one_of_the_four_reads_as_no_master() {
1022 assert!(State::from(1) == State::Connect);
1023 assert!(State::from(2) == State::Sync);
1024 assert!(State::from(3) == State::Up);
1025 assert!(State::from(0) == State::None);
1026 assert!(State::from(99) == State::None);
1027 }
1028
1029 #[test]
1030 fn a_read_that_timed_out_is_not_a_broken_link() {
1031 use std::io::{Error, ErrorKind};
1032 assert!(soft(&Error::from(ErrorKind::WouldBlock)));
1033 assert!(soft(&Error::from(ErrorKind::TimedOut)));
1034 assert!(soft(&Error::from(ErrorKind::Interrupted)));
1035 // Everything else is, including the one that means the master hung up.
1036 assert!(!soft(&Error::from(ErrorKind::ConnectionReset)));
1037 assert!(!soft(&Error::from(ErrorKind::UnexpectedEof)));
1038 }
1039}