1#![cfg_attr(
31 not(any(
32 feature = "sync-sender-tcp",
33 feature = "sync-sender-http",
34 feature = "sync-sender-qwp-udp"
35 )),
36 allow(unreachable_patterns)
37)]
38
39use crate::error::{self, Result};
40#[cfg(feature = "sync-sender-qwp-ws")]
41use crate::ingress::AckLevel;
42#[cfg(feature = "_sync-sender")]
43use crate::ingress::SenderBuilder;
44use crate::ingress::{Buffer, Protocol, ProtocolVersion};
45use std::fmt::{Debug, Formatter};
46#[cfg(feature = "sync-sender-qwp-ws")]
47use std::sync::atomic::{AtomicUsize, Ordering};
48#[cfg(feature = "sync-sender-qwp-ws")]
49use std::time::{Duration, Instant};
50
51#[cfg(feature = "sync-sender-qwp-udp")]
52mod qwp_udp;
53
54#[cfg(feature = "sync-sender-qwp-udp")]
55pub(crate) use qwp_udp::*;
56
57#[cfg(feature = "_sender-qwp-ws")]
58mod qwp_ws_codec;
59
60#[cfg(feature = "_sender-qwp-ws")]
61mod qwp_ws_driver;
62
63#[cfg(feature = "sync-sender-qwp-ws")]
64pub(crate) use qwp_ws_driver::{
65 ReconnectPolicy, ReconnectReason, reconnect_backoff_step, reconnect_error_is_terminal,
66};
67
68#[cfg(feature = "_sender-qwp-ws")]
69mod qwp_ws_ownership;
70
71#[cfg(feature = "_sender-qwp-ws")]
72mod qwp_ws_orphan;
73#[cfg(all(test, feature = "_sender-qwp-ws"))]
74pub(crate) use qwp_ws_orphan::has_any_sfa_file;
75#[cfg(feature = "_sender-qwp-ws")]
76pub(crate) use qwp_ws_orphan::is_candidate_orphan;
77
78#[cfg(feature = "_sender-qwp-ws")]
79mod qwp_ws_publisher;
80
81#[cfg(feature = "_sender-qwp-ws")]
82mod qwp_ws_queue;
83
84#[cfg(feature = "_sender-qwp-ws")]
85mod qwp_ws_sfa_segment;
86
87#[cfg(feature = "_sender-qwp-ws")]
88pub(crate) mod qwp_ws_sfa_manifest;
89
90#[cfg(feature = "_sender-qwp-ws")]
91mod qwp_ws_sfa_queue;
92
93#[cfg(feature = "_sender-qwp-ws")]
94mod qwp_ws_sfa_slot;
95
96#[cfg(feature = "_sender-qwp-ws")]
97pub(crate) mod qwp_ws_sfa_publisher;
98
99#[cfg(feature = "_sender-qwp-ws")]
100pub(crate) mod qwp_ws_sfa_symbol_dict;
101
102#[cfg(feature = "_sender-qwp-ws")]
103mod qwp_ws_sfa_catchup;
104
105#[cfg(all(test, feature = "_sender-qwp-ws"))]
106pub(crate) use qwp_ws_sfa_catchup::fail_next_catch_up_allocation_for_test;
107
108#[cfg(feature = "_sender-qwp-ws")]
109pub(crate) use qwp_ws_ownership::QwpWsRoleReject;
110#[cfg(feature = "_sender-qwp-ws")]
111pub use qwp_ws_ownership::*;
112
113#[cfg(feature = "sync-sender-qwp-ws")]
114pub(crate) mod qwp_ws;
115
116#[cfg(feature = "sync-sender-qwp-ws")]
117pub(crate) use qwp_ws::*;
118
119#[cfg(feature = "sync-sender-tcp")]
120mod tcp;
121
122#[cfg(feature = "sync-sender-tcp")]
123pub(crate) use tcp::*;
124
125#[cfg(feature = "sync-sender-tcp")]
126use std::io::Write;
127
128#[cfg(feature = "sync-sender-tcp")]
129use crate::ingress::map_io_to_socket_err;
130
131#[cfg(feature = "sync-sender-http")]
132mod http;
133
134#[cfg(feature = "sync-sender-http")]
135pub(crate) use http::*;
136
137#[cfg(feature = "sync-sender-qwp-ws")]
138fn effective_qwp_ws_max_buf_size(configured: usize, server_max: &AtomicUsize) -> usize {
139 let server = server_max.load(Ordering::Relaxed);
140 if server > 0 {
141 configured.min(server)
142 } else {
143 configured
144 }
145}
146
147#[allow(clippy::enum_variant_names)]
148pub(crate) enum SyncProtocolHandler {
149 #[cfg(feature = "sync-sender-qwp-udp")]
150 SyncQwpUdp(SyncQwpUdpHandlerState),
151
152 #[cfg(feature = "sync-sender-qwp-ws")]
153 SyncQwpWs(Box<SyncQwpWsHandlerState>),
154
155 #[cfg(feature = "sync-sender-qwp-ws")]
156 ManualQwpWs(Box<ManualQwpWsHandlerState>),
157
158 #[cfg(feature = "sync-sender-tcp")]
159 SyncTcp(SyncConnection),
160
161 #[cfg(feature = "sync-sender-http")]
162 SyncHttp(SyncHttpHandlerState),
163}
164
165pub struct Sender {
172 descr: String,
173 handler: SyncProtocolHandler,
174 connected: bool,
175 init_buf_size: usize,
176 max_buf_size: usize,
177 protocol: Protocol,
178 protocol_version: ProtocolVersion,
179 max_name_len: usize,
180 #[cfg(feature = "_sender-qwp-ws")]
181 qwp_ws_error_handler: QwpWsErrorHandler,
182 #[cfg(feature = "_sender-qwp-ws")]
183 conn_events: Option<std::sync::Arc<crate::ingress::conn_events::ConnectionEventSource>>,
184}
185
186impl Debug for Sender {
187 fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
188 f.write_str(self.descr.as_str())
189 }
190}
191
192impl Sender {
193 #[cfg(feature = "_sender-qwp-ws")]
196 pub fn connection_events_dropped(&self) -> u64 {
197 self.conn_events
198 .as_deref()
199 .map(|events| events.dropped())
200 .unwrap_or(0)
201 }
202
203 #[cfg(feature = "_sender-qwp-ws")]
206 pub fn connection_events_delivered(&self) -> u64 {
207 self.conn_events
208 .as_deref()
209 .map(|events| events.delivered())
210 .unwrap_or(0)
211 }
212
213 #[allow(clippy::too_many_arguments)]
214 pub(crate) fn new(
215 descr: String,
216 handler: SyncProtocolHandler,
217 init_buf_size: usize,
218 max_buf_size: usize,
219 protocol: Protocol,
220 protocol_version: ProtocolVersion,
221 max_name_len: usize,
222 #[cfg(feature = "_sender-qwp-ws")] qwp_ws_error_handler: QwpWsErrorHandler,
223 #[cfg(feature = "_sender-qwp-ws")] conn_events: Option<
224 std::sync::Arc<crate::ingress::conn_events::ConnectionEventSource>,
225 >,
226 ) -> Self {
227 Self {
228 descr,
229 handler,
230 connected: true,
231 init_buf_size,
232 max_buf_size,
233 protocol,
234 protocol_version,
235 max_name_len,
236 #[cfg(feature = "_sender-qwp-ws")]
237 qwp_ws_error_handler,
238 #[cfg(feature = "_sender-qwp-ws")]
239 conn_events,
240 }
241 }
242
243 #[cfg(feature = "_sync-sender")]
267 pub fn from_conf<T: AsRef<str>>(conf: T) -> Result<Self> {
268 SenderBuilder::from_conf(conf)?.build()
269 }
270
271 #[cfg(feature = "_sync-sender")]
280 pub fn from_env() -> Result<Self> {
281 SenderBuilder::from_env()?.build()
282 }
283
284 pub fn new_buffer(&self) -> Buffer {
286 #[cfg(feature = "sync-sender-qwp-udp")]
287 if matches!(&self.handler, SyncProtocolHandler::SyncQwpUdp(_)) {
288 return Buffer::qwp_with_max_name_len(self.max_name_len);
289 }
290
291 #[cfg(feature = "sync-sender-qwp-ws")]
292 if matches!(
293 &self.handler,
294 SyncProtocolHandler::SyncQwpWs(_) | SyncProtocolHandler::ManualQwpWs(_)
295 ) {
296 return Buffer::qwp_ws_with_max_name_len(self.max_name_len);
297 }
298
299 Buffer::with_init_capacity_and_max_name_len(
300 self.protocol_version,
301 self.init_buf_size,
302 self.max_name_len,
303 )
304 }
305
306 #[cfg(feature = "sync-sender-qwp-ws")]
307 fn drain_qwp_ws_error_notifications(&mut self) -> Result<()> {
308 loop {
309 let error = match &mut self.handler {
310 SyncProtocolHandler::SyncQwpWs(state) => {
311 qwp_ws_poll_sender_error_notification_background(state)?
312 }
313 SyncProtocolHandler::ManualQwpWs(state) => {
314 qwp_ws_poll_sender_error_notification_manual(state)?
315 }
316 _ => return Ok(()),
317 };
318 let Some(error) = error else {
319 return Ok(());
320 };
321 self.qwp_ws_error_handler.handle(&error);
322 }
323 }
324
325 #[cfg(feature = "sync-sender-qwp-ws")]
326 fn flush_qwp_ws_buffer(&mut self, buf: &Buffer, transactional: bool) -> Result<Option<u64>> {
327 if !matches!(
328 &self.handler,
329 SyncProtocolHandler::SyncQwpWs(_) | SyncProtocolHandler::ManualQwpWs(_)
330 ) {
331 return Err(error::fmt!(
332 InvalidApiCall,
333 "QWP/WebSocket FSN methods are only supported for QWP/WebSocket senders."
334 ));
335 }
336 match &self.handler {
337 SyncProtocolHandler::SyncQwpWs(state) => {
338 if let Err(err) = qwp_ws_check_error_background(state) {
339 let _ = self.drain_qwp_ws_error_notifications();
340 return Err(err);
341 }
342 }
343 SyncProtocolHandler::ManualQwpWs(state) => {
344 if let Err(err) = qwp_ws_check_error_manual(state) {
345 let _ = self.drain_qwp_ws_error_notifications();
346 return Err(err);
347 }
348 }
349 _ => unreachable!("QWP/WebSocket handler was checked above"),
350 }
351 self.drain_qwp_ws_error_notifications()?;
352
353 let qwp = buf.as_qwp_ws().ok_or_else(|| {
354 error::fmt!(
355 InvalidApiCall,
356 "QWP/WebSocket sender requires a QWP/WebSocket buffer created by `Sender::new_buffer()`."
357 )
358 })?;
359 qwp.check_can_flush()?;
360 if qwp.is_empty() {
361 return Ok(None);
362 }
363 if transactional {
364 return Err(error::fmt!(
365 InvalidApiCall,
366 "Transactional flushes are not supported for QWP/WebSocket."
367 ));
368 }
369
370 let result = match &mut self.handler {
371 SyncProtocolHandler::SyncQwpWs(state) => {
372 let max =
373 effective_qwp_ws_max_buf_size(self.max_buf_size, &state.server_max_batch_size);
374 flush_qwp_ws(state, qwp, max)
375 }
376 SyncProtocolHandler::ManualQwpWs(state) => {
377 let max =
378 effective_qwp_ws_max_buf_size(self.max_buf_size, &state.server_max_batch_size);
379 flush_qwp_ws_manual(state, qwp, max)
380 }
381 _ => unreachable!("QWP/WebSocket handler was checked above"),
382 };
383 if result
384 .as_ref()
385 .is_err_and(|err| matches!(err.code(), crate::ErrorCode::SocketError))
386 {
387 self.connected = false;
388 }
389 result
390 }
391
392 #[allow(unused_variables)]
393 fn flush_impl(&mut self, buf: &Buffer, transactional: bool) -> Result<()> {
394 #[cfg(feature = "sync-sender-qwp-udp")]
395 #[allow(irrefutable_let_patterns)]
396 if let SyncProtocolHandler::SyncQwpUdp(ref mut state) = self.handler {
397 let qwp = buf.as_qwp().ok_or_else(|| {
398 error::fmt!(
399 InvalidApiCall,
400 "QWP/UDP sender requires a QWP buffer created by `Sender::new_buffer()`."
401 )
402 })?;
403 qwp.check_can_flush()?;
404 if qwp.is_empty() {
405 return Ok(());
406 }
407 if qwp.len() > self.max_buf_size {
408 return Err(error::fmt!(
409 InvalidApiCall,
410 "Could not flush buffer: QWP buffer size hint of {} exceeds maximum configured allowed size of {} bytes.",
411 qwp.len(),
412 self.max_buf_size
413 ));
414 }
415 if transactional {
416 return Err(error::fmt!(
417 InvalidApiCall,
418 "Transactional flushes are not supported for QWP/UDP."
419 ));
420 }
421 return flush_qwp_udp(state, qwp);
422 }
423
424 #[cfg(feature = "sync-sender-qwp-ws")]
425 if matches!(
426 &self.handler,
427 SyncProtocolHandler::SyncQwpWs(_) | SyncProtocolHandler::ManualQwpWs(_)
428 ) {
429 return self.flush_qwp_ws_buffer(buf, transactional).map(|_| ());
430 }
431
432 if !self.connected {
433 return Err(error::fmt!(
434 SocketError,
435 "Could not flush buffer: not connected to database."
436 ));
437 }
438 let ilp = buf.as_ilp().ok_or_else(|| {
439 error::fmt!(
440 InvalidApiCall,
441 "ILP sender requires an ILP buffer. QWP buffers must be flushed with a QWP/UDP sender."
442 )
443 })?;
444 ilp.check_can_flush()?;
445
446 let bytes = ilp.as_bytes();
447 if bytes.is_empty() {
448 return Ok(());
449 }
450
451 if ilp.len() > self.max_buf_size {
452 return Err(error::fmt!(
453 InvalidApiCall,
454 "Could not flush buffer: Buffer size of {} exceeds maximum configured allowed size of {} bytes.",
455 ilp.len(),
456 self.max_buf_size
457 ));
458 }
459
460 self.check_protocol_version(ilp.protocol_version())?;
461 match self.handler {
462 #[cfg(feature = "sync-sender-tcp")]
463 SyncProtocolHandler::SyncTcp(ref mut conn) => {
464 if transactional {
465 return Err(error::fmt!(
466 InvalidApiCall,
467 "Transactional flushes are not supported for ILP over TCP."
468 ));
469 }
470 conn.write_all(bytes).map_err(|io_err| {
471 self.connected = false;
472 map_io_to_socket_err("Could not flush buffer: ", io_err)
473 })?;
474 conn.flush().map_err(|io_err| {
475 self.connected = false;
476 map_io_to_socket_err("Could not flush to network: ", io_err)
477 })?;
478 Ok(())
479 }
480 #[cfg(feature = "sync-sender-http")]
481 SyncProtocolHandler::SyncHttp(ref state) => {
482 if transactional && !ilp.transactional() {
483 return Err(error::fmt!(
484 InvalidApiCall,
485 "Buffer contains lines for multiple tables. \
486 Transactional flushes are only supported for buffers containing lines for a single table."
487 ));
488 }
489 let request_min_throughput = *state.config.request_min_throughput;
490 let extra_time = if request_min_throughput > 0 {
491 (bytes.len() as f64) / (request_min_throughput as f64)
492 } else {
493 0.0f64
494 };
495
496 match http_send_with_retries(
497 state,
498 bytes,
499 *state.config.request_timeout + std::time::Duration::from_secs_f64(extra_time),
500 *state.config.retry_timeout,
501 *state.config.retry_max_backoff,
502 ) {
503 Ok(res) => {
504 if res.status().is_client_error() || res.status().is_server_error() {
505 Err(parse_http_error(res.status().as_u16(), res))
506 } else {
507 res.into_body();
508 Ok(())
509 }
510 }
511 Err(err) => Err(crate::error::Error::from_ureq_error(err, &state.url)),
512 }
513 }
514 #[cfg(feature = "sync-sender-qwp-udp")]
515 SyncProtocolHandler::SyncQwpUdp(_) => Err(error::fmt!(
516 InvalidApiCall,
517 "internal error: QWP/UDP handler in ILP flush path"
518 )),
519 #[cfg(feature = "sync-sender-qwp-ws")]
520 SyncProtocolHandler::SyncQwpWs(_) => Err(error::fmt!(
521 InvalidApiCall,
522 "internal error: QWP/WebSocket handler in ILP flush path"
523 )),
524 #[cfg(feature = "sync-sender-qwp-ws")]
525 SyncProtocolHandler::ManualQwpWs(_) => Err(error::fmt!(
526 InvalidApiCall,
527 "internal error: manual QWP/WebSocket handler in ILP flush path"
528 )),
529 }
530 }
531
532 #[cfg(feature = "sync-sender-http")]
550 pub fn flush_and_keep_with_flags(&mut self, buf: &Buffer, transactional: bool) -> Result<()> {
551 self.flush_impl(buf, transactional)
552 }
553
554 pub fn flush_and_keep(&mut self, buf: &Buffer) -> Result<()> {
560 self.flush_impl(buf, false)
561 }
562
563 pub fn flush(&mut self, buf: &mut Buffer) -> crate::Result<()> {
601 self.flush_impl(buf, false)?;
602 buf.clear();
603 Ok(())
604 }
605
606 #[cfg(feature = "sync-sender-qwp-ws")]
619 pub fn flush_and_get_fsn(&mut self, buf: &mut Buffer) -> Result<Option<u64>> {
620 let fsn = self.flush_and_keep_and_get_fsn(buf)?;
621 buf.clear();
622 Ok(fsn)
623 }
624
625 #[cfg(feature = "sync-sender-qwp-ws")]
631 pub fn flush_and_keep_and_get_fsn(&mut self, buf: &Buffer) -> Result<Option<u64>> {
632 self.flush_qwp_ws_buffer(buf, false)
633 }
634
635 #[cfg(feature = "sync-sender-qwp-ws")]
640 pub fn published_fsn(&self) -> Result<Option<u64>> {
641 match &self.handler {
642 SyncProtocolHandler::SyncQwpWs(state) => qwp_ws_published_fsn_background(state),
643 SyncProtocolHandler::ManualQwpWs(state) => qwp_ws_published_fsn_manual(state),
644 _ => Err(error::fmt!(
645 InvalidApiCall,
646 "published_fsn is only supported for QWP/WebSocket senders."
647 )),
648 }
649 }
650
651 #[cfg(feature = "sync-sender-qwp-ws")]
661 pub fn acked_fsn(&self) -> Result<Option<u64>> {
662 match &self.handler {
663 SyncProtocolHandler::SyncQwpWs(state) => qwp_ws_acked_fsn_background(state),
664 SyncProtocolHandler::ManualQwpWs(state) => qwp_ws_acked_fsn_manual(state),
665 _ => Err(error::fmt!(
666 InvalidApiCall,
667 "acked_fsn is only supported for QWP/WebSocket senders."
668 )),
669 }
670 }
671
672 #[cfg(feature = "sync-sender-qwp-ws")]
702 pub fn wait(&mut self, ack_level: AckLevel, timeout: Duration) -> Result<()> {
703 let result = self.wait_inner(ack_level, timeout);
704 let drain_result = self.drain_qwp_ws_error_notifications();
705 result.and(drain_result)
706 }
707
708 #[cfg(feature = "sync-sender-qwp-ws")]
709 fn wait_inner(&mut self, ack_level: AckLevel, timeout: Duration) -> Result<()> {
710 if !matches!(
711 &self.handler,
712 SyncProtocolHandler::SyncQwpWs(_) | SyncProtocolHandler::ManualQwpWs(_)
713 ) {
714 return Err(error::fmt!(
715 InvalidApiCall,
716 "wait is only supported for QWP/WebSocket senders."
717 ));
718 }
719
720 if ack_level == AckLevel::Durable {
721 let request_durable_ack = match &self.handler {
722 SyncProtocolHandler::SyncQwpWs(state) => state.request_durable_ack,
723 SyncProtocolHandler::ManualQwpWs(state) => state.request_durable_ack,
724 _ => unreachable!("QWP/WebSocket handler was checked above"),
725 };
726 if !request_durable_ack {
727 return Err(error::fmt!(
728 InvalidApiCall,
729 "AckLevel::Durable requires the pool to be opened with \
730 `request_durable_ack=on` in the connect string."
731 ));
732 }
733 }
734
735 let Some(boundary) = self.published_fsn()? else {
736 return Ok(());
737 };
738
739 let mut deadline_anchor = Instant::now();
742 let mut last_completed: Option<u64> = None;
743
744 loop {
745 let completed = self.qwp_ws_completed_fsn(ack_level)?;
746 if completed.is_some_and(|fsn| fsn >= boundary) {
747 return Ok(());
748 }
749 if completed != last_completed {
750 last_completed = completed;
751 deadline_anchor = Instant::now();
752 }
753 if !timeout.is_zero() && deadline_anchor.elapsed() >= timeout {
754 return Err(qwp_ws_wait_timeout(ack_level, timeout, boundary, completed));
755 }
756
757 match &mut self.handler {
758 SyncProtocolHandler::ManualQwpWs(state) => {
759 if !qwp_ws_drive_once(state)? {
760 qwp_ws_sleep_until(None);
761 }
762 }
763 SyncProtocolHandler::SyncQwpWs(_) => qwp_ws_sleep_until(None),
764 _ => unreachable!("QWP/WebSocket handler was checked above"),
765 }
766 }
767 }
768
769 #[cfg(feature = "sync-sender-qwp-ws")]
773 fn qwp_ws_completed_fsn(&self, ack_level: AckLevel) -> Result<Option<u64>> {
774 match (&self.handler, ack_level) {
775 (SyncProtocolHandler::SyncQwpWs(state), AckLevel::Ok) => {
776 qwp_ws_ok_fsn_background(state)
777 }
778 (SyncProtocolHandler::SyncQwpWs(state), AckLevel::Durable) => {
779 qwp_ws_acked_fsn_background(state)
780 }
781 (SyncProtocolHandler::ManualQwpWs(state), AckLevel::Ok) => qwp_ws_ok_fsn_manual(state),
782 (SyncProtocolHandler::ManualQwpWs(state), AckLevel::Durable) => {
783 qwp_ws_acked_fsn_manual(state)
784 }
785 _ => Err(error::fmt!(
786 InvalidApiCall,
787 "wait is only supported for QWP/WebSocket senders."
788 )),
789 }
790 }
791
792 #[cfg(feature = "sync-sender-qwp-ws")]
799 pub fn poll_qwp_ws_error(&mut self) -> Result<Option<QwpWsSenderError>> {
800 match &mut self.handler {
801 SyncProtocolHandler::SyncQwpWs(state) => qwp_ws_poll_sender_error_background(state),
802 SyncProtocolHandler::ManualQwpWs(state) => qwp_ws_poll_sender_error_manual(state),
803 _ => Err(error::fmt!(
804 InvalidApiCall,
805 "poll_qwp_ws_error is only supported for QWP/WebSocket senders."
806 )),
807 }
808 }
809
810 #[cfg(feature = "sync-sender-qwp-ws")]
816 #[doc(hidden)]
817 pub fn qwp_ws_terminal_error(&self) -> Result<Option<QwpWsSenderError>> {
818 match &self.handler {
819 SyncProtocolHandler::SyncQwpWs(state) => qwp_ws_terminal_sender_error_background(state),
820 SyncProtocolHandler::ManualQwpWs(state) => qwp_ws_terminal_sender_error_manual(state),
821 _ => Err(error::fmt!(
822 InvalidApiCall,
823 "qwp_ws_terminal_error is only supported for QWP/WebSocket senders."
824 )),
825 }
826 }
827
828 #[cfg(feature = "sync-sender-qwp-ws")]
837 pub fn qwp_ws_errors_dropped(&self) -> Result<u64> {
838 match &self.handler {
839 SyncProtocolHandler::SyncQwpWs(state) => qwp_ws_sender_errors_dropped_background(state),
840 SyncProtocolHandler::ManualQwpWs(state) => qwp_ws_sender_errors_dropped_manual(state),
841 _ => Err(error::fmt!(
842 InvalidApiCall,
843 "qwp_ws_errors_dropped is only supported for QWP/WebSocket senders."
844 )),
845 }
846 }
847
848 #[cfg(feature = "sync-sender-qwp-ws")]
855 pub fn qwp_ws_totals(&self) -> Result<QwpWsTotals> {
856 let counters = match &self.handler {
857 SyncProtocolHandler::SyncQwpWs(state) => qwp_ws_counters_background(state)?,
858 SyncProtocolHandler::ManualQwpWs(state) => qwp_ws_counters_manual(state)?,
859 _ => {
860 return Err(error::fmt!(
861 InvalidApiCall,
862 "qwp_ws_totals is only supported for QWP/WebSocket senders."
863 ));
864 }
865 };
866 Ok(counters.into())
867 }
868
869 #[cfg(feature = "sync-sender-qwp-ws")]
890 pub fn drive_once(&mut self) -> Result<bool> {
891 let result = match &mut self.handler {
892 SyncProtocolHandler::ManualQwpWs(state) => qwp_ws_drive_once(state),
893 SyncProtocolHandler::SyncQwpWs(_) => {
894 return Err(error::fmt!(
895 InvalidApiCall,
896 "drive_once is only supported when qwp_ws_progress is manual."
897 ));
898 }
899 _ => {
900 return Err(error::fmt!(
901 InvalidApiCall,
902 "drive_once is only supported for QWP/WebSocket senders."
903 ));
904 }
905 };
906 let drain_result = self.drain_qwp_ws_error_notifications();
907 let progressed = result?;
908 drain_result?;
909 Ok(progressed)
910 }
911
912 #[cfg(feature = "sync-sender-qwp-ws")]
919 pub fn close_drain(&mut self) -> Result<()> {
920 let result = match &mut self.handler {
921 SyncProtocolHandler::SyncQwpWs(state) => qwp_ws_close_drain_background(state),
922 SyncProtocolHandler::ManualQwpWs(state) => qwp_ws_close_drain_manual(state),
923 _ => Err(error::fmt!(
924 InvalidApiCall,
925 "close_drain is only supported for QWP/WebSocket senders."
926 )),
927 };
928 let drain_result = if matches!(
929 &self.handler,
930 SyncProtocolHandler::SyncQwpWs(_) | SyncProtocolHandler::ManualQwpWs(_)
931 ) {
932 self.drain_qwp_ws_error_notifications()
933 } else {
934 Ok(())
935 };
936 if result
937 .as_ref()
938 .is_err_and(|err| matches!(err.code(), crate::ErrorCode::SocketError))
939 {
940 self.connected = false;
941 }
942 result.and(drain_result)
943 }
944
945 #[must_use]
958 pub fn must_close(&self) -> bool {
959 if !self.connected {
960 return true;
961 }
962 #[cfg(feature = "sync-sender-qwp-ws")]
963 match &self.handler {
964 SyncProtocolHandler::SyncQwpWs(state) => return qwp_ws_is_terminal_background(state),
965 SyncProtocolHandler::ManualQwpWs(state) => return qwp_ws_is_terminal_manual(state),
966 _ => {}
967 }
968 false
969 }
970
971 #[cfg(all(test, feature = "sync-sender-qwp-ws"))]
977 pub(crate) fn sfa_fully_delivered(&self, durable: bool) -> bool {
978 let SyncProtocolHandler::SyncQwpWs(state) = &self.handler else {
979 return true;
980 };
981 if qwp_ws_is_terminal_background(state) {
982 return true;
983 }
984 let Ok(Some(published)) = qwp_ws_published_fsn_background(state) else {
985 return true;
986 };
987 let watermark = if durable {
988 qwp_ws_acked_fsn_background(state)
989 } else {
990 qwp_ws_ok_fsn_background(state)
991 };
992 matches!(watermark, Ok(Some(w)) if w >= published)
993 }
994
995 pub fn protocol(&self) -> Protocol {
997 self.protocol
998 }
999
1000 pub fn protocol_version(&self) -> ProtocolVersion {
1008 self.protocol_version
1009 }
1010
1011 pub fn max_name_len(&self) -> usize {
1018 self.max_name_len
1019 }
1020
1021 #[inline(always)]
1022 fn check_protocol_version(&self, version: ProtocolVersion) -> Result<()> {
1023 if self.protocol_version != version {
1024 return Err(error::fmt!(
1025 ProtocolVersionError,
1026 "Attempting to send with protocol version {} \
1027 but the sender is configured to use protocol version {}",
1028 version,
1029 self.protocol_version
1030 ));
1031 }
1032 Ok(())
1033 }
1034}
1035
1036#[cfg(feature = "sync-sender-qwp-ws")]
1037fn qwp_ws_deadline_expired(deadline: Option<Instant>) -> bool {
1038 deadline.is_some_and(|deadline| Instant::now() >= deadline)
1039}
1040
1041#[cfg(feature = "sync-sender-qwp-ws")]
1042fn qwp_ws_sleep_until(deadline: Option<Instant>) {
1043 const PARK: Duration = Duration::from_micros(50);
1044 if qwp_ws_deadline_expired(deadline) {
1045 return;
1046 }
1047 let sleep_for = deadline
1048 .map(|deadline| deadline.saturating_duration_since(Instant::now()).min(PARK))
1049 .unwrap_or(PARK);
1050 if !sleep_for.is_zero() {
1051 std::thread::sleep(sleep_for);
1052 }
1053}
1054
1055#[cfg(feature = "sync-sender-qwp-ws")]
1062fn qwp_ws_wait_timeout(
1063 ack_level: AckLevel,
1064 timeout: Duration,
1065 boundary: u64,
1066 completed: Option<u64>,
1067) -> crate::Error {
1068 let level = match ack_level {
1069 AckLevel::Ok => "ok",
1070 AckLevel::Durable => "durable",
1071 };
1072 let progress = match completed {
1073 Some(fsn) => format!("reached FSN {fsn}"),
1074 None => "reached no frame".to_string(),
1075 };
1076 error::Error::new(
1077 error::ErrorCode::FailoverRetry,
1078 format!(
1079 "QWP/WebSocket wait({level}) timed out after {timeout:?} with no ack \
1080 progress (target FSN {boundary}, {progress}); the connection is alive \
1081 but the server is not advancing the watermark. The published frames \
1082 remain queued and the background runner keeps delivering them: retry \
1083 wait() to keep awaiting the ack, or close the pool to drain. Do not \
1084 re-flush the same data, which is already accepted and would be \
1085 delivered twice."
1086 ),
1087 )
1088}