1use bytes::Bytes;
2use std::sync::Arc;
3use tokio::io::{AsyncRead, AsyncWrite, BufReader};
4use tokio::sync::{mpsc, watch};
5use tokio::time::Instant;
6
7use super::metrics::ReplicationMetrics;
8use super::progress::ProgressCell;
9use crate::config::ReplicationConfig;
10use crate::error::{PgWireError, Result};
11use crate::lsn::Lsn;
12use crate::protocol::framing::{
13 read_backend_message, write_copy_data, write_copy_done, write_password_message, write_query,
14 write_startup_message, MessageReader,
15};
16use crate::protocol::messages::{parse_auth_request, parse_error_response};
17use crate::protocol::replication::{
18 encode_standby_status_update, parse_copy_data, ReplicationCopyData, PG_EPOCH_MICROS,
19};
20
21pub struct SharedProgress {
29 cell: ProgressCell,
30 changed: watch::Sender<u64>,
31}
32
33fn clock_origin() -> Instant {
38 static ORIGIN: std::sync::OnceLock<Instant> = std::sync::OnceLock::new();
39 *ORIGIN.get_or_init(|| {
40 let now = Instant::now();
41 now.checked_sub(std::time::Duration::from_secs(3600))
42 .unwrap_or(now)
43 })
44}
45
46impl SharedProgress {
47 #[cfg(test)]
48 pub fn new(start: Lsn) -> Self {
49 Self::with_timing(start, false)
50 }
51
52 pub fn with_timing(start: Lsn, timed: bool) -> Self {
55 Self {
56 cell: ProgressCell::new(start.as_u64(), timed),
57 changed: watch::Sender::new(0),
58 }
59 }
60
61 fn clock(&self) -> u64 {
62 clock_origin().elapsed().as_nanos() as u64 + 1
63 }
64
65 fn instant(&self, clock: u64) -> Instant {
66 clock_origin() + std::time::Duration::from_nanos(clock - 1)
67 }
68
69 pub fn pending_since(&self) -> Option<std::time::Instant> {
71 match self.cell.pending_since() {
72 0 => None,
73 clock => Some(self.instant(clock).into_std()),
74 }
75 }
76
77 pub fn carry_pending(&self, since: std::time::Instant) {
79 let since = Instant::from_std(since);
80 let clock = since.saturating_duration_since(clock_origin()).as_nanos() as u64 + 1;
81 self.cell.carry_pending(clock);
82 }
83
84 #[inline]
85 pub fn load_applied(&self) -> Lsn {
86 Lsn::from_u64(self.cell.load())
87 }
88
89 #[inline]
92 pub fn update_applied(&self, lsn: Lsn) {
93 if self.cell.raise(lsn.as_u64(), || self.clock()) {
94 self.changed.send_modify(|g| *g = g.wrapping_add(1));
95 }
96 }
97
98 pub fn subscribe(&self) -> watch::Receiver<u64> {
100 self.changed.subscribe()
101 }
102
103 fn take_signal(&self) -> Lsn {
106 Lsn::from_u64(self.cell.take_signal())
107 }
108}
109
110#[derive(Debug, Clone)]
112pub enum ReplicationEvent {
113 KeepAlive {
115 wal_end: Lsn,
117 reply_requested: bool,
119 server_time_micros: i64,
121 },
122
123 Begin {
125 final_lsn: Lsn,
126 xid: u32,
127 commit_time_micros: i64,
128 },
129
130 XLogData {
132 wal_start: Lsn,
134 wal_end: Lsn,
136 server_time_micros: i64,
138 data: Bytes,
140 },
141
142 Commit {
144 lsn: Lsn,
145 end_lsn: Lsn,
146 commit_time_micros: i64,
147 },
148
149 Message {
155 transactional: bool,
157 lsn: Lsn,
159 prefix: String,
161 content: Bytes,
163 },
164
165 StoppedAt {
170 reached: Lsn,
172 },
173}
174
175pub type ReplicationEventReceiver =
177 mpsc::Receiver<std::result::Result<ReplicationEvent, PgWireError>>;
178
179pub type QueryRow = Vec<Option<Bytes>>;
182
183pub(crate) enum GateCommand {
186 Query {
189 sql: String,
190 reply: tokio::sync::oneshot::Sender<Result<Vec<QueryRow>>>,
191 },
192 Start,
194}
195
196pub struct WorkerState {
198 cfg: ReplicationConfig,
199 progress: Arc<SharedProgress>,
200 stop_rx: watch::Receiver<bool>,
201 out: mpsc::Sender<std::result::Result<ReplicationEvent, PgWireError>>,
202 metrics: Arc<ReplicationMetrics>,
203 started: Option<watch::Sender<bool>>,
205 gate: Option<mpsc::Receiver<GateCommand>>,
208 last_sent: Option<Lsn>,
210}
211
212impl WorkerState {
213 pub fn new(
214 cfg: ReplicationConfig,
215 progress: Arc<SharedProgress>,
216 stop_rx: watch::Receiver<bool>,
217 out: mpsc::Sender<std::result::Result<ReplicationEvent, PgWireError>>,
218 metrics: Arc<ReplicationMetrics>,
219 ) -> Self {
220 Self {
221 cfg,
222 progress,
223 stop_rx,
224 out,
225 metrics,
226 started: None,
227 gate: None,
228 last_sent: None,
229 }
230 }
231
232 pub(crate) fn notify_started(&mut self, started: watch::Sender<bool>) {
234 self.started = Some(started);
235 }
236
237 pub(crate) fn gate_before_start(&mut self, gate: mpsc::Receiver<GateCommand>) {
241 self.gate = Some(gate);
242 }
243
244 async fn serve_gate<S: AsyncRead + AsyncWrite + Unpin>(
247 gate: &mut mpsc::Receiver<GateCommand>,
248 stream: &mut S,
249 max_message_size: usize,
250 ) -> Result<bool> {
251 loop {
252 match gate.recv().await {
253 Some(GateCommand::Query { sql, reply }) => {
254 let result = simple_query(stream, &sql, max_message_size).await;
255 let broken = matches!(&result, Err(e) if !e.is_server());
259 let ended = result.as_ref().err().cloned();
260 let _ = reply.send(result);
261 if broken {
262 return Err(ended.expect("checked error"));
263 }
264 }
265 Some(GateCommand::Start) => return Ok(true),
266 None => {
267 write_terminate(stream).await?;
268 return Ok(false);
269 }
270 }
271 }
272 }
273
274 pub async fn run_on_stream<S: AsyncRead + AsyncWrite + Unpin>(
276 &mut self,
277 stream: &mut S,
278 ) -> Result<()> {
279 let mut stream = BufReader::with_capacity(128 * 1024, stream);
283 self.startup(&mut stream).await?;
284 self.authenticate(&mut stream).await?;
285 if let Some(mut gate) = self.gate.take() {
286 if !Self::serve_gate(&mut gate, &mut stream, self.cfg.max_message_size).await? {
287 return Ok(());
288 }
289 }
290 self.start_replication(&mut stream).await?;
291 if let Some(started) = &self.started {
292 started.send_replace(true);
293 }
294 self.stream_loop(&mut stream).await
295 }
296
297 async fn startup<S: AsyncWrite + Unpin>(&self, stream: &mut S) -> Result<()> {
299 let mut params = vec![
300 ("user", self.cfg.user.as_str()),
301 ("database", self.cfg.database.as_str()),
302 ("replication", "database"),
303 ("client_encoding", "UTF8"),
304 (
305 "application_name",
306 self.cfg
307 .application_name
308 .as_deref()
309 .unwrap_or("pgwire-replication"),
310 ),
311 ];
312 if let Some(options) = self.cfg.options.as_deref() {
317 params.push(("options", options));
318 }
319 write_startup_message(stream, 196608, ¶ms).await
320 }
321
322 async fn start_replication<S: AsyncRead + AsyncWrite + Unpin>(
324 &self,
325 stream: &mut S,
326 ) -> Result<()> {
327 let binary_opt = if self.cfg.binary {
328 ", binary 'true'"
329 } else {
330 ""
331 };
332 let sql = format!(
333 "START_REPLICATION SLOT {} LOGICAL {} \
334 (proto_version '1', publication_names '{}', messages 'true'{})",
335 self.cfg.slot,
336 self.cfg.start_lsn,
337 self.cfg.publication.to_option_value(),
338 binary_opt,
339 );
340 write_query(stream, &sql).await?;
341
342 loop {
344 let msg = read_backend_message(stream).await?;
345 match msg.tag {
346 b'W' => return Ok(()), b'E' => return Err(PgWireError::Server(parse_error_response(&msg.payload))),
348 b'N' | b'S' | b'K' => continue, _ => continue,
350 }
351 }
352 }
353
354 async fn stream_loop<S: AsyncRead + AsyncWrite + Unpin>(
365 &mut self,
366 stream: &mut BufReader<S>,
367 ) -> Result<()> {
368 let mut last_status_sent = Instant::now() - self.cfg.status_interval;
369 let mut last_applied = self.progress.load_applied();
370 let mut applied_rx = self.progress.subscribe();
372 let mut reader = MessageReader::new().with_max_message_size(self.cfg.max_message_size);
374 const DRAIN_BATCH: usize = 256;
377
378 loop {
379 let current_applied = self.progress.load_applied();
381 if current_applied != last_applied {
382 last_applied = current_applied;
383 }
384
385 if last_status_sent.elapsed() >= self.cfg.status_interval {
387 self.send_feedback(stream, last_applied, false).await?;
388 last_status_sent = Instant::now();
389 }
390
391 let mut drained = 0usize;
396 while stream.buffer().len() >= 5 && drained < DRAIN_BATCH {
397 let msg = reader.read(stream).await?;
398 drained += 1;
399 if msg.tag == b'E' {
400 return Err(PgWireError::Server(parse_error_response(&msg.payload)));
401 }
402 if msg.tag == b'd'
403 && self
404 .handle_copy_data(
405 stream,
406 msg.payload,
407 &mut last_applied,
408 &mut last_status_sent,
409 )
410 .await?
411 {
412 return Ok(());
413 }
414 }
415
416 if drained > 0 {
419 if self.stop_rx.has_changed().unwrap_or(false) && *self.stop_rx.borrow() {
421 let _ = write_copy_done(stream).await;
422 return Ok(());
423 }
424 continue;
425 }
426
427 applied_rx.mark_unchanged();
440 let applied = self.progress.take_signal();
441 let pending = self.last_sent.is_none_or(|sent| applied > sent);
442 let feedback_due = last_status_sent + self.cfg.status_interval;
443 let msg = tokio::select! {
444 biased;
445
446 _ = self.stop_rx.changed() => {
447 if *self.stop_rx.borrow() {
448 let _ = write_copy_done(stream).await;
449 return Ok(());
450 }
451 continue;
452 }
453
454 _ = tokio::time::sleep_until(feedback_due), if pending => {
455 let applied = self.progress.load_applied();
456 last_applied = applied;
457 self.send_feedback(stream, applied, false).await?;
458 last_status_sent = Instant::now();
459 continue;
460 }
461
462 _ = applied_rx.changed(), if !pending => continue,
464
465 msg_result = tokio::time::timeout(
466 self.cfg.idle_wakeup_interval,
467 reader.read(stream),
468 ) => {
469 match msg_result {
470 Ok(res) => res?,
471 Err(_) => {
472 let applied = self.progress.load_applied();
473 last_applied = applied;
474 self.send_feedback(stream, applied, false).await?;
475 last_status_sent = Instant::now();
476 continue;
477 }
478 }
479 }
480 };
481
482 if msg.tag == b'E' {
483 return Err(PgWireError::Server(parse_error_response(&msg.payload)));
484 }
485 if msg.tag == b'd'
486 && self
487 .handle_copy_data(
488 stream,
489 msg.payload,
490 &mut last_applied,
491 &mut last_status_sent,
492 )
493 .await?
494 {
495 return Ok(());
496 }
497 }
498 }
499
500 async fn handle_copy_data<S: AsyncRead + AsyncWrite + Unpin>(
502 &mut self,
503 stream: &mut BufReader<S>,
504 payload: Bytes,
505 last_applied: &mut Lsn,
506 last_status_sent: &mut Instant,
507 ) -> Result<bool> {
508 let cd = parse_copy_data(payload)?;
509
510 match cd {
511 ReplicationCopyData::KeepAlive {
512 wal_end,
513 server_time_micros,
514 reply_requested,
515 } => {
516 self.metrics.set_last_wal_end(wal_end);
517
518 if reply_requested {
520 let applied = self.progress.load_applied();
521 *last_applied = applied;
522 self.send_feedback(stream, applied, true).await?;
523 *last_status_sent = Instant::now();
524 self.metrics.record_keepalive_reply();
525 }
526
527 if self
528 .send_event_backpressured(
529 stream,
530 Ok(ReplicationEvent::KeepAlive {
531 wal_end,
532 reply_requested,
533 server_time_micros,
534 }),
535 last_status_sent,
536 )
537 .await?
538 {
539 return Ok(true);
540 }
541
542 Ok(false)
543 }
544 ReplicationCopyData::XLogData {
545 wal_start,
546 wal_end,
547 server_time_micros,
548 data,
549 } => {
550 self.metrics.set_last_wal_end(wal_end);
551
552 if let Some(boundary_ev) = parse_pgoutput_boundary(&data)? {
554 let reached_lsn = match boundary_ev {
555 ReplicationEvent::Begin { final_lsn, .. } => final_lsn,
556 ReplicationEvent::Commit { end_lsn, .. } => end_lsn,
557 _ => wal_end, };
559
560 if self
561 .send_event_backpressured(stream, Ok(boundary_ev), last_status_sent)
562 .await?
563 {
564 return Ok(true);
565 }
566
567 if let Some(stop_lsn) = self.cfg.stop_at_lsn {
569 if reached_lsn >= stop_lsn {
570 if self
571 .send_event_backpressured(
572 stream,
573 Ok(ReplicationEvent::StoppedAt {
574 reached: reached_lsn,
575 }),
576 last_status_sent,
577 )
578 .await?
579 {
580 return Ok(true);
581 }
582 let _ = write_copy_done(stream).await;
583 return Ok(true); }
585 }
586
587 return Ok(false);
588 }
589 if let Some(stop_lsn) = self.cfg.stop_at_lsn {
592 if wal_end >= stop_lsn {
593 if self
595 .send_event_backpressured(
596 stream,
597 Ok(ReplicationEvent::XLogData {
598 wal_start,
599 wal_end,
600 server_time_micros,
601 data,
602 }),
603 last_status_sent,
604 )
605 .await?
606 {
607 return Ok(true);
608 }
609
610 if self
611 .send_event_backpressured(
612 stream,
613 Ok(ReplicationEvent::StoppedAt { reached: wal_end }),
614 last_status_sent,
615 )
616 .await?
617 {
618 return Ok(true);
619 }
620
621 let _ = write_copy_done(stream).await;
622 return Ok(true);
623 }
624 }
625
626 if self
627 .send_event_backpressured(
628 stream,
629 Ok(ReplicationEvent::XLogData {
630 wal_start,
631 wal_end,
632 server_time_micros,
633 data,
634 }),
635 last_status_sent,
636 )
637 .await?
638 {
639 return Ok(true);
640 }
641
642 Ok(false)
643 }
644 }
645 }
646
647 async fn send_event_backpressured<S: AsyncWrite + Unpin>(
664 &mut self,
665 stream: &mut S,
666 event: std::result::Result<ReplicationEvent, PgWireError>,
667 last_status_sent: &mut Instant,
668 ) -> Result<bool> {
669 match self.out.try_reserve() {
671 Ok(permit) => {
672 permit.send(event);
673 self.metrics.record_event_forwarded();
674 return Ok(false);
675 }
676 Err(mpsc::error::TrySendError::Closed(())) => {
677 tracing::debug!("event channel closed, client may have disconnected");
678 return Ok(false);
679 }
680 Err(mpsc::error::TrySendError::Full(())) => {}
683 }
684
685 self.metrics.record_stall_begin();
688 let stall_start = Instant::now();
689 loop {
690 let deadline = *last_status_sent + self.cfg.status_interval;
691 tokio::select! {
692 biased;
693
694 _ = self.stop_rx.changed() => {
695 if *self.stop_rx.borrow() {
696 let _ = write_copy_done(stream).await;
697 return Ok(true);
698 }
699 continue;
700 }
701
702 permit = self.out.reserve() => {
703 self.metrics
704 .record_stall_end(stall_start.elapsed().as_micros() as u64);
705 match permit {
706 Ok(permit) => {
707 permit.send(event);
708 self.metrics.record_event_forwarded();
709 }
710 Err(_closed) => {
711 tracing::debug!("event channel closed while backpressured");
712 }
713 }
714 return Ok(false);
715 }
716
717 _ = tokio::time::sleep_until(deadline) => {}
718 }
719 let applied = self.progress.load_applied();
721 self.send_feedback(stream, applied, false).await?;
722 *last_status_sent = Instant::now();
723 }
724 }
725
726 async fn authenticate<S: AsyncRead + AsyncWrite + Unpin>(
728 &mut self,
729 stream: &mut S,
730 ) -> Result<()> {
731 loop {
732 let msg = read_backend_message(stream).await?;
733 match msg.tag {
734 b'R' => {
735 let (code, rest) = parse_auth_request(&msg.payload)?;
736 self.handle_auth_request(stream, code, rest).await?;
737 }
738 b'E' => return Err(PgWireError::Server(parse_error_response(&msg.payload))),
739 b'S' | b'K' => {} b'Z' => return Ok(()), _ => {}
742 }
743 }
744 }
745
746 async fn handle_auth_request<S: AsyncRead + AsyncWrite + Unpin>(
748 &mut self,
749 stream: &mut S,
750 code: i32,
751 data: &[u8],
752 ) -> Result<()> {
753 match code {
754 0 => Ok(()), 3 => {
756 let mut payload = Vec::from(self.cfg.password.as_bytes());
758 payload.push(0);
759 write_password_message(stream, &payload).await
760 }
761 10 => {
762 self.auth_scram(stream, data).await
764 }
765 #[cfg(feature = "md5")]
766 5 => {
767 if data.len() != 4 {
769 return Err(PgWireError::Protocol(
770 "MD5 auth: expected 4-byte salt".into(),
771 ));
772 }
773 let mut salt = [0u8; 4];
774 salt.copy_from_slice(&data[..4]);
775
776 let hash = postgres_md5(&self.cfg.password, &self.cfg.user, &salt);
777 let mut payload = hash.into_bytes();
778 payload.push(0);
779 write_password_message(stream, &payload).await
780 }
781 _ => Err(PgWireError::Auth(format!(
782 "unsupported auth method code: {code}"
783 ))),
784 }
785 }
786
787 async fn auth_scram<S: AsyncRead + AsyncWrite + Unpin>(
789 &mut self,
790 stream: &mut S,
791 mechanisms_data: &[u8],
792 ) -> Result<()> {
793 let mechanisms = parse_sasl_mechanisms(mechanisms_data);
795
796 if !mechanisms.iter().any(|m| m == "SCRAM-SHA-256") {
797 return Err(PgWireError::Auth(format!(
798 "server doesn't offer SCRAM-SHA-256, available: {mechanisms:?}"
799 )));
800 }
801
802 #[cfg(not(feature = "scram"))]
803 return Err(PgWireError::Auth(
804 "SCRAM authentication required but 'scram' feature not enabled".into(),
805 ));
806
807 #[cfg(feature = "scram")]
808 {
809 use crate::auth::scram::ScramClient;
810
811 let scram = ScramClient::new(&self.cfg.user);
812
813 let mut init = Vec::new();
815 init.extend_from_slice(b"SCRAM-SHA-256\0");
816 init.extend_from_slice(&(scram.client_first.len() as i32).to_be_bytes());
817 init.extend_from_slice(scram.client_first.as_bytes());
818 write_password_message(stream, &init).await?;
819
820 let server_first = read_auth_data(stream, 11).await?;
822 let server_first_str = String::from_utf8_lossy(&server_first);
823
824 let (client_final, auth_message, salted_password) =
826 scram.client_final(&self.cfg.password, &server_first_str)?;
827 write_password_message(stream, client_final.as_bytes()).await?;
828
829 let server_final = read_auth_data(stream, 12).await?;
831 let server_final_str = String::from_utf8_lossy(&server_final);
832 ScramClient::verify_server_final(&server_final_str, &salted_password, &auth_message)?;
833
834 Ok(())
835 }
836 }
837
838 async fn send_feedback<S: AsyncWrite + Unpin>(
843 &mut self,
844 stream: &mut S,
845 applied: Lsn,
846 reply_requested: bool,
847 ) -> Result<()> {
848 let loaded_at = (self.cfg.feedback_observer.is_some()
851 && self.last_sent.is_none_or(|sent| applied > sent))
852 .then(|| self.progress.clock());
853 let client_time = current_pg_timestamp();
854 let payload = encode_standby_status_update(applied, client_time, reply_requested);
855 write_copy_data(stream, &payload).await?;
856 self.last_sent = Some(applied);
857 self.metrics.record_feedback_sent(applied);
858 if let (Some(at), Some(observer)) = (loaded_at, &self.cfg.feedback_observer) {
859 let since = self.progress.cell.take_pending(applied.as_u64(), at);
860 if since != 0 {
861 observer.observe(crate::FeedbackSent {
862 pending_for: self.progress.instant(since).elapsed(),
863 });
864 }
865 }
866 Ok(())
867 }
868}
869
870async fn simple_query<S: AsyncRead + AsyncWrite + Unpin>(
874 stream: &mut S,
875 sql: &str,
876 max_message_size: usize,
877) -> Result<Vec<QueryRow>> {
878 write_query(stream, sql).await?;
879 let mut reader = MessageReader::new().with_max_message_size(max_message_size);
882 let mut rows = Vec::new();
883 let mut error = None;
884 loop {
885 let msg = reader.read(stream).await?;
886 match msg.tag {
887 b'D' => rows.push(parse_data_row(&msg.payload)?),
888 b'E' => error = Some(PgWireError::Server(parse_error_response(&msg.payload))),
889 b'Z' => return error.map_or(Ok(rows), Err),
890 _ => continue,
893 }
894 }
895}
896
897fn parse_data_row(payload: &Bytes) -> Result<QueryRow> {
899 let malformed = || PgWireError::Protocol("malformed DataRow".into());
900 let mut p = payload.clone();
901 if p.len() < 2 {
902 return Err(malformed());
903 }
904 let columns = i16::from_be_bytes([p[0], p[1]]);
905 p = p.slice(2..);
906 let mut row = Vec::with_capacity(columns.max(0) as usize);
907 for _ in 0..columns {
908 if p.len() < 4 {
909 return Err(malformed());
910 }
911 let len = i32::from_be_bytes([p[0], p[1], p[2], p[3]]);
912 p = p.slice(4..);
913 if len < 0 {
914 row.push(None);
915 continue;
916 }
917 let len = len as usize;
918 if p.len() < len {
919 return Err(malformed());
920 }
921 row.push(Some(p.slice(..len)));
922 p = p.slice(len..);
923 }
924 Ok(row)
925}
926
927async fn write_terminate<S: AsyncWrite + Unpin>(stream: &mut S) -> Result<()> {
929 use tokio::io::AsyncWriteExt;
930 stream.write_all(&[b'X', 0, 0, 0, 4]).await?;
931 stream.flush().await?;
932 Ok(())
933}
934
935fn parse_sasl_mechanisms(data: &[u8]) -> Vec<String> {
937 let mut mechanisms = Vec::new();
938 let mut remaining = data;
939
940 while !remaining.is_empty() {
941 if let Some(pos) = remaining.iter().position(|&x| x == 0) {
942 if pos == 0 {
943 break; }
945 mechanisms.push(String::from_utf8_lossy(&remaining[..pos]).to_string());
946 remaining = &remaining[pos + 1..];
947 } else {
948 break;
949 }
950 }
951
952 mechanisms
953}
954
955fn parse_pgoutput_boundary(data: &Bytes) -> Result<Option<ReplicationEvent>> {
956 if data.is_empty() {
957 return Ok(None);
958 }
959
960 let tag = data[0];
961 let mut p = &data[1..];
962
963 fn take_i8(p: &mut &[u8]) -> Result<i8> {
964 if p.is_empty() {
965 return Err(PgWireError::Protocol("pgoutput: truncated i8".into()));
966 }
967 let v = p[0] as i8;
968 *p = &p[1..];
969 Ok(v)
970 }
971
972 fn take_i32(p: &mut &[u8]) -> Result<i32> {
973 if p.len() < 4 {
974 return Err(PgWireError::Protocol("pgoutput: truncated i32".into()));
975 }
976 let (head, tail) = p.split_at(4);
977 *p = tail;
978 Ok(i32::from_be_bytes(head.try_into().unwrap()))
979 }
980
981 fn take_i64(p: &mut &[u8]) -> Result<i64> {
982 if p.len() < 8 {
983 return Err(PgWireError::Protocol("pgoutput: truncated i64".into()));
984 }
985 let (head, tail) = p.split_at(8);
986 *p = tail;
987 Ok(i64::from_be_bytes(head.try_into().unwrap()))
988 }
989
990 match tag {
991 b'B' => {
992 let final_lsn = Lsn::from_u64(take_i64(&mut p)? as u64);
993 let commit_time_micros = take_i64(&mut p)?;
994 let xid = take_i32(&mut p)? as u32;
995
996 Ok(Some(ReplicationEvent::Begin {
997 final_lsn,
998 commit_time_micros,
999 xid,
1000 }))
1001 }
1002 b'C' => {
1003 let _flags = take_i8(&mut p)?;
1004 let lsn = Lsn::from_u64(take_i64(&mut p)? as u64); let end_lsn = Lsn::from_u64(take_i64(&mut p)? as u64);
1006 let commit_time_micros = take_i64(&mut p)?;
1007
1008 Ok(Some(ReplicationEvent::Commit {
1009 lsn,
1010 end_lsn,
1011 commit_time_micros,
1012 }))
1013 }
1014 b'M' => {
1015 let flags = take_i8(&mut p)?;
1018 let transactional = (flags & 1) != 0;
1019 let lsn = Lsn::from_u64(take_i64(&mut p)? as u64);
1020
1021 let prefix_end = p.iter().position(|&b| b == 0).ok_or_else(|| {
1023 PgWireError::Protocol("pgoutput Message: missing null terminator for prefix".into())
1024 })?;
1025 let prefix = String::from_utf8_lossy(&p[..prefix_end]).into_owned();
1026 p = &p[prefix_end + 1..]; let content_len = take_i32(&mut p)? as usize;
1029 if p.len() < content_len {
1030 return Err(PgWireError::Protocol(format!(
1031 "pgoutput Message: expected {} content bytes, got {}",
1032 content_len,
1033 p.len()
1034 )));
1035 }
1036 let content = Bytes::copy_from_slice(&p[..content_len]);
1037
1038 Ok(Some(ReplicationEvent::Message {
1039 transactional,
1040 lsn,
1041 prefix,
1042 content,
1043 }))
1044 }
1045 _ => Ok(None),
1046 }
1047}
1048
1049async fn read_auth_data<S: AsyncRead + AsyncWrite + Unpin>(
1051 stream: &mut S,
1052 expected_code: i32,
1053) -> Result<Vec<u8>> {
1054 loop {
1055 let msg = read_backend_message(stream).await?;
1056 match msg.tag {
1057 b'R' => {
1058 let (code, data) = parse_auth_request(&msg.payload)?;
1059 if code == expected_code {
1060 return Ok(data.to_vec());
1061 }
1062 return Err(PgWireError::Auth(format!(
1063 "unexpected auth code {code}, expected {expected_code}"
1064 )));
1065 }
1066 b'E' => return Err(PgWireError::Server(parse_error_response(&msg.payload))),
1067 _ => {} }
1069 }
1070}
1071
1072fn current_pg_timestamp() -> i64 {
1074 use std::time::{SystemTime, UNIX_EPOCH};
1075
1076 let now = SystemTime::now()
1077 .duration_since(UNIX_EPOCH)
1078 .unwrap_or_default();
1079
1080 let unix_micros = (now.as_secs() as i64) * 1_000_000 + (now.subsec_micros() as i64);
1081 unix_micros - PG_EPOCH_MICROS
1082}
1083
1084#[cfg(feature = "md5")]
1086fn postgres_md5(password: &str, user: &str, salt: &[u8; 4]) -> String {
1087 fn md5_hex(data: &[u8]) -> String {
1088 format!("{:x}", md5::compute(data))
1089 }
1090
1091 let inner = md5_hex(format!("{password}{user}").as_bytes());
1093
1094 let mut outer_input = inner.into_bytes();
1096 outer_input.extend_from_slice(salt);
1097
1098 format!("md5{}", md5_hex(&outer_input))
1099}
1100
1101#[cfg(test)]
1102mod tests {
1103 use super::*;
1104
1105 #[test]
1106 fn parse_sasl_mechanisms_single() {
1107 let data = b"SCRAM-SHA-256\0\0";
1108 let mechs = parse_sasl_mechanisms(data);
1109 assert_eq!(mechs, vec!["SCRAM-SHA-256"]);
1110 }
1111
1112 #[test]
1113 fn parse_sasl_mechanisms_multiple() {
1114 let data = b"SCRAM-SHA-256\0SCRAM-SHA-256-PLUS\0\0";
1115 let mechs = parse_sasl_mechanisms(data);
1116 assert_eq!(mechs, vec!["SCRAM-SHA-256", "SCRAM-SHA-256-PLUS"]);
1117 }
1118
1119 #[test]
1120 fn parse_sasl_mechanisms_empty() {
1121 let mechs = parse_sasl_mechanisms(b"\0");
1122 assert!(mechs.is_empty());
1123 }
1124
1125 #[test]
1126 #[cfg(feature = "md5")]
1127 fn postgres_md5_known_value() {
1128 let hash = postgres_md5("md5_pass", "md5_user", &[0x01, 0x02, 0x03, 0x04]);
1131 assert!(hash.starts_with("md5"));
1132 assert_eq!(hash.len(), 35); }
1134
1135 #[test]
1136 fn current_pg_timestamp_is_positive() {
1137 let ts = current_pg_timestamp();
1139 assert!(ts > 0);
1140 }
1141
1142 fn xlog_frame(wal_end: u64, data: &[u8]) -> Vec<u8> {
1146 let mut payload = Vec::new();
1147 payload.push(b'w');
1148 payload.extend_from_slice(&(wal_end as i64).to_be_bytes()); payload.extend_from_slice(&(wal_end as i64).to_be_bytes()); payload.extend_from_slice(&0i64.to_be_bytes()); payload.extend_from_slice(data);
1152
1153 let mut frame = Vec::new();
1154 frame.push(b'd');
1155 frame.extend_from_slice(&((payload.len() + 4) as i32).to_be_bytes());
1156 frame.extend_from_slice(&payload);
1157 frame
1158 }
1159
1160 fn test_worker(
1164 cfg: ReplicationConfig,
1165 ) -> (
1166 WorkerState,
1167 watch::Sender<bool>,
1168 mpsc::Receiver<std::result::Result<ReplicationEvent, PgWireError>>,
1169 ) {
1170 let progress = Arc::new(SharedProgress::new(cfg.ack_lsn.unwrap_or(cfg.start_lsn)));
1171 let (stop_tx, stop_rx) = watch::channel(false);
1172 let (tx, rx) = mpsc::channel(16);
1173 let metrics = Arc::new(ReplicationMetrics::default());
1174 let worker = WorkerState::new(cfg, progress, stop_rx, tx, metrics);
1175 (worker, stop_tx, rx)
1176 }
1177
1178 #[tokio::test]
1184 async fn startup_includes_options_when_set() {
1185 use tokio::io::{AsyncReadExt, AsyncWriteExt};
1186
1187 let cfg = ReplicationConfig::new("h", "u", "p", "db", "slot", "pub")
1188 .with_options("-c datestyle=ISO,MDY -c intervalstyle=iso_8601");
1189 let (worker, _stop_tx, _rx) = test_worker(cfg);
1190 let (mut worker_end, mut server) = tokio::io::duplex(64 * 1024);
1191
1192 worker.startup(&mut worker_end).await.unwrap();
1193 worker_end.shutdown().await.unwrap();
1194
1195 let mut sent = Vec::new();
1196 server.read_to_end(&mut sent).await.unwrap();
1197 let sent = String::from_utf8_lossy(&sent);
1198
1199 assert!(
1200 sent.contains("options"),
1201 "startup message missing 'options' key: {sent:?}"
1202 );
1203 assert!(
1204 sent.contains("-c datestyle=ISO,MDY -c intervalstyle=iso_8601"),
1205 "startup message missing options value: {sent:?}"
1206 );
1207 }
1208
1209 #[tokio::test]
1210 async fn startup_sends_the_application_name() {
1211 use tokio::io::{AsyncReadExt, AsyncWriteExt};
1212
1213 for (cfg, expected) in [
1214 (ReplicationConfig::default(), "pgwire-replication"),
1215 (
1216 ReplicationConfig::default().with_application_name("df-7f3a"),
1217 "df-7f3a",
1218 ),
1219 ] {
1220 let (worker, _stop_tx, _rx) = test_worker(cfg);
1221 let (mut worker_end, mut server) = tokio::io::duplex(64 * 1024);
1222 worker.startup(&mut worker_end).await.unwrap();
1223 worker_end.shutdown().await.unwrap();
1224 let mut sent = Vec::new();
1225 server.read_to_end(&mut sent).await.unwrap();
1226 let pair = format!("application_name\0{expected}\0");
1227 assert!(
1228 sent.windows(pair.len()).any(|w| w == pair.as_bytes()),
1229 "startup message missing {pair:?}"
1230 );
1231 }
1232 }
1233
1234 #[tokio::test]
1235 async fn startup_omits_options_when_unset() {
1236 use tokio::io::{AsyncReadExt, AsyncWriteExt};
1237
1238 let (worker, _stop_tx, _rx) = test_worker(ReplicationConfig::default());
1239 let (mut worker_end, mut server) = tokio::io::duplex(64 * 1024);
1240
1241 worker.startup(&mut worker_end).await.unwrap();
1242 worker_end.shutdown().await.unwrap();
1243
1244 let mut sent = Vec::new();
1245 server.read_to_end(&mut sent).await.unwrap();
1246 let sent = String::from_utf8_lossy(&sent);
1247
1248 assert!(
1249 !sent.contains("options"),
1250 "startup message should omit 'options' when unset: {sent:?}"
1251 );
1252 }
1253
1254 #[tokio::test]
1258 async fn stream_loop_refuses_a_frame_over_the_configured_limit() {
1259 use tokio::io::AsyncWriteExt;
1260
1261 let cfg = ReplicationConfig {
1262 max_message_size: 64,
1263 ..Default::default()
1264 };
1265
1266 let progress = Arc::new(SharedProgress::new(Lsn(0)));
1267 let (_stop_tx, stop_rx) = watch::channel(false);
1268 let (tx, mut rx) = mpsc::channel(16);
1269 let metrics = Arc::new(ReplicationMetrics::default());
1270 let mut worker = WorkerState::new(cfg, progress, stop_rx, tx, Arc::clone(&metrics));
1271
1272 let (worker_end, mut test_end) = tokio::io::duplex(64 * 1024);
1273 let worker_task = tokio::spawn(async move {
1274 let mut buf = BufReader::with_capacity(1024, worker_end);
1275 worker.stream_loop(&mut buf).await
1276 });
1277
1278 test_end
1281 .write_all(&xlog_frame(1, &[b'x'; 200]))
1282 .await
1283 .unwrap();
1284 test_end.write_all(&xlog_frame(2, b"Ismall")).await.unwrap();
1285 test_end.flush().await.unwrap();
1286
1287 let err = tokio::time::timeout(std::time::Duration::from_secs(5), worker_task)
1288 .await
1289 .expect("stream_loop should end on the oversized frame")
1290 .unwrap()
1291 .expect_err("stream_loop should fail, not return Ok");
1292
1293 assert!(
1294 matches!(
1295 err,
1296 PgWireError::MessageTooLarge {
1297 limit_bytes: 64,
1298 ..
1299 }
1300 ),
1301 "expected MessageTooLarge, got {err:?}"
1302 );
1303 assert!(!err.is_transient());
1304
1305 assert!(
1307 rx.try_recv().is_err(),
1308 "no event should be emitted for a refused frame"
1309 );
1310 assert_eq!(metrics.last_applied_lsn(), Lsn(0));
1312 }
1313
1314 fn frame(tag: u8, payload: &[u8]) -> Vec<u8> {
1316 let mut f = vec![tag];
1317 f.extend_from_slice(&((payload.len() + 4) as i32).to_be_bytes());
1318 f.extend_from_slice(payload);
1319 f
1320 }
1321
1322 fn data_row(cols: &[Option<&[u8]>]) -> Vec<u8> {
1323 let mut p = (cols.len() as i16).to_be_bytes().to_vec();
1324 for c in cols {
1325 match c {
1326 Some(v) => {
1327 p.extend_from_slice(&(v.len() as i32).to_be_bytes());
1328 p.extend_from_slice(v);
1329 }
1330 None => p.extend_from_slice(&(-1i32).to_be_bytes()),
1331 }
1332 }
1333 frame(b'D', &p)
1334 }
1335
1336 async fn read_frontend(server: &mut tokio::io::DuplexStream) -> (u8, Vec<u8>) {
1338 use tokio::io::AsyncReadExt;
1339 let tag = server.read_u8().await.unwrap();
1340 let len = server.read_i32().await.unwrap() as usize;
1341 let mut payload = vec![0; len - 4];
1342 server.read_exact(&mut payload).await.unwrap();
1343 (tag, payload)
1344 }
1345
1346 async fn gated_query(
1347 gate: &mpsc::Sender<GateCommand>,
1348 sql: &str,
1349 ) -> tokio::sync::oneshot::Receiver<Result<Vec<QueryRow>>> {
1350 let (reply, rx) = tokio::sync::oneshot::channel();
1351 gate.send(GateCommand::Query {
1352 sql: sql.into(),
1353 reply,
1354 })
1355 .await
1356 .unwrap();
1357 rx
1358 }
1359
1360 #[tokio::test]
1361 async fn a_gated_session_answers_queries_then_starts() {
1362 use tokio::io::AsyncWriteExt;
1363
1364 let (mut worker_end, mut server) = tokio::io::duplex(64 * 1024);
1365 let (gate_tx, mut gate_rx) = mpsc::channel(1);
1366 let serve = tokio::spawn(async move {
1367 WorkerState::serve_gate(
1368 &mut gate_rx,
1369 &mut worker_end,
1370 crate::protocol::framing::MAX_MESSAGE_SIZE,
1371 )
1372 .await
1373 });
1374
1375 let reply = gated_query(&gate_tx, "IDENTIFY_SYSTEM").await;
1376 let (tag, payload) = read_frontend(&mut server).await;
1377 assert_eq!(tag, b'Q');
1378 assert_eq!(payload, b"IDENTIFY_SYSTEM\0");
1379 let mut answer = frame(b'T', &[0, 0]);
1380 answer.extend(data_row(&[
1381 Some(b"7"),
1382 Some(b"2"),
1383 Some(b"0/16B3748"),
1384 None,
1385 ]));
1386 answer.extend(frame(b'C', b"IDENTIFY_SYSTEM\0"));
1387 answer.extend(frame(b'Z', b"I"));
1388 server.write_all(&answer).await.unwrap();
1389 let rows = reply.await.unwrap().unwrap();
1390 assert_eq!(rows.len(), 1);
1391 assert_eq!(rows[0][0].as_deref(), Some(&b"7"[..]));
1392 assert_eq!(rows[0][3], None, "NULL stays NULL");
1393
1394 gate_tx.send(GateCommand::Start).await.unwrap();
1395 assert!(
1396 matches!(serve.await.unwrap(), Ok(true)),
1397 "Start ends the gate"
1398 );
1399 }
1400
1401 #[tokio::test]
1402 async fn a_server_error_keeps_the_gated_session_usable() {
1403 use tokio::io::AsyncWriteExt;
1404
1405 let (mut worker_end, mut server) = tokio::io::duplex(64 * 1024);
1406 let (gate_tx, mut gate_rx) = mpsc::channel(1);
1407 let serve = tokio::spawn(async move {
1408 WorkerState::serve_gate(
1409 &mut gate_rx,
1410 &mut worker_end,
1411 crate::protocol::framing::MAX_MESSAGE_SIZE,
1412 )
1413 .await
1414 });
1415
1416 let reply = gated_query(&gate_tx, "TIMELINE_HISTORY 1").await;
1417 read_frontend(&mut server).await;
1418 let mut answer = frame(b'E', b"Mno history\0\0");
1419 answer.extend(frame(b'Z', b"I"));
1420 server.write_all(&answer).await.unwrap();
1421 assert!(matches!(reply.await.unwrap(), Err(PgWireError::Server(_))));
1422
1423 let reply = gated_query(&gate_tx, "SELECT 1").await;
1424 read_frontend(&mut server).await;
1425 let mut answer = data_row(&[Some(b"1")]);
1426 answer.extend(frame(b'Z', b"I"));
1427 server.write_all(&answer).await.unwrap();
1428 assert_eq!(reply.await.unwrap().unwrap().len(), 1);
1429
1430 drop(gate_tx);
1431 assert!(matches!(serve.await.unwrap(), Ok(false)));
1432 }
1433
1434 #[tokio::test]
1435 async fn a_closed_gate_terminates_without_starting() {
1436 let (mut worker_end, mut server) = tokio::io::duplex(64 * 1024);
1437 let (gate_tx, mut gate_rx) = mpsc::channel::<GateCommand>(1);
1438 drop(gate_tx);
1439 let started = WorkerState::serve_gate(
1440 &mut gate_rx,
1441 &mut worker_end,
1442 crate::protocol::framing::MAX_MESSAGE_SIZE,
1443 )
1444 .await
1445 .unwrap();
1446 assert!(!started);
1447 assert_eq!(read_frontend(&mut server).await.0, b'X', "Terminate sent");
1448 }
1449
1450 #[tokio::test]
1451 async fn a_broken_session_ends_the_gate_with_its_error() {
1452 let (mut worker_end, server) = tokio::io::duplex(64 * 1024);
1453 let (gate_tx, mut gate_rx) = mpsc::channel(1);
1454 let serve = tokio::spawn(async move {
1455 WorkerState::serve_gate(
1456 &mut gate_rx,
1457 &mut worker_end,
1458 crate::protocol::framing::MAX_MESSAGE_SIZE,
1459 )
1460 .await
1461 });
1462 let reply = gated_query(&gate_tx, "IDENTIFY_SYSTEM").await;
1463 drop(server);
1464 assert!(reply.await.unwrap().is_err());
1465 assert!(serve.await.unwrap().is_err(), "the session ends");
1466 }
1467
1468 #[tokio::test]
1469 async fn a_gated_query_row_over_the_limit_ends_the_session() {
1470 use tokio::io::AsyncWriteExt;
1471
1472 let (mut worker_end, mut server) = tokio::io::duplex(64 * 1024);
1473 let (gate_tx, mut gate_rx) = mpsc::channel(1);
1474 let serve = tokio::spawn(async move {
1475 WorkerState::serve_gate(&mut gate_rx, &mut worker_end, 64).await
1476 });
1477
1478 let reply = gated_query(&gate_tx, "SELECT big").await;
1479 read_frontend(&mut server).await;
1480 let mut answer = data_row(&[Some(&[b'x'; 200])]);
1481 answer.extend(frame(b'Z', b"I"));
1482 server.write_all(&answer).await.unwrap();
1483 assert!(matches!(
1484 reply.await.unwrap(),
1485 Err(PgWireError::MessageTooLarge {
1486 limit_bytes: 64,
1487 ..
1488 })
1489 ));
1490 assert!(
1491 matches!(
1492 serve.await.unwrap(),
1493 Err(PgWireError::MessageTooLarge { .. })
1494 ),
1495 "the session ends"
1496 );
1497 }
1498
1499 #[test]
1500 fn a_malformed_data_row_is_a_protocol_error() {
1501 let truncated = Bytes::from_static(&[0, 1, 0, 0, 0, 5, b'a']);
1502 assert!(matches!(
1503 parse_data_row(&truncated),
1504 Err(PgWireError::Protocol(_))
1505 ));
1506 }
1507
1508 #[tokio::test]
1509 async fn start_replication_returns_ok_on_copy_both_response() {
1510 use std::time::Duration;
1511 use tokio::io::AsyncWriteExt;
1512
1513 let (worker, _stop_tx, _rx) = test_worker(ReplicationConfig::default());
1514 let (mut worker_end, mut server) = tokio::io::duplex(64 * 1024);
1515
1516 server.write_all(&[b'W', 0, 0, 0, 4]).await.unwrap();
1518
1519 let res = tokio::time::timeout(
1520 Duration::from_secs(5),
1521 worker.start_replication(&mut worker_end),
1522 )
1523 .await;
1524 assert!(matches!(res, Ok(Ok(()))), "expected Ok on 'W', got {res:?}");
1525 }
1526
1527 #[tokio::test]
1528 async fn start_replication_surfaces_server_error_response() {
1529 use std::time::Duration;
1530 use tokio::io::AsyncWriteExt;
1531
1532 let (worker, _stop_tx, _rx) = test_worker(ReplicationConfig::default());
1533 let (mut worker_end, mut server) = tokio::io::duplex(64 * 1024);
1534
1535 server.write_all(&[b'E', 0, 0, 0, 5, 0]).await.unwrap();
1537
1538 let res = tokio::time::timeout(
1539 Duration::from_secs(5),
1540 worker.start_replication(&mut worker_end),
1541 )
1542 .await;
1543 assert!(
1544 matches!(res, Ok(Err(PgWireError::Server(_)))),
1545 "expected Server error on 'E', got {res:?}"
1546 );
1547 }
1548
1549 #[tokio::test]
1554 async fn feedback_continues_under_backpressure() {
1555 use std::time::Duration;
1556 use tokio::io::AsyncWriteExt;
1557
1558 let cfg = ReplicationConfig {
1559 status_interval: Duration::from_millis(40),
1560 idle_wakeup_interval: Duration::from_secs(10),
1561 buffer_events: 2,
1562 ..Default::default()
1563 };
1564
1565 let progress = Arc::new(SharedProgress::new(Lsn(0)));
1566 let (stop_tx, stop_rx) = watch::channel(false);
1567 let (tx, _rx_never_drained) = mpsc::channel(cfg.buffer_events);
1569 let metrics = Arc::new(ReplicationMetrics::default());
1570 let mut worker = WorkerState::new(cfg, progress, stop_rx, tx, Arc::clone(&metrics));
1571
1572 let (worker_end, mut test_end) = tokio::io::duplex(64 * 1024);
1573
1574 let worker_task = tokio::spawn(async move {
1575 let mut buf = BufReader::with_capacity(1024, worker_end);
1576 let _ = worker.stream_loop(&mut buf).await;
1577 });
1578
1579 for i in 1..=5u64 {
1582 test_end.write_all(&xlog_frame(i, b"Idummy")).await.unwrap();
1583 }
1584 test_end.flush().await.unwrap();
1585
1586 let mut reader = MessageReader::new();
1590 let mut feedback_count = 0;
1591 let deadline = Instant::now() + Duration::from_millis(500);
1592 while feedback_count < 3 {
1593 let remaining = deadline.saturating_duration_since(Instant::now());
1594 if remaining.is_zero() {
1595 break;
1596 }
1597 match tokio::time::timeout(remaining, reader.read(&mut test_end)).await {
1598 Ok(Ok(msg)) if msg.tag == b'd' && msg.payload.first() == Some(&b'r') => {
1599 feedback_count += 1;
1600 }
1601 Ok(Ok(_)) => continue,
1602 _ => break,
1603 }
1604 }
1605
1606 assert!(
1607 feedback_count >= 3,
1608 "expected repeated feedback under backpressure, got {feedback_count}"
1609 );
1610 assert!(
1611 metrics.stall_count() >= 1,
1612 "expected at least one stall to be recorded"
1613 );
1614 assert!(
1615 metrics.feedback_sent() >= 3,
1616 "feedback_sent metric should track sends, got {}",
1617 metrics.feedback_sent()
1618 );
1619
1620 let _ = stop_tx.send(true);
1621 let _ = worker_task.await;
1622 }
1623
1624 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1628 async fn feedback_under_backpressure_is_rate_limited() {
1629 use std::time::Duration;
1630 use tokio::io::AsyncWriteExt;
1631
1632 let interval = Duration::from_millis(50);
1633 let cfg = ReplicationConfig {
1634 status_interval: interval,
1635 idle_wakeup_interval: Duration::from_secs(10),
1636 buffer_events: 2,
1637 ..Default::default()
1638 };
1639
1640 let progress = Arc::new(SharedProgress::new(Lsn(0)));
1641 let (stop_tx, stop_rx) = watch::channel(false);
1642 let (tx, _rx_never_drained) = mpsc::channel(cfg.buffer_events);
1643 let metrics = Arc::new(ReplicationMetrics::default());
1644 let mut worker = WorkerState::new(cfg, progress, stop_rx, tx, Arc::clone(&metrics));
1645
1646 let (worker_end, mut test_end) = tokio::io::duplex(64 * 1024);
1647 let worker_task = tokio::spawn(async move {
1648 let mut buf = BufReader::with_capacity(1024, worker_end);
1649 let _ = worker.stream_loop(&mut buf).await;
1650 });
1651
1652 for i in 1..=5u64 {
1653 test_end.write_all(&xlog_frame(i, b"Idummy")).await.unwrap();
1654 }
1655 test_end.flush().await.unwrap();
1656
1657 let window = Duration::from_millis(350);
1662 let end = Instant::now() + window;
1663 let mut reader = MessageReader::new();
1664 let mut feedback = 0usize;
1665 loop {
1666 let remaining = end.saturating_duration_since(Instant::now());
1667 if remaining.is_zero() {
1668 break;
1669 }
1670 match tokio::time::timeout(remaining, reader.read(&mut test_end)).await {
1671 Ok(Ok(msg)) if msg.tag == b'd' && msg.payload.first() == Some(&b'r') => {
1672 feedback += 1;
1673 }
1674 Ok(Ok(_)) => {}
1675 _ => break,
1676 }
1677 }
1678
1679 assert!(
1680 feedback >= 2,
1681 "feedback should keep flowing under backpressure, got {feedback}"
1682 );
1683 assert!(
1684 feedback <= 25,
1685 "feedback must be rate-limited to ~status_interval, got {feedback}"
1686 );
1687
1688 let _ = stop_tx.send(true);
1689 let _ = worker_task.await;
1690 }
1691
1692 fn keepalive_frame(wal_end: u64, reply_requested: bool) -> Vec<u8> {
1696 let mut payload = vec![b'k'];
1697 payload.extend_from_slice(&(wal_end as i64).to_be_bytes());
1698 payload.extend_from_slice(&0i64.to_be_bytes());
1699 payload.push(u8::from(reply_requested));
1700 let mut frame = vec![b'd'];
1701 frame.extend_from_slice(&((payload.len() + 4) as i32).to_be_bytes());
1702 frame.extend_from_slice(&payload);
1703 frame
1704 }
1705
1706 #[derive(Debug, PartialEq)]
1708 struct Status {
1709 applied: u64,
1710 reply: bool,
1711 }
1712
1713 struct Server {
1715 end: tokio::io::DuplexStream,
1716 reader: MessageReader,
1717 }
1718
1719 impl Server {
1720 async fn status(&mut self) -> Status {
1722 loop {
1723 let msg = self.reader.read(&mut self.end).await.expect("stream open");
1724 if msg.tag == b'd' && msg.payload.first() == Some(&b'r') {
1725 let p = &msg.payload;
1726 return Status {
1727 applied: u64::from_be_bytes(p[1..9].try_into().unwrap()),
1728 reply: p[33] == 1,
1729 };
1730 }
1731 }
1732 }
1733
1734 async fn quiet(&mut self, within: std::time::Duration) -> bool {
1736 tokio::time::timeout(within, self.status()).await.is_err()
1737 }
1738
1739 async fn send(&mut self, frame: &[u8]) {
1740 use tokio::io::AsyncWriteExt;
1741 self.end.write_all(frame).await.unwrap();
1742 self.end.flush().await.unwrap();
1743 }
1744 }
1745
1746 struct Running {
1747 started: Instant,
1749 server: Server,
1750 task: tokio::task::JoinHandle<Result<()>>,
1751 stop: watch::Sender<bool>,
1752 _events: mpsc::Receiver<std::result::Result<ReplicationEvent, PgWireError>>,
1753 }
1754
1755 fn feedback_cfg() -> ReplicationConfig {
1756 ReplicationConfig {
1757 status_interval: std::time::Duration::from_secs(1),
1758 idle_wakeup_interval: std::time::Duration::from_secs(10),
1759 ..Default::default()
1760 }
1761 }
1762
1763 fn run_stream(progress: &Arc<SharedProgress>, capacity: usize) -> Running {
1766 run_stream_with(feedback_cfg(), progress, capacity)
1767 }
1768
1769 fn run_stream_with(
1770 cfg: ReplicationConfig,
1771 progress: &Arc<SharedProgress>,
1772 capacity: usize,
1773 ) -> Running {
1774 let (stop, stop_rx) = watch::channel(false);
1775 let (tx, events) = mpsc::channel(16);
1776 let mut worker = WorkerState::new(
1777 cfg,
1778 Arc::clone(progress),
1779 stop_rx,
1780 tx,
1781 Arc::new(ReplicationMetrics::default()),
1782 );
1783 let (worker_end, end) = tokio::io::duplex(capacity);
1784 let task = tokio::spawn(async move {
1785 let mut buf = BufReader::with_capacity(1024, worker_end);
1786 worker.stream_loop(&mut buf).await
1787 });
1788 Running {
1789 started: Instant::now(),
1790 server: Server {
1791 end,
1792 reader: MessageReader::new(),
1793 },
1794 task,
1795 stop,
1796 _events: events,
1797 }
1798 }
1799
1800 const SECOND: std::time::Duration = std::time::Duration::from_secs(1);
1801
1802 #[tokio::test(start_paused = true)]
1803 async fn an_update_is_sent_within_the_status_interval() {
1804 let progress = Arc::new(SharedProgress::new(Lsn(0)));
1805 let mut r = run_stream(&progress, 64 * 1024);
1806 assert_eq!(
1807 r.server.status().await,
1808 Status {
1809 applied: 0,
1810 reply: false
1811 }
1812 );
1813 assert!(r.server.quiet(3 * SECOND).await);
1815
1816 let t = Instant::now();
1818 progress.update_applied(Lsn(100));
1819 assert_eq!(
1820 r.server.status().await,
1821 Status {
1822 applied: 100,
1823 reply: false
1824 }
1825 );
1826 assert!(t.elapsed() < SECOND);
1827
1828 progress.update_applied(Lsn(200));
1830 let t = Instant::now();
1831 assert_eq!(
1832 r.server.status().await,
1833 Status {
1834 applied: 200,
1835 reply: false
1836 }
1837 );
1838 assert!(t.elapsed() <= SECOND, "took {:?}", t.elapsed());
1839 r.task.abort();
1840 }
1841
1842 #[tokio::test(start_paused = true)]
1843 async fn coalesced_updates_send_the_newest_once() {
1844 let progress = Arc::new(SharedProgress::new(Lsn(0)));
1845 let mut r = run_stream(&progress, 64 * 1024);
1846 assert_eq!(r.server.status().await.applied, 0);
1847 let t = Instant::now();
1848 for lsn in 1..=100 {
1849 progress.update_applied(Lsn(lsn));
1850 }
1851 assert_eq!(
1852 r.server.status().await,
1853 Status {
1854 applied: 100,
1855 reply: false
1856 }
1857 );
1858 assert!(t.elapsed() <= SECOND);
1859 assert!(r.server.quiet(8 * SECOND).await);
1861 r.task.abort();
1862 }
1863
1864 #[tokio::test(start_paused = true)]
1865 async fn an_update_during_a_send_stays_pending() {
1866 let progress = Arc::new(SharedProgress::new(Lsn(0)));
1867 let mut r = run_stream(&progress, 8);
1869 for _ in 0..10 {
1870 tokio::task::yield_now().await;
1871 }
1872 progress.update_applied(Lsn(300));
1873 assert_eq!(r.server.status().await.applied, 0);
1875 let t = Instant::now();
1877 assert_eq!(r.server.status().await.applied, 300);
1878 assert!(t.elapsed() <= SECOND);
1879 r.task.abort();
1880 }
1881
1882 #[tokio::test(start_paused = true)]
1883 async fn a_new_session_starts_with_the_newest_position() {
1884 let progress = Arc::new(SharedProgress::new(Lsn(0)));
1885 progress.update_applied(Lsn(500));
1886 let mut r = run_stream(&progress, 64 * 1024);
1887 assert_eq!(r.server.status().await.applied, 500);
1888 r.task.abort();
1889 }
1890
1891 #[tokio::test(start_paused = true)]
1892 async fn a_failed_send_leaves_the_position_for_the_next_session() {
1893 let progress = Arc::new(SharedProgress::new(Lsn(0)));
1894 let mut r = run_stream(&progress, 64 * 1024);
1895 assert_eq!(r.server.status().await.applied, 0);
1896 progress.update_applied(Lsn(700));
1897 drop(r.server);
1899 let ended = tokio::time::timeout(2 * SECOND, r.task).await.unwrap();
1900 assert!(ended.unwrap().is_err(), "the failed send ends the session");
1901 let mut next = run_stream(&progress, 64 * 1024);
1902 assert_eq!(next.server.status().await.applied, 700);
1903 next.task.abort();
1904 }
1905
1906 #[tokio::test(start_paused = true)]
1907 async fn a_keepalive_requesting_a_reply_is_answered_at_once() {
1908 let progress = Arc::new(SharedProgress::new(Lsn(40)));
1909 let mut r = run_stream(&progress, 64 * 1024);
1910 assert_eq!(r.server.status().await.applied, 40);
1911 let t = Instant::now();
1913 r.server.send(&keepalive_frame(90, true)).await;
1914 assert_eq!(
1915 r.server.status().await,
1916 Status {
1917 applied: 40,
1918 reply: true
1919 }
1920 );
1921 assert!(t.elapsed() < SECOND);
1922 r.task.abort();
1923 }
1924
1925 #[tokio::test(start_paused = true)]
1926 async fn a_completely_idle_connection_keeps_sending_status() {
1927 let progress = Arc::new(SharedProgress::new(Lsn(10)));
1928 let mut r = run_stream(&progress, 64 * 1024);
1929 let t = Instant::now();
1930 let mut statuses = 0;
1931 while t.elapsed() < std::time::Duration::from_secs(65) {
1932 assert_eq!(r.server.status().await.applied, 10);
1933 statuses += 1;
1934 }
1935 assert!((6..=8).contains(&statuses), "{statuses} statuses in 65 s");
1938 assert!(!r.task.is_finished());
1939 r.task.abort();
1940 }
1941
1942 #[tokio::test(start_paused = true)]
1943 async fn stopping_with_the_timer_armed_ends_at_once() {
1944 let progress = Arc::new(SharedProgress::new(Lsn(0)));
1945 let mut r = run_stream(&progress, 64 * 1024);
1946 assert_eq!(r.server.status().await.applied, 0);
1947 progress.update_applied(Lsn(900)); let t = Instant::now();
1949 r.stop.send(true).unwrap();
1950 let ended = tokio::time::timeout(SECOND, r.task)
1951 .await
1952 .expect("the worker ends without waiting for the timer");
1953 assert!(ended.unwrap().is_ok());
1954 assert!(t.elapsed() < SECOND);
1955 }
1956
1957 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1961 async fn racing_raises_never_strand_the_newest_position() {
1962 use std::time::Duration;
1963 let interval = Duration::from_millis(50);
1964 let cfg = ReplicationConfig {
1965 status_interval: interval,
1966 idle_wakeup_interval: Duration::from_secs(60),
1967 ..Default::default()
1968 };
1969 let progress = Arc::new(SharedProgress::new(Lsn(0)));
1970 let mut r = run_stream_with(cfg, &progress, 64 * 1024);
1971 let mut newest = 0u64;
1972 for burst in 0..50u64 {
1973 let p = Arc::clone(&progress);
1974 let from = newest;
1975 let len = 1 + (burst * 7919) % 5000;
1976 newest = std::thread::spawn(move || {
1977 for lsn in from + 1..=from + len {
1978 p.update_applied(Lsn(lsn));
1979 }
1980 from + len
1981 })
1982 .join()
1983 .unwrap();
1984 let ended = Instant::now();
1985 loop {
1986 let status = tokio::time::timeout(4 * interval, r.server.status())
1987 .await
1988 .unwrap_or_else(|_| {
1989 panic!("burst {burst}: {newest} not sent within {:?}", 4 * interval)
1990 });
1991 if status.applied == newest {
1992 break;
1993 }
1994 assert!(status.applied < newest);
1995 }
1996 assert!(ended.elapsed() <= 4 * interval);
1997 }
1998 r.task.abort();
1999 }
2000
2001 type Seen = Arc<std::sync::Mutex<Vec<std::time::Duration>>>;
2004
2005 fn observed_cfg() -> (ReplicationConfig, Seen) {
2006 let seen: Seen = Arc::default();
2007 let sink = Arc::clone(&seen);
2008 let cfg = feedback_cfg().with_feedback_observer(crate::FeedbackObserver::new(move |s| {
2009 sink.lock().unwrap().push(s.pending_for)
2010 }));
2011 (cfg, seen)
2012 }
2013
2014 fn seen(s: &Seen) -> Vec<std::time::Duration> {
2015 s.lock().unwrap().clone()
2016 }
2017
2018 #[tokio::test(start_paused = true)]
2019 async fn the_observer_reports_how_long_a_position_waited() {
2020 let (cfg, s) = observed_cfg();
2021 let progress = Arc::new(SharedProgress::with_timing(Lsn(0), true));
2022 let mut r = run_stream_with(cfg, &progress, 64 * 1024);
2023 assert_eq!(r.server.status().await.applied, 0);
2024 assert!(
2025 seen(&s).is_empty(),
2026 "nothing was pending at the first status"
2027 );
2028 assert!(r.server.quiet(3 * SECOND).await);
2029
2030 progress.update_applied(Lsn(100));
2032 assert_eq!(r.server.status().await.applied, 100);
2033 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
2035 progress.update_applied(Lsn(200));
2036 assert_eq!(r.server.status().await.applied, 200);
2037 assert_eq!(
2038 seen(&s),
2039 vec![
2040 std::time::Duration::ZERO,
2041 std::time::Duration::from_millis(900)
2042 ]
2043 );
2044 r.task.abort();
2045 }
2046
2047 #[tokio::test(start_paused = true)]
2048 async fn coalesced_raises_report_the_oldest_wait() {
2049 let (cfg, s) = observed_cfg();
2050 let progress = Arc::new(SharedProgress::with_timing(Lsn(0), true));
2051 let mut r = run_stream_with(cfg, &progress, 64 * 1024);
2052 assert_eq!(r.server.status().await.applied, 0);
2053 for (at_ms, lsn) in [(100, 1), (500, 2), (900, 3)] {
2054 tokio::time::sleep_until(r.started + std::time::Duration::from_millis(at_ms)).await;
2055 progress.update_applied(Lsn(lsn));
2056 }
2057 assert_eq!(r.server.status().await.applied, 3);
2058 assert_eq!(seen(&s), vec![std::time::Duration::from_millis(900)]);
2059 r.task.abort();
2060 }
2061
2062 #[tokio::test(start_paused = true)]
2063 async fn no_observation_when_the_position_does_not_advance() {
2064 let (cfg, s) = observed_cfg();
2065 let progress = Arc::new(SharedProgress::with_timing(Lsn(40), true));
2066 let mut r = run_stream_with(cfg, &progress, 64 * 1024);
2067 assert_eq!(r.server.status().await.applied, 40);
2068 r.server.send(&keepalive_frame(90, true)).await;
2069 assert!(r.server.status().await.reply);
2070 progress.update_applied(Lsn(40)); for _ in 0..3 {
2072 r.server.status().await; }
2074 assert!(seen(&s).is_empty());
2075 assert_eq!(progress.pending_since(), None);
2076 r.task.abort();
2077 }
2078
2079 #[tokio::test(start_paused = true)]
2080 async fn a_failed_write_carries_its_wait_into_the_next_session() {
2081 let (cfg, s) = observed_cfg();
2082 let progress = Arc::new(SharedProgress::with_timing(Lsn(0), true));
2083 let mut r = run_stream_with(cfg.clone(), &progress, 64 * 1024);
2084 assert_eq!(r.server.status().await.applied, 0);
2085 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
2086 progress.update_applied(Lsn(700));
2087 drop(r.server);
2088 assert!(r.task.await.unwrap().is_err(), "the write fails");
2089 let since = progress.pending_since().expect("still pending");
2090
2091 tokio::time::sleep(2 * SECOND).await;
2094 let next = Arc::new(SharedProgress::with_timing(Lsn(700), true));
2095 next.carry_pending(since);
2096 let mut r = run_stream_with(cfg, &next, 64 * 1024);
2097 assert_eq!(r.server.status().await.applied, 700);
2098 let waited = seen(&s);
2099 assert_eq!(waited.len(), 1);
2100 assert!(waited[0] >= 2 * SECOND, "{waited:?}");
2101 r.task.abort();
2102 }
2103
2104 #[tokio::test(start_paused = true)]
2105 async fn nothing_is_timed_without_an_observer() {
2106 let progress = Arc::new(SharedProgress::with_timing(Lsn(0), false));
2107 progress.update_applied(Lsn(5));
2108 assert_eq!(progress.pending_since(), None);
2109 }
2110
2111 #[tokio::test(start_paused = true)]
2112 async fn a_raise_during_a_write_keeps_a_pending_start() {
2113 let (cfg, s) = observed_cfg();
2114 let progress = Arc::new(SharedProgress::with_timing(Lsn(0), true));
2115 progress.update_applied(Lsn(1));
2116 let mut r = run_stream_with(cfg, &progress, 8);
2118 for _ in 0..10 {
2119 tokio::task::yield_now().await;
2120 }
2121 progress.update_applied(Lsn(2));
2122 assert_eq!(r.server.status().await.applied, 1);
2123 assert_eq!(r.server.status().await.applied, 2);
2124 assert_eq!(seen(&s).len(), 2, "both waits are reported");
2125 r.task.abort();
2126 }
2127
2128 #[tokio::test(start_paused = true)]
2132 async fn a_repeated_position_does_not_report_a_pending_raise() {
2133 let (cfg, s) = observed_cfg();
2134 let progress = Arc::new(SharedProgress::with_timing(Lsn(40), true));
2135 let mut r = run_stream_with(cfg, &progress, 8);
2137 assert_eq!(r.server.status().await.applied, 40);
2138 tokio::time::sleep_until(r.started + std::time::Duration::from_millis(10_500)).await;
2141 progress.update_applied(Lsn(50));
2142 assert_eq!(r.server.status().await.applied, 40);
2143 assert!(
2144 seen(&s).is_empty(),
2145 "the repeated position reported nothing"
2146 );
2147 assert_eq!(r.server.status().await.applied, 50);
2148 assert_eq!(seen(&s).len(), 1);
2149 r.task.abort();
2150 }
2151}