1use core::cell::Cell;
4use std::io::{self, BufWriter, Write};
5use std::sync::atomic::{AtomicU64, Ordering};
6use std::sync::mpsc::{self, Receiver, SyncSender, TryRecvError, TrySendError};
7use std::sync::{Arc, Mutex, PoisonError, RwLock, RwLockReadGuard, TryLockError};
8use std::thread::JoinHandle;
9
10use super::{Sink, lock_recover, trim_buffer};
11use crate::error::{Error, Result};
12use crate::format::Format;
13use crate::record::Record;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
17#[non_exhaustive]
18pub enum Overflow {
19 #[default]
27 Block,
28 DropNewest,
40}
41
42pub type WorkerErrorHandler = Arc<dyn Fn(&io::Error) + Send + Sync>;
44
45enum Message {
46 Record(String),
47 Flush(SyncSender<io::Result<()>>),
48 FlushRequest,
51 Shutdown,
52}
53
54thread_local! {
55 static WORKER_OF: Cell<usize> = const { Cell::new(0) };
58}
59
60pub(crate) fn on_background_worker() -> bool {
64 WORKER_OF.try_with(Cell::get).unwrap_or(0) != 0
65}
66
67struct Shared {
68 dropped: AtomicU64,
69 write_errors: AtomicU64,
70 deferred: Mutex<Option<io::Error>>,
72 on_error: Option<WorkerErrorHandler>,
73}
74
75struct Channel {
79 sender: Option<SyncSender<Message>>,
80 worker: Option<JoinHandle<Box<dyn Write + Send>>>,
81}
82
83#[cfg_attr(all(feature = "json"), doc = "```")]
136#[cfg_attr(not(all(feature = "json")), doc = "```ignore")]
137pub struct NonBlocking<F: Format> {
151 format: F,
152 overflow: Overflow,
153 channel: RwLock<Channel>,
154 fallback: Mutex<Option<Box<dyn Write + Send>>>,
159 pool: Mutex<Receiver<String>>,
160 shared: Arc<Shared>,
161}
162
163impl<F: Format> NonBlocking<F> {
164 pub fn new<W: Write + Send + 'static>(writer: W, format: F) -> Result<Self> {
171 Self::builder(writer, format).build()
172 }
173
174 pub fn builder<W: Write + Send + 'static>(writer: W, format: F) -> NonBlockingBuilder<F> {
176 NonBlockingBuilder {
177 writer: Box::new(writer),
178 format,
179 capacity: 8192,
180 overflow: Overflow::Block,
181 buffer_capacity: 64 * 1024,
182 thread_name: String::from("log-io-writer"),
183 on_error: None,
184 }
185 }
186
187 #[must_use]
190 pub fn dropped(&self) -> u64 {
191 self.shared.dropped.load(Ordering::Relaxed)
192 }
193
194 #[must_use]
196 pub fn write_errors(&self) -> u64 {
197 self.shared.write_errors.load(Ordering::Relaxed)
198 }
199
200 fn take_buffer(&self) -> String {
201 self.pool
204 .try_lock()
205 .ok()
206 .and_then(|rx| rx.try_recv().ok())
207 .unwrap_or_default()
208 }
209
210 fn on_own_worker(&self) -> bool {
212 let me = Arc::as_ptr(&self.shared).addr();
213 WORKER_OF.try_with(Cell::get).is_ok_and(|w| w == me)
214 }
215
216 fn count_drop(&self) {
217 let _previous = self.shared.dropped.fetch_add(1, Ordering::Relaxed);
218 }
219
220 fn channel(&self, may_wait: bool) -> Option<RwLockReadGuard<'_, Channel>> {
223 match self.channel.try_read() {
224 Ok(channel) => Some(channel),
225 Err(TryLockError::Poisoned(p)) => Some(p.into_inner()),
226 Err(TryLockError::WouldBlock) if may_wait => {
227 Some(self.channel.read().unwrap_or_else(PoisonError::into_inner))
228 }
229 Err(TryLockError::WouldBlock) => None,
230 }
231 }
232
233 fn queue_flush(&self) -> Result<()> {
235 let Some(channel) = self.channel(false) else {
237 return Ok(());
238 };
239 if let Some(sender) = &channel.sender {
240 match sender.try_send(Message::FlushRequest) {
241 Ok(()) | Err(TrySendError::Full(_)) => {}
244 Err(TrySendError::Disconnected(_)) => return Err(Error::Closed),
245 }
246 drop(channel);
247 return self.take_deferred();
248 }
249 drop(channel);
250 if let Some(writer) = lock_recover(&self.fallback).as_mut() {
252 writer.flush()?;
253 }
254 self.take_deferred()
255 }
256
257 fn take_deferred(&self) -> Result<()> {
258 match lock_recover(&self.shared.deferred).take() {
259 Some(e) => Err(Error::Io(e)),
260 None => Ok(()),
261 }
262 }
263}
264
265impl<F: Format> Sink for NonBlocking<F> {
266 fn write_record(&self, record: &Record<'_>) -> Result<()> {
267 let mut text = self.take_buffer();
268 text.clear();
269 self.format
270 .write_record_generic(record, &mut text)
271 .map_err(Error::Format)?;
272
273 let Some(channel) = self.channel(!self.on_own_worker()) else {
276 self.count_drop();
277 return Ok(());
278 };
279 if let Some(sender) = &channel.sender {
280 return match sender.try_send(Message::Record(text)) {
281 Ok(()) => Ok(()),
282 Err(TrySendError::Full(message)) => {
283 if matches!(self.overflow, Overflow::Block) && !self.on_own_worker() {
284 sender.send(message).map_err(|_| Error::Closed)
285 } else {
286 self.count_drop();
287 Ok(())
288 }
289 }
290 Err(TrySendError::Disconnected(_)) => Err(Error::Closed),
291 };
292 }
293 drop(channel);
294 match lock_recover(&self.fallback).as_mut() {
296 Some(writer) => writer.write_all(text.as_bytes()).map_err(Error::from),
297 None => Err(Error::Closed),
298 }
299 }
300
301 fn flush(&self) -> Result<()> {
302 if self.on_own_worker() {
303 return Err(Error::Closed);
305 }
306 let channel = self.channel.read().unwrap_or_else(PoisonError::into_inner);
307 if let Some(sender) = &channel.sender {
308 let (ack_tx, ack_rx) = mpsc::sync_channel(1);
309 sender
310 .send(Message::Flush(ack_tx))
311 .map_err(|_| Error::Closed)?;
312 drop(channel);
313 let flushed = ack_rx.recv().map_err(|_| Error::Closed)?;
314 self.take_deferred()?;
315 return flushed.map_err(Error::from);
316 }
317 drop(channel);
318 if let Some(writer) = lock_recover(&self.fallback).as_mut() {
319 writer.flush()?;
320 }
321 self.take_deferred()
322 }
323
324 fn request_flush(&self) -> Result<()> {
325 if self.on_own_worker() {
326 return Ok(());
328 }
329 match self.overflow {
330 Overflow::DropNewest => self.queue_flush(),
331 _ => self.flush(),
333 }
334 }
335
336 fn shutdown(&self) -> Result<()> {
337 if self.on_own_worker() {
338 return Err(Error::Closed);
340 }
341 let mut channel = self.channel.write().unwrap_or_else(PoisonError::into_inner);
342 let Some(sender) = channel.sender.take() else {
343 drop(channel);
344 return self.flush();
345 };
346 let sent = sender.send(Message::Shutdown);
349 drop(sender);
350 let joined = channel.worker.take().map(JoinHandle::join);
351 match joined {
352 Some(Ok(writer)) => *lock_recover(&self.fallback) = Some(writer),
353 Some(Err(_)) => return Err(Error::Closed),
354 None => {}
355 }
356 drop(channel);
357 sent.map_err(|_| Error::Closed)?;
358 self.take_deferred()
359 }
360}
361
362impl<F: Format> Drop for NonBlocking<F> {
363 fn drop(&mut self) {
364 let _shutdown_in_drop = self.shutdown();
367 }
368}
369
370impl<F: Format + core::fmt::Debug> core::fmt::Debug for NonBlocking<F> {
371 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
372 f.debug_struct("NonBlocking")
373 .field("format", &self.format)
374 .field("overflow", &self.overflow)
375 .field("dropped", &self.dropped())
376 .field("write_errors", &self.write_errors())
377 .finish_non_exhaustive()
378 }
379}
380
381#[must_use = "call `build()` to start the background writer"]
384pub struct NonBlockingBuilder<F> {
385 writer: Box<dyn Write + Send>,
386 format: F,
387 capacity: usize,
388 overflow: Overflow,
389 buffer_capacity: usize,
390 thread_name: String,
391 on_error: Option<WorkerErrorHandler>,
392}
393
394const MAX_CAPACITY: usize = 1 << 20;
398
399impl<F: Format> NonBlockingBuilder<F> {
400 pub fn capacity(mut self, records: usize) -> Self {
404 self.capacity = records.clamp(1, MAX_CAPACITY);
405 self
406 }
407
408 pub fn overflow(mut self, overflow: Overflow) -> Self {
410 self.overflow = overflow;
411 self
412 }
413
414 pub fn buffer_capacity(mut self, bytes: usize) -> Self {
417 self.buffer_capacity = bytes;
418 self
419 }
420
421 pub fn thread_name(mut self, name: impl Into<String>) -> Self {
424 self.thread_name = name.into();
425 self
426 }
427
428 pub fn on_error(mut self, handler: WorkerErrorHandler) -> Self {
430 self.on_error = Some(handler);
431 self
432 }
433
434 pub fn build(self) -> Result<NonBlocking<F>> {
441 if self.thread_name.contains('\0') {
442 return Err(Error::Configuration(
443 "NonBlocking thread name must not contain a NUL byte",
444 ));
445 }
446 let (tx, rx) = mpsc::sync_channel::<Message>(self.capacity);
447 let (pool_tx, pool_rx) = mpsc::sync_channel::<String>(self.capacity);
448 let shared = Arc::new(Shared {
449 dropped: AtomicU64::new(0),
450 write_errors: AtomicU64::new(0),
451 deferred: Mutex::new(None),
452 on_error: self.on_error,
453 });
454 let worker_shared = Arc::clone(&shared);
455 let identity = Arc::as_ptr(&shared).addr();
456 let writer = BufWriter::with_capacity(self.buffer_capacity, self.writer);
457 let worker = std::thread::Builder::new()
458 .name(self.thread_name)
459 .spawn(move || {
460 WORKER_OF.with(|w| w.set(identity));
461 run_worker(writer, &rx, &pool_tx, &worker_shared)
462 })?;
463 Ok(NonBlocking {
464 format: self.format,
465 overflow: self.overflow,
466 channel: RwLock::new(Channel {
467 sender: Some(tx),
468 worker: Some(worker),
469 }),
470 fallback: Mutex::new(None),
471 pool: Mutex::new(pool_rx),
472 shared,
473 })
474 }
475}
476
477impl<F: core::fmt::Debug> core::fmt::Debug for NonBlockingBuilder<F> {
478 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
479 f.debug_struct("NonBlockingBuilder")
480 .field("format", &self.format)
481 .field("capacity", &self.capacity)
482 .field("overflow", &self.overflow)
483 .field("thread_name", &self.thread_name)
484 .finish_non_exhaustive()
485 }
486}
487
488fn report(shared: &Shared, error: io::Error) {
489 let _previous = shared.write_errors.fetch_add(1, Ordering::Relaxed);
490 if let Some(handler) = &shared.on_error {
491 handler(&error);
492 }
493 let mut deferred = lock_recover(&shared.deferred);
494 if deferred.is_none() {
495 *deferred = Some(error);
496 }
497}
498
499fn run_worker(
500 mut writer: BufWriter<Box<dyn Write + Send>>,
501 rx: &Receiver<Message>,
502 pool: &SyncSender<String>,
503 shared: &Shared,
504) -> Box<dyn Write + Send> {
505 let mut dirty = false;
506 loop {
507 let message = match rx.try_recv() {
508 Ok(m) => m,
509 Err(TryRecvError::Empty) => {
510 if dirty {
513 if let Err(e) = writer.flush() {
514 report(shared, e);
515 }
516 }
517 match rx.recv() {
518 Ok(m) => m,
519 Err(_) => break,
520 }
521 }
522 Err(TryRecvError::Disconnected) => break,
523 };
524 match message {
525 Message::Record(mut text) => {
526 if let Err(e) = writer.write_all(text.as_bytes()) {
527 report(shared, e);
528 }
529 dirty = true;
530 trim_buffer(&mut text);
531 let _recycled = pool.try_send(text);
533 }
534 Message::Flush(ack) => {
535 dirty = false;
536 let _requester_may_be_gone = ack.send(writer.flush());
537 }
538 Message::FlushRequest => {
539 dirty = false;
540 if let Err(e) = writer.flush() {
541 report(shared, e);
542 }
543 }
544 Message::Shutdown => break,
545 }
546 }
547 if let Err(e) = writer.flush() {
548 report(shared, e);
549 }
550 match writer.into_inner() {
551 Ok(inner) => inner,
552 Err(e) => {
553 let (error, writer) = e.into_parts();
554 report(shared, error);
555 Box::new(writer)
558 }
559 }
560}
561
562#[cfg(all(test, feature = "json"))]
563mod tests {
564 use super::*;
565 use crate::format::JsonFormat;
566 use crate::{Level, Metadata};
567 use std::sync::Barrier;
568
569 #[derive(Clone, Default)]
570 struct Capture(Arc<Mutex<Vec<u8>>>);
571 impl Write for Capture {
572 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
573 self.0.lock().unwrap().extend_from_slice(buf);
574 Ok(buf.len())
575 }
576 fn flush(&mut self) -> io::Result<()> {
577 Ok(())
578 }
579 }
580 impl Capture {
581 fn lines(&self) -> usize {
582 String::from_utf8(self.0.lock().unwrap().clone())
583 .unwrap()
584 .lines()
585 .count()
586 }
587 }
588
589 fn record(msg: &str) -> Record<'_> {
590 Record::new(Metadata::new(Level::Info, "t"), msg, &[])
591 }
592
593 #[test]
594 fn test_non_blocking_flush_waits_for_queued_records() {
595 let out = Capture::default();
596 let sink = NonBlocking::new(out.clone(), JsonFormat::new()).unwrap();
597 for _ in 0..1_000 {
598 sink.write_record(&record("x")).unwrap();
599 }
600 sink.flush().unwrap();
601 assert_eq!(out.lines(), 1_000);
602 }
603
604 #[test]
605 fn test_non_blocking_shutdown_drains_then_writes_synchronously() {
606 let out = Capture::default();
607 let sink = NonBlocking::new(out.clone(), JsonFormat::new()).unwrap();
608 for _ in 0..100 {
609 sink.write_record(&record("queued")).unwrap();
610 }
611 sink.shutdown().unwrap();
612 assert_eq!(out.lines(), 100);
613 sink.write_record(&record("late")).unwrap();
615 sink.flush().unwrap();
616 sink.shutdown().unwrap();
617 assert_eq!(out.lines(), 101);
618 }
619
620 #[test]
621 fn test_non_blocking_drop_newest_counts_drops() {
622 struct Gate(Arc<Barrier>, Capture, bool);
624 impl Write for Gate {
625 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
626 if !self.2 {
627 self.2 = true;
628 let _leader = self.0.wait();
629 }
630 self.1.write(buf)
631 }
632 fn flush(&mut self) -> io::Result<()> {
633 Ok(())
634 }
635 }
636 let gate = Arc::new(Barrier::new(2));
637 let out = Capture::default();
638 let sink = NonBlocking::builder(Gate(gate.clone(), out.clone(), false), JsonFormat::new())
639 .capacity(2)
640 .overflow(Overflow::DropNewest)
641 .buffer_capacity(0)
642 .build()
643 .unwrap();
644 for _ in 0..50 {
645 sink.write_record(&record("burst")).unwrap();
646 }
647 let _leader = gate.wait();
648 sink.flush().unwrap();
649 let written = out.lines() as u64;
650 assert!(sink.dropped() > 0, "the queue must have overflowed");
651 assert_eq!(
652 written + sink.dropped(),
653 50,
654 "every record is written or counted"
655 );
656 }
657
658 #[test]
659 fn test_non_blocking_write_errors_are_counted_and_deferred_to_flush() {
660 struct Failing;
661 impl Write for Failing {
662 fn write(&mut self, _: &[u8]) -> io::Result<usize> {
663 Err(io::Error::other("disk full"))
664 }
665 fn flush(&mut self) -> io::Result<()> {
666 Ok(())
667 }
668 }
669 let seen = Arc::new(AtomicU64::new(0));
670 let seen2 = Arc::clone(&seen);
671 let sink = NonBlocking::builder(Failing, JsonFormat::new())
672 .buffer_capacity(0)
673 .thread_name("log-io-test-writer")
674 .on_error(Arc::new(move |_| {
675 let _n = seen2.fetch_add(1, Ordering::Relaxed);
676 }))
677 .build()
678 .unwrap();
679 sink.write_record(&record("lost")).unwrap();
680 sink.write_record(&record("lost too")).unwrap();
681 let err = sink.flush().unwrap_err();
682 assert!(matches!(err, Error::Io(_)), "{err}");
683 assert_eq!(sink.write_errors(), 2);
684 assert_eq!(seen.load(Ordering::Relaxed), 2);
685 assert!(sink.flush().is_ok(), "the deferred error is reported once");
686 assert!(format!("{sink:?}").contains("write_errors: 2"));
687 }
688
689 #[test]
690 fn test_non_blocking_many_producers_lose_nothing_with_block() {
691 let out = Capture::default();
692 let sink = Arc::new(
693 NonBlocking::builder(out.clone(), JsonFormat::new())
694 .capacity(16)
695 .build()
696 .unwrap(),
697 );
698 std::thread::scope(|s| {
699 for _ in 0..8 {
700 let sink = Arc::clone(&sink);
701 let _handle = s.spawn(move || {
702 for _ in 0..500 {
703 sink.write_record(&record("p")).unwrap();
704 }
705 });
706 }
707 });
708 sink.shutdown().unwrap();
709 assert_eq!(out.lines(), 8 * 500);
710 assert_eq!(sink.dropped(), 0);
711 }
712
713 #[test]
714 fn test_non_blocking_worker_panic_reports_closed() {
715 struct Panics;
716 impl Write for Panics {
717 fn write(&mut self, _: &[u8]) -> io::Result<usize> {
718 panic!("writer exploded");
719 }
720 fn flush(&mut self) -> io::Result<()> {
721 Ok(())
722 }
723 }
724 let sink = NonBlocking::builder(Panics, JsonFormat::new())
725 .buffer_capacity(0)
726 .build()
727 .unwrap();
728 let _queued = sink.write_record(&record("boom"));
729 assert!(matches!(sink.shutdown(), Err(Error::Closed)));
730 assert!(matches!(
731 sink.write_record(&record("after")),
732 Err(Error::Closed)
733 ));
734 let builder = NonBlocking::builder(Vec::<u8>::new(), JsonFormat::new()).capacity(0);
735 assert!(format!("{builder:?}").contains("capacity: 1"));
736 }
737
738 #[test]
739 fn test_non_blocking_builder_rejects_bad_options_without_panicking() {
740 let err = NonBlocking::builder(Vec::<u8>::new(), JsonFormat::new())
742 .thread_name("a\0b")
743 .build()
744 .unwrap_err();
745 assert!(matches!(err, Error::Configuration(_)), "{err}");
746 let builder =
747 NonBlocking::builder(Vec::<u8>::new(), JsonFormat::new()).capacity(usize::MAX / 2);
748 assert!(format!("{builder:?}").contains("capacity: 1048576"));
749 }
750
751 struct Stall(Option<mpsc::Receiver<()>>, Capture);
753 impl Write for Stall {
754 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
755 if let Some(gate) = self.0.take() {
756 let _released = gate.recv();
757 }
758 self.1.write(buf)
759 }
760 fn flush(&mut self) -> io::Result<()> {
761 Ok(())
762 }
763 }
764
765 #[test]
766 fn test_non_blocking_drop_newest_flush_level_record_does_not_wait() {
767 let (release, gate) = mpsc::channel();
770 let out = Capture::default();
771 let sink = NonBlocking::builder(Stall(Some(gate), out.clone()), JsonFormat::new())
772 .capacity(4)
773 .overflow(Overflow::DropNewest)
774 .buffer_capacity(0)
775 .build()
776 .unwrap();
777 let logger = crate::Logger::builder()
778 .level(Level::Trace)
779 .no_timestamps()
780 .with_sink(sink)
781 .build();
782 for _ in 0..20 {
783 logger.log(Level::Info, "queued", &[]);
784 }
785 let (done, returned) = mpsc::channel();
786 let error_logger = logger.clone();
787 let handle = std::thread::spawn(move || {
788 error_logger.log(Level::Error, "error", &[]);
789 let _sent = done.send(());
790 });
791 let waited = returned.recv_timeout(std::time::Duration::from_secs(10));
792 release.send(()).unwrap();
793 handle.join().unwrap();
794 assert!(
795 waited.is_ok(),
796 "a flush-level record must not wait for the writer under DropNewest"
797 );
798 logger.flush().unwrap();
800 assert!(out.lines() >= 1);
801 }
802
803 #[test]
804 fn test_non_blocking_worker_logging_into_its_own_sink_never_deadlocks() {
805 struct Echo {
808 logger: Arc<std::sync::OnceLock<crate::Logger>>,
809 echoes: usize,
810 flush_from_worker: Arc<Mutex<Option<bool>>>,
811 }
812 impl Write for Echo {
813 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
814 if let Some(logger) = self.logger.get() {
815 if self.echoes < 50 {
816 self.echoes += 1;
817 logger.log(Level::Error, "echo from the writer", &[]);
818 let closed = matches!(logger.flush(), Err(Error::Closed));
819 *self.flush_from_worker.lock().unwrap() = Some(closed);
820 }
821 }
822 Ok(buf.len())
823 }
824 fn flush(&mut self) -> io::Result<()> {
825 Ok(())
826 }
827 }
828 let cell = Arc::new(std::sync::OnceLock::new());
829 let flush_from_worker = Arc::new(Mutex::new(None));
830 let sink = NonBlocking::builder(
831 Echo {
832 logger: Arc::clone(&cell),
833 echoes: 0,
834 flush_from_worker: Arc::clone(&flush_from_worker),
835 },
836 JsonFormat::new(),
837 )
838 .capacity(1)
839 .buffer_capacity(0)
840 .build()
841 .unwrap();
842 let logger = crate::Logger::builder()
843 .level(Level::Trace)
844 .no_timestamps()
845 .with_sink(sink)
846 .build();
847 let _set = cell.set(logger.clone());
848 let (done, finished) = mpsc::channel();
849 let _detached = std::thread::spawn(move || {
850 for _ in 0..200 {
851 logger.log(Level::Info, "from a producer", &[]);
852 }
853 let shut = logger.shutdown();
854 let _sent = done.send(shut.is_ok());
855 });
856 let outcome = finished.recv_timeout(std::time::Duration::from_secs(30));
857 assert_eq!(outcome, Ok(true), "the worker waited for itself");
858 assert_eq!(
859 *flush_from_worker.lock().unwrap(),
860 Some(true),
861 "a flush from the worker thread reports `Closed` instead of waiting"
862 );
863 drop(cell);
866 }
867
868 #[test]
869 fn test_non_blocking_drop_without_logger_drains_the_queue() {
870 let out = Capture::default();
871 let sink = NonBlocking::new(out.clone(), JsonFormat::new()).unwrap();
872 for _ in 0..100 {
873 sink.write_record(&record("x")).unwrap();
874 }
875 drop(sink);
876 assert_eq!(out.lines(), 100);
877 }
878}