use crate::engine::LogEvent;
use crossbeam_queue::ArrayQueue;
use std::cell::Cell;
use std::sync::atomic::{AtomicUsize, Ordering};
#[cfg(feature = "fast-queue")]
pub(crate) const SHARD_COUNT: usize = 8;
#[cfg(not(feature = "fast-queue"))]
pub(crate) const SHARD_COUNT: usize = 1;
const _: () = assert!(SHARD_COUNT > 0, "SHARD_COUNT must be > 0");
static NEXT_SHARD: AtomicUsize = AtomicUsize::new(0);
thread_local! {
static SHARD_INDEX: Cell<Option<usize>> = const { Cell::new(None) };
}
fn thread_shard() -> usize {
SHARD_INDEX.with(|slot| {
slot.get().unwrap_or_else(|| {
#[allow(clippy::modulo_one)]
let idx = NEXT_SHARD.fetch_add(1, Ordering::Relaxed)
% SHARD_COUNT;
slot.set(Some(idx));
idx
})
})
}
#[derive(Debug)]
pub(crate) struct ShardedQueue {
shards: Box<[ArrayQueue<LogEvent>]>,
}
impl ShardedQueue {
pub(crate) fn new(total_capacity: usize) -> Self {
#[allow(clippy::modulo_one)]
let (base, remainder) = (
total_capacity / SHARD_COUNT,
total_capacity % SHARD_COUNT,
);
let shards = (0..SHARD_COUNT)
.map(|i| {
let cap = base + usize::from(i < remainder);
ArrayQueue::new(cap.max(1))
})
.collect::<Box<[_]>>();
Self { shards }
}
pub(crate) fn push(&self, event: LogEvent) -> Result<(), LogEvent> {
self.shards[thread_shard()].push(event)
}
pub(crate) fn pop_local(&self) -> Option<LogEvent> {
self.shards[thread_shard()].pop()
}
#[cfg_attr(miri, allow(dead_code))]
pub(crate) fn pop(&self) -> Option<LogEvent> {
for shard in &self.shards {
if let Some(event) = shard.pop() {
return Some(event);
}
}
None
}
#[cfg_attr(miri, allow(dead_code))]
pub(crate) fn is_empty(&self) -> bool {
self.shards.iter().all(ArrayQueue::is_empty)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::LogLevel;
use crate::log::Log;
fn make_event(level: LogLevel) -> LogEvent {
LogEvent {
level,
level_num: level.to_numeric(),
log: Log::info("test"),
}
}
#[test]
fn empty_reports_no_events() {
let q = ShardedQueue::new(16);
assert!(q.is_empty());
}
#[test]
fn push_then_pop_round_trips() {
let q = ShardedQueue::new(8);
q.push(make_event(LogLevel::INFO)).unwrap();
assert!(!q.is_empty());
let e = q.pop().unwrap();
assert_eq!(e.level, LogLevel::INFO);
assert!(q.is_empty());
}
#[test]
#[cfg_attr(miri, ignore)] fn concurrent_producers_share_shards() {
use std::sync::Arc;
use std::thread;
let q = Arc::new(ShardedQueue::new(1024));
let mut handles = Vec::new();
for _ in 0..4 {
let q = q.clone();
handles.push(thread::spawn(move || {
for _ in 0..64 {
let _ = q.push(make_event(LogLevel::INFO));
}
}));
}
for h in handles {
h.join().unwrap();
}
let mut drained = 0;
while q.pop().is_some() {
drained += 1;
}
assert_eq!(drained, 4 * 64);
}
}