1use core::cell::Cell;
4use std::io::{self, BufWriter, Write};
5use std::sync::atomic::{AtomicU64, AtomicUsize, 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 queued_bytes: AtomicUsize,
70 dropped: AtomicU64,
71 write_errors: AtomicU64,
72 deferred: Mutex<Option<io::Error>>,
74 on_error: Option<WorkerErrorHandler>,
75}
76
77struct Channel {
81 sender: Option<SyncSender<Message>>,
82 worker: Option<JoinHandle<Box<dyn Write + Send>>>,
83}
84
85#[cfg_attr(all(feature = "json"), doc = "```")]
140#[cfg_attr(not(all(feature = "json")), doc = "```ignore")]
141pub struct NonBlocking<F: Format> {
155 format: F,
156 overflow: Overflow,
157 channel: RwLock<Channel>,
158 fallback: Mutex<Option<Box<dyn Write + Send>>>,
163 pool: Mutex<Receiver<String>>,
164 shared: Arc<Shared>,
165 max_queued_bytes: usize,
166}
167
168impl<F: Format> NonBlocking<F> {
169 pub fn new<W: Write + Send + 'static>(writer: W, format: F) -> Result<Self> {
176 Self::builder(writer, format).build()
177 }
178
179 pub fn builder<W: Write + Send + 'static>(writer: W, format: F) -> NonBlockingBuilder<F> {
181 NonBlockingBuilder {
182 writer: Box::new(writer),
183 format,
184 capacity: 8192,
185 max_queued_bytes: DEFAULT_MAX_QUEUED_BYTES,
186 overflow: Overflow::Block,
187 buffer_capacity: 64 * 1024,
188 thread_name: String::from("log-io-writer"),
189 on_error: None,
190 }
191 }
192
193 #[must_use]
196 pub fn dropped(&self) -> u64 {
197 self.shared.dropped.load(Ordering::Relaxed)
198 }
199
200 #[must_use]
202 pub fn write_errors(&self) -> u64 {
203 self.shared.write_errors.load(Ordering::Relaxed)
204 }
205
206 #[must_use]
208 pub fn queued_bytes(&self) -> usize {
209 self.shared.queued_bytes.load(Ordering::Relaxed)
210 }
211
212 fn fits(&self, len: usize) -> bool {
216 let queued = self.shared.queued_bytes.load(Ordering::Relaxed);
217 queued == 0 || queued.saturating_add(len) <= self.max_queued_bytes
218 }
219
220 fn unqueue(&self, len: usize) {
221 let _previous = self.shared.queued_bytes.fetch_sub(len, Ordering::Relaxed);
222 }
223
224 fn take_buffer(&self) -> String {
225 self.pool
228 .try_lock()
229 .ok()
230 .and_then(|rx| rx.try_recv().ok())
231 .unwrap_or_default()
232 }
233
234 fn on_own_worker(&self) -> bool {
236 let me = Arc::as_ptr(&self.shared).addr();
237 WORKER_OF.try_with(Cell::get).is_ok_and(|w| w == me)
238 }
239
240 fn count_drop(&self) {
241 let _previous = self.shared.dropped.fetch_add(1, Ordering::Relaxed);
242 }
243
244 fn channel(&self, may_wait: bool) -> Option<RwLockReadGuard<'_, Channel>> {
247 match self.channel.try_read() {
248 Ok(channel) => Some(channel),
249 Err(TryLockError::Poisoned(p)) => Some(p.into_inner()),
250 Err(TryLockError::WouldBlock) if may_wait => {
251 Some(self.channel.read().unwrap_or_else(PoisonError::into_inner))
252 }
253 Err(TryLockError::WouldBlock) => None,
254 }
255 }
256
257 fn queue_flush(&self) -> Result<()> {
259 let Some(channel) = self.channel(false) else {
261 return Ok(());
262 };
263 if let Some(sender) = &channel.sender {
264 match sender.try_send(Message::FlushRequest) {
265 Ok(()) | Err(TrySendError::Full(_)) => {}
268 Err(TrySendError::Disconnected(_)) => return Err(Error::Closed),
269 }
270 drop(channel);
271 return self.take_deferred();
272 }
273 drop(channel);
274 if let Some(writer) = lock_recover(&self.fallback).as_mut() {
276 writer.flush()?;
277 }
278 self.take_deferred()
279 }
280
281 fn take_deferred(&self) -> Result<()> {
282 match lock_recover(&self.shared.deferred).take() {
283 Some(e) => Err(Error::Io(e)),
284 None => Ok(()),
285 }
286 }
287}
288
289impl<F: Format> Sink for NonBlocking<F> {
290 fn write_record(&self, record: &Record<'_>) -> Result<()> {
291 let mut text = self.take_buffer();
292 text.clear();
293 self.format
294 .write_record_generic(record, &mut text)
295 .map_err(Error::Format)?;
296
297 let Some(channel) = self.channel(!self.on_own_worker()) else {
300 self.count_drop();
301 return Ok(());
302 };
303 if let Some(sender) = &channel.sender {
304 let len = text.len();
305 let may_block = matches!(self.overflow, Overflow::Block) && !self.on_own_worker();
306 while !self.fits(len) {
307 if !may_block {
308 self.count_drop();
309 return Ok(());
310 }
311 let (ack_tx, ack_rx) = mpsc::sync_channel(1);
314 sender
315 .send(Message::Flush(ack_tx))
316 .map_err(|_| Error::Closed)?;
317 let _flushed = ack_rx.recv().map_err(|_| Error::Closed)?;
318 }
319 let _previous = self.shared.queued_bytes.fetch_add(len, Ordering::Relaxed);
320 return match sender.try_send(Message::Record(text)) {
321 Ok(()) => Ok(()),
322 Err(TrySendError::Full(message)) => {
323 if may_block {
324 sender.send(message).map_err(|_| {
325 self.unqueue(len);
326 Error::Closed
327 })
328 } else {
329 self.unqueue(len);
330 self.count_drop();
331 Ok(())
332 }
333 }
334 Err(TrySendError::Disconnected(_)) => {
335 self.unqueue(len);
336 Err(Error::Closed)
337 }
338 };
339 }
340 drop(channel);
341 match lock_recover(&self.fallback).as_mut() {
343 Some(writer) => writer.write_all(text.as_bytes()).map_err(Error::from),
344 None => Err(Error::Closed),
345 }
346 }
347
348 fn flush(&self) -> Result<()> {
349 if self.on_own_worker() {
350 return Err(Error::Closed);
352 }
353 let channel = self.channel.read().unwrap_or_else(PoisonError::into_inner);
354 if let Some(sender) = &channel.sender {
355 let (ack_tx, ack_rx) = mpsc::sync_channel(1);
356 sender
357 .send(Message::Flush(ack_tx))
358 .map_err(|_| Error::Closed)?;
359 drop(channel);
360 let flushed = ack_rx.recv().map_err(|_| Error::Closed)?;
361 self.take_deferred()?;
362 return flushed.map_err(Error::from);
363 }
364 drop(channel);
365 if let Some(writer) = lock_recover(&self.fallback).as_mut() {
366 writer.flush()?;
367 }
368 self.take_deferred()
369 }
370
371 fn request_flush(&self) -> Result<()> {
372 if self.on_own_worker() {
373 return Ok(());
375 }
376 match self.overflow {
377 Overflow::DropNewest => self.queue_flush(),
378 _ => self.flush(),
380 }
381 }
382
383 fn shutdown(&self) -> Result<()> {
384 if self.on_own_worker() {
385 return Err(Error::Closed);
387 }
388 let mut channel = self.channel.write().unwrap_or_else(PoisonError::into_inner);
389 let Some(sender) = channel.sender.take() else {
390 drop(channel);
391 return self.flush();
392 };
393 let sent = sender.send(Message::Shutdown);
396 drop(sender);
397 let joined = channel.worker.take().map(JoinHandle::join);
398 match joined {
399 Some(Ok(writer)) => *lock_recover(&self.fallback) = Some(writer),
400 Some(Err(_)) => return Err(Error::Closed),
401 None => {}
402 }
403 drop(channel);
404 sent.map_err(|_| Error::Closed)?;
405 self.take_deferred()
406 }
407}
408
409impl<F: Format> Drop for NonBlocking<F> {
410 fn drop(&mut self) {
411 let _shutdown_in_drop = self.shutdown();
414 }
415}
416
417impl<F: Format + core::fmt::Debug> core::fmt::Debug for NonBlocking<F> {
418 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
419 f.debug_struct("NonBlocking")
420 .field("format", &self.format)
421 .field("overflow", &self.overflow)
422 .field("dropped", &self.dropped())
423 .field("write_errors", &self.write_errors())
424 .finish_non_exhaustive()
425 }
426}
427
428#[must_use = "call `build()` to start the background writer"]
431pub struct NonBlockingBuilder<F> {
432 writer: Box<dyn Write + Send>,
433 format: F,
434 capacity: usize,
435 max_queued_bytes: usize,
436 overflow: Overflow,
437 buffer_capacity: usize,
438 thread_name: String,
439 on_error: Option<WorkerErrorHandler>,
440}
441
442const MAX_CAPACITY: usize = 1 << 20;
446
447const DEFAULT_MAX_QUEUED_BYTES: usize = 16 * 1024 * 1024;
449
450const MAX_POOLED: usize = 256;
454
455impl<F: Format> NonBlockingBuilder<F> {
456 pub fn capacity(mut self, records: usize) -> Self {
460 self.capacity = records.clamp(1, MAX_CAPACITY);
461 self
462 }
463
464 pub fn max_queued_bytes(mut self, bytes: usize) -> Self {
476 self.max_queued_bytes = bytes.max(1);
477 self
478 }
479
480 pub fn overflow(mut self, overflow: Overflow) -> Self {
482 self.overflow = overflow;
483 self
484 }
485
486 pub fn buffer_capacity(mut self, bytes: usize) -> Self {
489 self.buffer_capacity = bytes;
490 self
491 }
492
493 pub fn thread_name(mut self, name: impl Into<String>) -> Self {
496 self.thread_name = name.into();
497 self
498 }
499
500 pub fn on_error(mut self, handler: WorkerErrorHandler) -> Self {
502 self.on_error = Some(handler);
503 self
504 }
505
506 pub fn build(self) -> Result<NonBlocking<F>> {
513 if self.thread_name.contains('\0') {
514 return Err(Error::Configuration(
515 "NonBlocking thread name must not contain a NUL byte",
516 ));
517 }
518 let (tx, rx) = mpsc::sync_channel::<Message>(self.capacity);
519 let (pool_tx, pool_rx) = mpsc::sync_channel::<String>(self.capacity.min(MAX_POOLED));
520 let shared = Arc::new(Shared {
521 queued_bytes: AtomicUsize::new(0),
522 dropped: AtomicU64::new(0),
523 write_errors: AtomicU64::new(0),
524 deferred: Mutex::new(None),
525 on_error: self.on_error,
526 });
527 let worker_shared = Arc::clone(&shared);
528 let identity = Arc::as_ptr(&shared).addr();
529 let writer = BufWriter::with_capacity(self.buffer_capacity, self.writer);
530 let worker = std::thread::Builder::new()
531 .name(self.thread_name)
532 .spawn(move || {
533 WORKER_OF.with(|w| w.set(identity));
534 run_worker(writer, &rx, &pool_tx, &worker_shared)
535 })?;
536 Ok(NonBlocking {
537 format: self.format,
538 overflow: self.overflow,
539 channel: RwLock::new(Channel {
540 sender: Some(tx),
541 worker: Some(worker),
542 }),
543 fallback: Mutex::new(None),
544 pool: Mutex::new(pool_rx),
545 shared,
546 max_queued_bytes: self.max_queued_bytes,
547 })
548 }
549}
550
551impl<F: core::fmt::Debug> core::fmt::Debug for NonBlockingBuilder<F> {
552 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
553 f.debug_struct("NonBlockingBuilder")
554 .field("format", &self.format)
555 .field("capacity", &self.capacity)
556 .field("max_queued_bytes", &self.max_queued_bytes)
557 .field("overflow", &self.overflow)
558 .field("thread_name", &self.thread_name)
559 .finish_non_exhaustive()
560 }
561}
562
563fn report(shared: &Shared, error: io::Error) {
564 let _previous = shared.write_errors.fetch_add(1, Ordering::Relaxed);
565 if let Some(handler) = &shared.on_error {
566 handler(&error);
567 }
568 let mut deferred = lock_recover(&shared.deferred);
569 if deferred.is_none() {
570 *deferred = Some(error);
571 }
572}
573
574fn run_worker(
575 mut writer: BufWriter<Box<dyn Write + Send>>,
576 rx: &Receiver<Message>,
577 pool: &SyncSender<String>,
578 shared: &Shared,
579) -> Box<dyn Write + Send> {
580 let mut dirty = false;
581 loop {
582 let message = match rx.try_recv() {
583 Ok(m) => m,
584 Err(TryRecvError::Empty) => {
585 if dirty {
588 if let Err(e) = writer.flush() {
589 report(shared, e);
590 }
591 }
592 match rx.recv() {
593 Ok(m) => m,
594 Err(_) => break,
595 }
596 }
597 Err(TryRecvError::Disconnected) => break,
598 };
599 match message {
600 Message::Record(mut text) => {
601 if let Err(e) = writer.write_all(text.as_bytes()) {
602 report(shared, e);
603 }
604 let _previous = shared.queued_bytes.fetch_sub(text.len(), Ordering::Relaxed);
605 dirty = true;
606 trim_buffer(&mut text);
607 let _recycled = pool.try_send(text);
609 }
610 Message::Flush(ack) => {
611 dirty = false;
612 let _requester_may_be_gone = ack.send(writer.flush());
613 }
614 Message::FlushRequest => {
615 dirty = false;
616 if let Err(e) = writer.flush() {
617 report(shared, e);
618 }
619 }
620 Message::Shutdown => break,
621 }
622 }
623 if let Err(e) = writer.flush() {
624 report(shared, e);
625 }
626 match writer.into_inner() {
627 Ok(inner) => inner,
628 Err(e) => {
629 let (error, writer) = e.into_parts();
630 report(shared, error);
631 Box::new(writer)
634 }
635 }
636}
637
638#[cfg(all(test, feature = "json"))]
639mod tests {
640 use super::*;
641 use crate::format::JsonFormat;
642 use crate::{Level, Metadata};
643 use std::sync::Barrier;
644
645 #[derive(Clone, Default)]
646 struct Capture(Arc<Mutex<Vec<u8>>>);
647 impl Write for Capture {
648 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
649 self.0.lock().unwrap().extend_from_slice(buf);
650 Ok(buf.len())
651 }
652 fn flush(&mut self) -> io::Result<()> {
653 Ok(())
654 }
655 }
656 impl Capture {
657 fn lines(&self) -> usize {
658 String::from_utf8(self.0.lock().unwrap().clone())
659 .unwrap()
660 .lines()
661 .count()
662 }
663 }
664
665 fn record(msg: &str) -> Record<'_> {
666 Record::new(Metadata::new(Level::Info, "t"), msg, &[])
667 }
668
669 #[test]
670 fn test_non_blocking_flush_waits_for_queued_records() {
671 let out = Capture::default();
672 let sink = NonBlocking::new(out.clone(), JsonFormat::new()).unwrap();
673 for _ in 0..1_000 {
674 sink.write_record(&record("x")).unwrap();
675 }
676 sink.flush().unwrap();
677 assert_eq!(out.lines(), 1_000);
678 }
679
680 #[test]
681 fn test_non_blocking_shutdown_drains_then_writes_synchronously() {
682 let out = Capture::default();
683 let sink = NonBlocking::new(out.clone(), JsonFormat::new()).unwrap();
684 for _ in 0..100 {
685 sink.write_record(&record("queued")).unwrap();
686 }
687 sink.shutdown().unwrap();
688 assert_eq!(out.lines(), 100);
689 sink.write_record(&record("late")).unwrap();
691 sink.flush().unwrap();
692 sink.shutdown().unwrap();
693 assert_eq!(out.lines(), 101);
694 }
695
696 #[test]
697 fn test_non_blocking_drop_newest_counts_drops() {
698 struct Gate(Arc<Barrier>, Capture, bool);
700 impl Write for Gate {
701 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
702 if !self.2 {
703 self.2 = true;
704 let _leader = self.0.wait();
705 }
706 self.1.write(buf)
707 }
708 fn flush(&mut self) -> io::Result<()> {
709 Ok(())
710 }
711 }
712 let gate = Arc::new(Barrier::new(2));
713 let out = Capture::default();
714 let sink = NonBlocking::builder(Gate(gate.clone(), out.clone(), false), JsonFormat::new())
715 .capacity(2)
716 .overflow(Overflow::DropNewest)
717 .buffer_capacity(0)
718 .build()
719 .unwrap();
720 for _ in 0..50 {
721 sink.write_record(&record("burst")).unwrap();
722 }
723 let _leader = gate.wait();
724 sink.flush().unwrap();
725 let written = out.lines() as u64;
726 assert!(sink.dropped() > 0, "the queue must have overflowed");
727 assert_eq!(
728 written + sink.dropped(),
729 50,
730 "every record is written or counted"
731 );
732 }
733
734 #[test]
735 fn test_non_blocking_write_errors_are_counted_and_deferred_to_flush() {
736 struct Failing;
737 impl Write for Failing {
738 fn write(&mut self, _: &[u8]) -> io::Result<usize> {
739 Err(io::Error::other("disk full"))
740 }
741 fn flush(&mut self) -> io::Result<()> {
742 Ok(())
743 }
744 }
745 let seen = Arc::new(AtomicU64::new(0));
746 let seen2 = Arc::clone(&seen);
747 let sink = NonBlocking::builder(Failing, JsonFormat::new())
748 .buffer_capacity(0)
749 .thread_name("log-io-test-writer")
750 .on_error(Arc::new(move |_| {
751 let _n = seen2.fetch_add(1, Ordering::Relaxed);
752 }))
753 .build()
754 .unwrap();
755 sink.write_record(&record("lost")).unwrap();
756 sink.write_record(&record("lost too")).unwrap();
757 let err = sink.flush().unwrap_err();
758 assert!(matches!(err, Error::Io(_)), "{err}");
759 assert_eq!(sink.write_errors(), 2);
760 assert_eq!(seen.load(Ordering::Relaxed), 2);
761 assert!(sink.flush().is_ok(), "the deferred error is reported once");
762 assert!(format!("{sink:?}").contains("write_errors: 2"));
763 }
764
765 #[test]
766 fn test_non_blocking_many_producers_lose_nothing_with_block() {
767 let out = Capture::default();
768 let sink = Arc::new(
769 NonBlocking::builder(out.clone(), JsonFormat::new())
770 .capacity(16)
771 .build()
772 .unwrap(),
773 );
774 std::thread::scope(|s| {
775 for _ in 0..8 {
776 let sink = Arc::clone(&sink);
777 let _handle = s.spawn(move || {
778 for _ in 0..500 {
779 sink.write_record(&record("p")).unwrap();
780 }
781 });
782 }
783 });
784 sink.shutdown().unwrap();
785 assert_eq!(out.lines(), 8 * 500);
786 assert_eq!(sink.dropped(), 0);
787 }
788
789 #[test]
790 fn test_non_blocking_worker_panic_reports_closed() {
791 struct Panics;
792 impl Write for Panics {
793 fn write(&mut self, _: &[u8]) -> io::Result<usize> {
794 panic!("writer exploded");
795 }
796 fn flush(&mut self) -> io::Result<()> {
797 Ok(())
798 }
799 }
800 let sink = NonBlocking::builder(Panics, JsonFormat::new())
801 .buffer_capacity(0)
802 .build()
803 .unwrap();
804 let _queued = sink.write_record(&record("boom"));
805 assert!(matches!(sink.shutdown(), Err(Error::Closed)));
806 assert!(matches!(
807 sink.write_record(&record("after")),
808 Err(Error::Closed)
809 ));
810 let builder = NonBlocking::builder(Vec::<u8>::new(), JsonFormat::new()).capacity(0);
811 assert!(format!("{builder:?}").contains("capacity: 1"));
812 }
813
814 #[test]
815 fn test_non_blocking_builder_rejects_bad_options_without_panicking() {
816 let err = NonBlocking::builder(Vec::<u8>::new(), JsonFormat::new())
818 .thread_name("a\0b")
819 .build()
820 .unwrap_err();
821 assert!(matches!(err, Error::Configuration(_)), "{err}");
822 let builder =
823 NonBlocking::builder(Vec::<u8>::new(), JsonFormat::new()).capacity(usize::MAX / 2);
824 assert!(format!("{builder:?}").contains("capacity: 1048576"));
825 }
826
827 struct Stall(Option<mpsc::Receiver<()>>, Capture);
829 impl Write for Stall {
830 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
831 if let Some(gate) = self.0.take() {
832 let _released = gate.recv();
833 }
834 self.1.write(buf)
835 }
836 fn flush(&mut self) -> io::Result<()> {
837 Ok(())
838 }
839 }
840
841 #[test]
845 fn test_non_blocking_queue_is_bounded_in_bytes() {
846 let (release, gate) = mpsc::channel();
847 let out = Capture::default();
848 let sink = NonBlocking::builder(Stall(Some(gate), out.clone()), JsonFormat::new())
849 .capacity(1024)
850 .overflow(Overflow::DropNewest)
851 .buffer_capacity(0)
852 .build()
853 .unwrap();
854 let big = "x".repeat(256 * 1024);
855 for _ in 0..256 {
856 sink.write_record(&record(&big)).unwrap();
857 }
858 let queued = sink.queued_bytes();
859 let limit = DEFAULT_MAX_QUEUED_BYTES + big.len() + 1024;
860 release.send(()).unwrap();
861 sink.flush().unwrap();
862 assert!(queued <= limit, "{queued} bytes queued");
863 assert!(sink.dropped() > 0, "the byte budget must have been hit");
864 assert_eq!(out.lines() as u64 + sink.dropped(), 256);
865 assert_eq!(sink.queued_bytes(), 0);
866 }
867
868 #[test]
869 fn test_non_blocking_block_waits_for_the_byte_budget_and_loses_nothing() {
870 let out = Capture::default();
871 let sink = NonBlocking::builder(out.clone(), JsonFormat::new())
872 .max_queued_bytes(4 * 1024)
873 .build()
874 .unwrap();
875 let text = "y".repeat(1024);
876 for _ in 0..200 {
877 sink.write_record(&record(&text)).unwrap();
878 }
879 sink.flush().unwrap();
880 assert_eq!(out.lines(), 200);
881 assert_eq!(sink.dropped(), 0);
882 assert_eq!(sink.queued_bytes(), 0);
883 }
884
885 #[test]
886 fn test_non_blocking_drop_newest_flush_level_record_does_not_wait() {
887 let (release, gate) = mpsc::channel();
890 let out = Capture::default();
891 let sink = NonBlocking::builder(Stall(Some(gate), out.clone()), JsonFormat::new())
892 .capacity(4)
893 .overflow(Overflow::DropNewest)
894 .buffer_capacity(0)
895 .build()
896 .unwrap();
897 let logger = crate::Logger::builder()
898 .level(Level::Trace)
899 .no_timestamps()
900 .with_sink(sink)
901 .build();
902 for _ in 0..20 {
903 logger.log(Level::Info, "queued", &[]);
904 }
905 let (done, returned) = mpsc::channel();
906 let error_logger = logger.clone();
907 let handle = std::thread::spawn(move || {
908 error_logger.log(Level::Error, "error", &[]);
909 let _sent = done.send(());
910 });
911 let waited = returned.recv_timeout(std::time::Duration::from_secs(10));
912 release.send(()).unwrap();
913 handle.join().unwrap();
914 assert!(
915 waited.is_ok(),
916 "a flush-level record must not wait for the writer under DropNewest"
917 );
918 logger.flush().unwrap();
920 assert!(out.lines() >= 1);
921 }
922
923 #[test]
924 fn test_non_blocking_worker_logging_into_its_own_sink_never_deadlocks() {
925 struct Echo {
928 logger: Arc<std::sync::OnceLock<crate::Logger>>,
929 echoes: usize,
930 flush_from_worker: Arc<Mutex<Option<bool>>>,
931 }
932 impl Write for Echo {
933 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
934 if let Some(logger) = self.logger.get() {
935 if self.echoes < 50 {
936 self.echoes += 1;
937 logger.log(Level::Error, "echo from the writer", &[]);
938 let closed = matches!(logger.flush(), Err(Error::Closed));
939 *self.flush_from_worker.lock().unwrap() = Some(closed);
940 }
941 }
942 Ok(buf.len())
943 }
944 fn flush(&mut self) -> io::Result<()> {
945 Ok(())
946 }
947 }
948 let cell = Arc::new(std::sync::OnceLock::new());
949 let flush_from_worker = Arc::new(Mutex::new(None));
950 let sink = NonBlocking::builder(
951 Echo {
952 logger: Arc::clone(&cell),
953 echoes: 0,
954 flush_from_worker: Arc::clone(&flush_from_worker),
955 },
956 JsonFormat::new(),
957 )
958 .capacity(1)
959 .buffer_capacity(0)
960 .build()
961 .unwrap();
962 let logger = crate::Logger::builder()
963 .level(Level::Trace)
964 .no_timestamps()
965 .with_sink(sink)
966 .build();
967 let _set = cell.set(logger.clone());
968 let (done, finished) = mpsc::channel();
969 let _detached = std::thread::spawn(move || {
970 for _ in 0..200 {
971 logger.log(Level::Info, "from a producer", &[]);
972 }
973 let shut = logger.shutdown();
974 let _sent = done.send(shut.is_ok());
975 });
976 let outcome = finished.recv_timeout(std::time::Duration::from_secs(30));
977 assert_eq!(outcome, Ok(true), "the worker waited for itself");
978 assert_eq!(
979 *flush_from_worker.lock().unwrap(),
980 Some(true),
981 "a flush from the worker thread reports `Closed` instead of waiting"
982 );
983 drop(cell);
986 }
987
988 #[test]
989 fn test_non_blocking_drop_without_logger_drains_the_queue() {
990 let out = Capture::default();
991 let sink = NonBlocking::new(out.clone(), JsonFormat::new()).unwrap();
992 for _ in 0..100 {
993 sink.write_record(&record("x")).unwrap();
994 }
995 drop(sink);
996 assert_eq!(out.lines(), 100);
997 }
998}