use std::collections::HashMap;
use std::fmt;
use std::io::{BufReader, BufWriter, Write};
use std::mem;
use std::net::{Shutdown, SocketAddr, TcpStream};
use std::panic::{self, AssertUnwindSafe};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
use std::time::Duration;
use flume::{Receiver, Selector, Sender, TryRecvError};
use tephra::log::set::PositionRange;
use tephra::query::Query;
use tephra::read::WaitOutcome;
use tephra::writer::{AppendError, WriteHandle};
use tephra::{Event, Position};
use tephra_proto::tephra as pb;
use tephra_proto::{FrameError, read_frame, write_frame};
use crate::convert;
use crate::stats;
use crate::{ServerConfig, SharedStats};
type AppendReply = (u64, Result<PositionRange, AppendError>);
pub(crate) fn serve_connection(
stream: TcpStream,
handle: WriteHandle,
config: ServerConfig,
running: Arc<AtomicBool>,
read_pool: Sender<ReadJob>,
stats: &Arc<SharedStats>,
) {
let peer = stream.peer_addr().ok();
if let Err(err) = stream.set_nodelay(true) {
tracing::warn!(?peer, %err, "failed to set TCP_NODELAY");
}
let read_half = match stream.try_clone() {
Ok(clone) => clone,
Err(err) => {
tracing::warn!(?peer, %err, "failed to clone connection stream");
return;
}
};
let write_half = match stream.try_clone() {
Ok(clone) => clone,
Err(err) => {
tracing::warn!(?peer, %err, "failed to clone connection stream");
return;
}
};
let alive = Arc::new(AtomicBool::new(true));
let control_depth = config.max_inflight_requests_per_conn.max(CONTROL_QUEUE_MIN);
let (control_tx, control_rx) = flume::bounded::<pb::Response>(control_depth);
let (bulk_tx, bulk_rx) = flume::bounded::<pb::Response>(config.frame_queue_depth);
let (reply_tx, reply_rx) = flume::unbounded::<AppendReply>();
let cancels: Arc<Mutex<HashMap<u64, Arc<AtomicBool>>>> = Arc::default();
let append_inflight = Arc::new(Semaphore::new(config.max_inflight_requests_per_conn));
let read_inflight = Arc::new(Semaphore::new(config.max_inflight_requests_per_conn));
let read_overflow = Arc::new(Semaphore::new(config.max_inflight_requests_per_conn));
let subscriptions = Arc::new(Semaphore::new(config.max_concurrent_subscriptions));
let workers = WaitGroup::default();
let writer_thread = {
let alive = Arc::clone(&alive);
let shutdown = stream.try_clone().ok();
let max = config.max_frame_len;
thread::Builder::new()
.name("tephra-conn-writer".to_string())
.spawn(move || writer_loop(write_half, control_rx, bulk_rx, shutdown, alive, max, peer))
.expect("spawn connection writer thread")
};
let pump_thread = {
let control_tx = control_tx.clone();
let append_inflight = Arc::clone(&append_inflight);
thread::Builder::new()
.name("tephra-conn-pump".to_string())
.spawn(move || pump_loop(reply_rx, control_tx, append_inflight))
.expect("spawn connection pump thread")
};
let conn = ConnCtx {
handle,
config,
running,
alive: Arc::clone(&alive),
control_tx: control_tx.clone(),
bulk_tx: bulk_tx.clone(),
cancels: Arc::clone(&cancels),
workers: workers.clone(),
append_inflight,
read_inflight,
read_overflow,
subscriptions,
stats: Arc::clone(stats),
read_pool,
};
let mut reader = BufReader::new(read_half);
loop {
let request = match read_frame::<pb::Request, _>(&mut reader, config.max_frame_len) {
Ok(Some(request)) => request,
Ok(None) => {
tracing::debug!(?peer, "connection closed by peer at a frame boundary");
break;
}
Err(err) => {
let error = match &err {
FrameError::TooLarge { .. } => Some(convert::too_large(err.to_string())),
FrameError::Parse(_) => Some(convert::bad_request(err.to_string())),
FrameError::Io(_) | FrameError::Serialize(_) => None,
};
if let Some(error) = error {
let _ = control_tx.try_send(make_response(0, ResponseKind::Error(error)));
}
if alive.load(Ordering::Acquire) {
tracing::warn!(?peer, %err, "closing connection: reader failed");
} else {
tracing::debug!(?peer, %err, "reader ended after the connection was closed");
}
break;
}
};
dispatch(&request, &conn, &reply_tx);
}
alive.store(false, Ordering::Release);
drop(conn);
drop(control_tx);
drop(bulk_tx);
drop(reply_tx);
workers.wait();
let _ = pump_thread.join();
let _ = writer_thread.join();
let _ = stream.shutdown(Shutdown::Both);
}
#[derive(Clone)]
struct ConnCtx {
handle: WriteHandle,
config: ServerConfig,
running: Arc<AtomicBool>,
alive: Arc<AtomicBool>,
control_tx: Sender<pb::Response>,
bulk_tx: Sender<pb::Response>,
cancels: Arc<Mutex<HashMap<u64, Arc<AtomicBool>>>>,
workers: WaitGroup,
append_inflight: Arc<Semaphore>,
read_inflight: Arc<Semaphore>,
read_overflow: Arc<Semaphore>,
subscriptions: Arc<Semaphore>,
stats: Arc<SharedStats>,
read_pool: Sender<ReadJob>,
}
impl ConnCtx {
fn should_continue(&self, cancel: &AtomicBool) -> bool {
self.running.load(Ordering::Acquire)
&& self.alive.load(Ordering::Acquire)
&& !cancel.load(Ordering::Acquire)
}
fn send_control(&self, response: pb::Response) {
let _ = self.control_tx.send(response);
}
fn send_error(&self, request_id: u64, message: impl fmt::Display) {
self.send_control(make_response(
request_id,
ResponseKind::Error(convert::bad_request(message)),
));
}
fn send_bulk(&self, response: pb::Response) -> bool {
self.bulk_tx.send(response).is_ok()
}
}
fn dispatch(request: &pb::Request, conn: &ConnCtx, reply_tx: &Sender<AppendReply>) {
let request_id = request.request_id();
match request.kind() {
pb::request::KindOneof::Append(append) => handle_append(request_id, append, conn, reply_tx),
pb::request::KindOneof::Read(read) => spawn_read(request_id, read, conn),
pb::request::KindOneof::Subscribe(subscribe) => {
spawn_subscribe(request_id, subscribe, conn)
}
pb::request::KindOneof::Cancel(cancel) => {
if let Some(flag) = conn.cancels.lock().unwrap().get(&cancel.target()) {
flag.store(true, Ordering::Release);
}
}
pb::request::KindOneof::Stats(_) => handle_stats(request_id, conn),
_ => conn.send_error(
request_id,
"request has no append, read, subscribe, or cancel set",
),
}
}
fn handle_stats(request_id: u64, conn: &ConnCtx) {
let snap = stats::gather(&conn.stats, &conn.handle);
let mut stats = pb::StatsResponse::new();
stats.set_event_count(snap.event_count);
stats.set_segment_count(snap.segment_count);
stats.set_disk_bytes(snap.disk_bytes);
stats.set_uptime_seconds(snap.uptime_seconds);
stats.set_active_connections(snap.active_connections);
stats.set_active_subscriptions(snap.active_subscriptions);
stats.set_version(snap.version.to_string());
conn.send_control(make_response(request_id, ResponseKind::Stats(stats)));
}
fn handle_append(
request_id: u64,
append: pb::AppendRequestView<'_>,
conn: &ConnCtx,
reply_tx: &Sender<AppendReply>,
) {
let events = match convert::events_from_proto(append) {
Ok(events) => events,
Err(err) => {
conn.send_error(request_id, err);
return;
}
};
let condition = match append.condition_opt() {
Some(condition) => match convert::condition_from_proto(condition) {
Ok(condition) => Some(condition),
Err(err) => {
conn.send_error(request_id, err);
return;
}
},
None => None,
};
conn.append_inflight.acquire();
if let Err(err) = conn
.handle
.append_submit(events, condition, request_id, reply_tx.clone())
{
conn.append_inflight.release();
conn.send_control(make_response(
request_id,
ResponseKind::Error(convert::append_error_to_proto(&err)),
));
}
}
fn spawn_read(request_id: u64, read: pb::ReadRequestView<'_>, conn: &ConnCtx) {
let query = match convert::query_from_proto(read.query()) {
Ok(query) => query,
Err(err) => {
conn.send_error(request_id, err);
return;
}
};
let reverse = read.reverse();
let cursor = Position::new(read.after());
let limit = read.limit_opt();
let admission = match conn.read_inflight.try_acquire_guard() {
Some(permit) => Admission::Permitted(permit),
None => match conn.read_overflow.try_acquire_guard() {
Some(slot) => Admission::Overflow { slot },
None => {
conn.send_error(request_id, "too many in-flight reads on this connection");
return;
}
},
};
let cancel = register_cancel(conn, request_id);
conn.workers.add();
let cleanup = WorkerCleanup {
cancels: Arc::clone(&conn.cancels),
sem: None,
workers: conn.workers.clone(),
request_id,
};
let job = ReadJob {
request_id,
query,
cursor,
reverse,
limit,
conn: conn.clone(),
cancel,
cleanup,
admission,
};
if conn.read_pool.send(job).is_err() {
tracing::debug!(request_id, "read pool closed; dropping read");
}
}
fn spawn_subscribe(request_id: u64, subscribe: pb::SubscribeRequestView<'_>, conn: &ConnCtx) {
let query = match convert::query_from_proto(subscribe.query()) {
Ok(query) => query,
Err(err) => {
conn.send_error(request_id, err);
return;
}
};
let after = Position::new(subscribe.after());
if !conn.subscriptions.try_acquire() {
conn.send_error(
request_id,
"too many concurrent subscriptions on this connection",
);
return;
}
let cancel = register_cancel(conn, request_id);
conn.workers.add();
let cleanup = WorkerCleanup {
cancels: Arc::clone(&conn.cancels),
sem: Some(Arc::clone(&conn.subscriptions)),
workers: conn.workers.clone(),
request_id,
};
let gauge = SubGauge::new(Arc::clone(&conn.stats));
let conn_owned = conn.clone();
if let Err(err) = thread::Builder::new()
.name("tephra-conn-subscribe".to_string())
.spawn(move || {
let _cleanup = cleanup;
let _gauge = gauge;
run_subscribe(request_id, query, after, &conn_owned, &cancel);
})
{
tracing::warn!(%err, "failed to spawn subscribe worker");
conn.send_error(request_id, "server could not start the subscription");
}
}
fn register_cancel(conn: &ConnCtx, request_id: u64) -> Arc<AtomicBool> {
let cancel = Arc::new(AtomicBool::new(false));
conn.cancels
.lock()
.unwrap()
.insert(request_id, Arc::clone(&cancel));
cancel
}
fn run_read(
request_id: u64,
query: &Query,
cursor: Position,
reverse: bool,
limit: Option<u64>,
conn: &ConnCtx,
cancel: &AtomicBool,
) {
let mut reads = if reverse {
conn.handle.read_back(query, cursor, limit)
} else {
conn.handle.read(query, cursor, limit)
};
let watermark = reads.watermark();
let mut batch = pb::ReadEvents::new();
let mut batch_bytes = 0usize;
while let Some(item) = reads.next() {
if !conn.should_continue(cancel) {
return;
}
let sequenced = match item {
Ok(sequenced) => sequenced,
Err(err) => {
conn.send_bulk(make_response(
request_id,
ResponseKind::Error(convert::internal_error(err)),
));
return;
}
};
batch_bytes += sequenced.event.as_bytes().len();
batch.events_mut().push(convert::sequenced_to_proto(
sequenced.position,
sequenced.event,
));
if batch.events().len() >= conn.config.read_batch_events
|| batch_bytes >= conn.config.read_batch_bytes
{
let full = mem::replace(&mut batch, pb::ReadEvents::new());
if !conn.send_bulk(make_response(request_id, ResponseKind::ReadEvents(full))) {
return;
}
batch_bytes = 0;
}
}
if !batch.events().is_empty()
&& !conn.send_bulk(make_response(request_id, ResponseKind::ReadEvents(batch)))
{
return;
}
let mut end = pb::ReadEnd::new();
end.set_watermark(watermark.get());
conn.send_bulk(make_response(request_id, ResponseKind::ReadEnd(end)));
}
fn run_subscribe(
request_id: u64,
query: Query,
after: Position,
conn: &ConnCtx,
cancel: &AtomicBool,
) {
let mut sub = conn.handle.subscribe(query, after);
let mut announced = false;
loop {
if !conn.should_continue(cancel) {
return;
}
let batch = match sub.poll_batch() {
Ok(batch) => batch,
Err(err) => {
conn.send_bulk(make_response(
request_id,
ResponseKind::Error(convert::internal_error(err)),
));
return;
}
};
if batch.is_empty() {
if !announced {
let mut caught_up = pb::SubscribeCaughtUp::new();
caught_up.set_watermark(sub.position().get());
if !conn.send_bulk(make_response(request_id, ResponseKind::CaughtUp(caught_up))) {
return;
}
announced = true;
}
match sub.wait_timeout(conn.config.subscribe_wait_tick) {
WaitOutcome::Advanced | WaitOutcome::TimedOut => {}
WaitOutcome::Closed => return,
}
} else {
if send_event_batch(request_id, &batch, conn).is_err() {
return;
}
announced = false;
}
}
}
fn send_event_batch(
request_id: u64,
events: &[(Position, Event)],
conn: &ConnCtx,
) -> Result<(), ()> {
let mut batch = pb::ReadEvents::new();
let mut batch_bytes = 0usize;
for (position, event) in events {
batch_bytes += event.as_bytes().len();
batch
.events_mut()
.push(convert::sequenced_to_proto(*position, event.as_ref()));
if batch.events().len() >= conn.config.read_batch_events
|| batch_bytes >= conn.config.read_batch_bytes
{
let full = mem::replace(&mut batch, pb::ReadEvents::new());
if !conn.send_bulk(make_response(request_id, ResponseKind::ReadEvents(full))) {
return Err(());
}
batch_bytes = 0;
}
}
if !batch.events().is_empty()
&& !conn.send_bulk(make_response(request_id, ResponseKind::ReadEvents(batch)))
{
return Err(());
}
Ok(())
}
const CONTROL_QUEUE_MIN: usize = 64;
const MAX_CONTROL_RUN: usize = 64;
const CANCEL_POLL_INTERVAL: Duration = Duration::from_millis(25);
fn writer_loop(
write_half: TcpStream,
control_rx: Receiver<pb::Response>,
bulk_rx: Receiver<pb::Response>,
shutdown: Option<TcpStream>,
alive: Arc<AtomicBool>,
max_frame_len: u32,
peer: Option<SocketAddr>,
) {
let mut writer = BufWriter::new(write_half);
let outcome = drive_writer(&mut writer, &control_rx, &bulk_rx, max_frame_len);
if let Err(err) = outcome {
if alive.load(Ordering::Acquire) {
tracing::warn!(?peer, %err, "closing connection: writer failed");
} else {
tracing::debug!(?peer, %err, "writer ended after the connection was closed");
}
}
alive.store(false, Ordering::Release);
if let Some(stream) = shutdown {
let _ = stream.shutdown(Shutdown::Both);
}
}
fn drive_writer<W: Write>(
writer: &mut W,
control_rx: &Receiver<pb::Response>,
bulk_rx: &Receiver<pb::Response>,
max_frame_len: u32,
) -> Result<(), FrameError> {
loop {
let mut control_written = 0;
let mut wrote_control = false;
loop {
if control_written >= MAX_CONTROL_RUN && !bulk_rx.is_empty() {
break;
}
match control_rx.try_recv() {
Ok(response) => {
write_frame(writer, &response, max_frame_len)?;
wrote_control = true;
control_written += 1;
}
Err(_) => break,
}
}
if wrote_control {
writer.flush().map_err(FrameError::Io)?;
}
match bulk_rx.try_recv() {
Ok(response) => {
write_frame(writer, &response, max_frame_len)?;
if bulk_rx.is_empty() && control_rx.is_empty() {
writer.flush().map_err(FrameError::Io)?;
}
continue;
}
Err(TryRecvError::Empty) | Err(TryRecvError::Disconnected) => {}
}
if wrote_control {
continue;
}
writer.flush().map_err(FrameError::Io)?;
let next = match (!control_rx.is_disconnected(), !bulk_rx.is_disconnected()) {
(true, true) => Selector::new()
.recv(control_rx, |r| r.ok())
.recv(bulk_rx, |r| r.ok())
.wait(),
(true, false) => control_rx.recv().ok(),
(false, true) => bulk_rx.recv().ok(),
(false, false) => return Ok(()),
};
match next {
Some(response) => write_frame(writer, &response, max_frame_len)?,
None => continue,
}
}
}
fn pump_loop(
reply_rx: Receiver<AppendReply>,
control_tx: Sender<pb::Response>,
inflight: Arc<Semaphore>,
) {
while let Ok((request_id, result)) = reply_rx.recv() {
let response = match result {
Ok(range) => {
let mut ok = pb::AppendResponse::new();
ok.set_first(range.first.get());
ok.set_last(range.last.get());
make_response(request_id, ResponseKind::Append(ok))
}
Err(err) => make_response(
request_id,
ResponseKind::Error(convert::append_error_to_proto(&err)),
),
};
let sent = control_tx.send(response);
inflight.release();
if sent.is_err() {
break;
}
}
}
enum ResponseKind {
Append(pb::AppendResponse),
ReadEvents(pb::ReadEvents),
ReadEnd(pb::ReadEnd),
CaughtUp(pb::SubscribeCaughtUp),
Stats(pb::StatsResponse),
Error(pb::ErrorResponse),
}
fn make_response(request_id: u64, kind: ResponseKind) -> pb::Response {
let mut response = pb::Response::new();
response.set_request_id(request_id);
match kind {
ResponseKind::Append(append) => response.set_append(append),
ResponseKind::ReadEvents(events) => response.set_read_events(events),
ResponseKind::ReadEnd(end) => response.set_read_end(end),
ResponseKind::CaughtUp(caught_up) => response.set_caught_up(caught_up),
ResponseKind::Stats(stats) => response.set_stats(stats),
ResponseKind::Error(error) => response.set_error(error),
}
response
}
pub(crate) struct ReadJob {
request_id: u64,
query: Query,
cursor: Position,
reverse: bool,
limit: Option<u64>,
conn: ConnCtx,
cancel: Arc<AtomicBool>,
cleanup: WorkerCleanup,
admission: Admission,
}
enum Admission {
Permitted(Permit),
Overflow { slot: Permit },
}
impl ReadJob {
fn run(self) {
let ReadJob {
request_id,
query,
cursor,
reverse,
limit,
conn,
cancel,
cleanup,
admission,
} = self;
let _permit = match admission {
Admission::Permitted(permit) => permit,
Admission::Overflow { slot } => {
match conn.read_inflight.acquire_guard_or_cancel(&cancel) {
Some(permit) => {
drop(slot);
permit
}
None => return,
}
}
};
if cancel.load(Ordering::Acquire) {
return;
}
run_read(request_id, &query, cursor, reverse, limit, &conn, &cancel);
drop(cleanup);
}
}
pub(crate) struct ReadPool {
tx: Sender<ReadJob>,
workers: Vec<thread::JoinHandle<()>>,
}
impl ReadPool {
pub(crate) fn new(size: usize) -> ReadPool {
let size = size.max(1);
let (tx, rx) = flume::unbounded::<ReadJob>();
let mut workers = Vec::with_capacity(size);
for _ in 0..size {
let rx = rx.clone();
let worker = thread::Builder::new()
.name("tephra-read-worker".to_string())
.spawn(move || {
while let Ok(job) = rx.recv() {
let _ = panic::catch_unwind(AssertUnwindSafe(move || job.run()));
}
})
.expect("spawn read worker thread");
workers.push(worker);
}
ReadPool { tx, workers }
}
pub(crate) fn sender(&self) -> Sender<ReadJob> {
self.tx.clone()
}
pub(crate) fn shutdown(self) {
drop(self.tx);
for worker in self.workers {
let _ = worker.join();
}
}
}
struct WorkerCleanup {
cancels: Arc<Mutex<HashMap<u64, Arc<AtomicBool>>>>,
sem: Option<Arc<Semaphore>>,
workers: WaitGroup,
request_id: u64,
}
impl Drop for WorkerCleanup {
fn drop(&mut self) {
self.cancels.lock().unwrap().remove(&self.request_id);
if let Some(sem) = &self.sem {
sem.release();
}
self.workers.done();
}
}
struct SubGauge(Arc<SharedStats>);
impl SubGauge {
fn new(stats: Arc<SharedStats>) -> SubGauge {
stats.active_subscriptions.fetch_add(1, Ordering::Relaxed);
SubGauge(stats)
}
}
impl Drop for SubGauge {
fn drop(&mut self) {
self.0.active_subscriptions.fetch_sub(1, Ordering::Relaxed);
}
}
struct Semaphore {
permits: Mutex<usize>,
available: Condvar,
}
impl Semaphore {
fn new(permits: usize) -> Semaphore {
Semaphore {
permits: Mutex::new(permits.max(1)),
available: Condvar::new(),
}
}
fn acquire(&self) {
let mut permits = self.permits.lock().unwrap();
while *permits == 0 {
permits = self.available.wait(permits).unwrap();
}
*permits -= 1;
}
fn try_acquire(&self) -> bool {
let mut permits = self.permits.lock().unwrap();
if *permits == 0 {
false
} else {
*permits -= 1;
true
}
}
fn release(&self) {
*self.permits.lock().unwrap() += 1;
self.available.notify_one();
}
fn try_acquire_guard(self: &Arc<Self>) -> Option<Permit> {
self.try_acquire().then(|| Permit(Arc::clone(self)))
}
fn acquire_guard_or_cancel(self: &Arc<Self>, cancel: &AtomicBool) -> Option<Permit> {
let mut permits = self.permits.lock().unwrap();
loop {
if cancel.load(Ordering::Acquire) {
if *permits > 0 {
self.available.notify_one();
}
return None;
}
if *permits > 0 {
*permits -= 1;
return Some(Permit(Arc::clone(self)));
}
let (guard, _timeout) = self
.available
.wait_timeout(permits, CANCEL_POLL_INTERVAL)
.unwrap();
permits = guard;
}
}
}
struct Permit(Arc<Semaphore>);
impl Drop for Permit {
fn drop(&mut self) {
self.0.release();
}
}
#[derive(Clone, Default)]
struct WaitGroup {
inner: Arc<(Mutex<usize>, Condvar)>,
}
impl WaitGroup {
fn add(&self) {
*self.inner.0.lock().unwrap() += 1;
}
fn done(&self) {
let mut count = self.inner.0.lock().unwrap();
*count -= 1;
if *count == 0 {
self.inner.1.notify_all();
}
}
fn wait(&self) {
let mut count = self.inner.0.lock().unwrap();
while *count > 0 {
count = self.inner.1.wait(count).unwrap();
}
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use super::*;
fn control(id: u64) -> pb::Response {
make_response(id, ResponseKind::Append(pb::AppendResponse::new()))
}
fn bulk(id: u64) -> pb::Response {
make_response(id, ResponseKind::ReadEvents(pb::ReadEvents::new()))
}
fn decode_ids(bytes: &[u8]) -> Vec<u64> {
let mut cursor = Cursor::new(bytes);
let mut ids = Vec::new();
while let Some(resp) = read_frame::<pb::Response, _>(&mut cursor, 1 << 20).unwrap() {
ids.push(resp.request_id());
}
ids
}
fn run(control_frames: &[u64], bulk_frames: &[u64]) -> Vec<u64> {
let (control_tx, control_rx) = flume::bounded::<pb::Response>(control_frames.len().max(1));
let (bulk_tx, bulk_rx) = flume::bounded::<pb::Response>(bulk_frames.len().max(1));
for id in bulk_frames {
bulk_tx.send(bulk(*id)).unwrap();
}
for id in control_frames {
control_tx.send(control(1000 + *id)).unwrap();
}
drop(control_tx);
drop(bulk_tx);
let mut out = Vec::new();
drive_writer(&mut out, &control_rx, &bulk_rx, 1 << 20).unwrap();
decode_ids(&out)
}
#[test]
fn control_frames_are_written_before_queued_bulk() {
let ids = run(&[0, 1, 2], &[0, 1, 2]);
assert_eq!(ids.len(), 6);
let first_bulk = ids.iter().position(|id| *id < 1000).unwrap();
assert_eq!(first_bulk, 3, "all queued control drains before any bulk");
assert!(ids[first_bulk..].iter().all(|id| *id < 1000));
}
#[test]
fn a_sustained_control_stream_cannot_starve_bulk() {
let control_frames: Vec<u64> = vec![0; MAX_CONTROL_RUN * 3];
let ids = run(&control_frames, &[0, 1]);
let first_bulk = ids
.iter()
.position(|id| *id < 1000)
.expect("a bulk frame must be written");
assert!(
first_bulk <= MAX_CONTROL_RUN,
"bulk starved: first bulk at {first_bulk}, cap {MAX_CONTROL_RUN}",
);
}
#[test]
fn a_cancelled_overflow_acquire_gives_up_without_a_permit() {
let sem = Arc::new(Semaphore::new(1));
let _held = sem.try_acquire_guard().expect("the sole permit");
let cancel = Arc::new(AtomicBool::new(false));
let waiter = {
let sem = Arc::clone(&sem);
let cancel = Arc::clone(&cancel);
thread::spawn(move || {
let started = std::time::Instant::now();
let gave_up = sem.acquire_guard_or_cancel(&cancel).is_none();
(gave_up, started.elapsed())
})
};
thread::sleep(Duration::from_millis(10));
cancel.store(true, Ordering::Release);
let (gave_up, waited) = waiter.join().unwrap();
assert!(gave_up, "a cancelled waiter returns None, not a permit");
assert!(
waited < CANCEL_POLL_INTERVAL * 8,
"cancel took {waited:?}, expected within a few poll intervals",
);
}
#[test]
fn an_overflow_acquire_takes_a_released_permit() {
let sem = Arc::new(Semaphore::new(1));
let held = sem.try_acquire_guard().expect("the sole permit");
let cancel = Arc::new(AtomicBool::new(false));
let waiter = {
let sem = Arc::clone(&sem);
let cancel = Arc::clone(&cancel);
thread::spawn(move || sem.acquire_guard_or_cancel(&cancel).is_some())
};
thread::sleep(Duration::from_millis(10));
drop(held);
assert!(waiter.join().unwrap(), "a waiter takes the released permit",);
}
}