use std::collections::BTreeMap;
use std::task::{Poll, ready};
use crate::{Datagram, Error, Result, frame, group, track};
use super::subscription::{Subscription, min_some};
#[derive(Clone)]
struct Segment {
id: u64,
start: Option<u64>,
end: Option<u64>,
track: track::Consumer,
}
impl Segment {
fn produced(&self) -> Option<u64> {
let latest = self.track.latest()?;
if let Some(start) = self.start
&& latest < start
{
return None;
}
Some(match self.end {
Some(end) => latest.min(end),
None => latest,
})
}
}
fn slice(prefs: &Subscription, start: Option<u64>, end: Option<u64>) -> Subscription {
let mut sub = prefs.clone();
sub.group_start = match (prefs.group_start, start) {
(Some(a), Some(b)) => Some(a.max(b)),
(Some(a), None) => Some(a),
(None, _) => None,
};
sub.group_end = min_some(prefs.group_end, end);
sub
}
const MAX_SEGMENTS: usize = 3;
struct ResumeState {
segments: Vec<Segment>,
pruned: Option<u64>,
epoch: u64,
finished: bool,
abort: Option<Error>,
}
impl Default for ResumeState {
fn default() -> Self {
Self {
segments: Vec::new(),
pruned: None,
epoch: 1,
finished: false,
abort: None,
}
}
}
struct Snapshot {
epoch: u64,
finished: bool,
abort: Option<Error>,
segments: Vec<Segment>,
}
impl ResumeState {
fn snapshot(&self) -> Snapshot {
Snapshot {
epoch: self.epoch,
finished: self.finished,
abort: self.abort.clone(),
segments: self.segments.clone(),
}
}
fn latest(&self) -> Option<u64> {
self.segments
.iter()
.filter_map(Segment::produced)
.chain(self.pruned)
.max()
}
fn switch(&mut self, track: track::Consumer, start: Option<u64>) -> Result<()> {
if !self.segments.is_empty() {
let Some(start) = start else {
return Err(crate::coding::BoundsExceeded.into());
};
while let Some(prev) = self.segments.last() {
let prev_start = prev.start.unwrap_or(0);
if start > prev_start {
break;
}
if prev.produced().is_some() {
return Err(crate::coding::BoundsExceeded.into());
}
self.segments.pop();
}
if let Some(prev) = self.segments.last_mut() {
prev.end = Some(start - 1);
}
}
let id = self.epoch;
self.segments.push(Segment {
id,
start,
end: None,
track,
});
self.epoch += 1;
self.prune();
Ok(())
}
fn prune(&mut self) {
while self.segments.len() > MAX_SEGMENTS {
let front = &self.segments[0];
let Some(end) = front.end else { break };
let owes_more =
front.produced() < Some(end) && front.track.poll_complete(&kio::Waiter::noop()).is_pending();
if owes_more {
break;
}
self.pruned = self.pruned.max(Some(end));
self.segments.remove(0);
}
}
}
#[derive(Clone, Default)]
pub struct Producer {
state: kio::Producer<ResumeState>,
}
impl Producer {
pub fn new() -> Self {
Self::default()
}
#[cfg_attr(not(test), expect(dead_code))]
pub fn switch(
&mut self,
track: impl super::origin_impl::Consume<track::Consumer>,
start: impl Into<Option<u64>>,
) -> Result<()> {
let track = track.consume();
let start = start.into();
let mut state = self.state.write().map_err(|_| Error::Dropped)?;
if state.finished || state.abort.is_some() {
return Err(Error::Closed);
}
state.switch(track, start)
}
pub fn takeover(&mut self, track: impl super::origin_impl::Consume<track::Consumer>) -> Result<()> {
let track = track.consume();
let mut state = self.state.write().map_err(|_| Error::Dropped)?;
if state.finished || state.abort.is_some() {
return Err(Error::Closed);
}
let start = match state.latest() {
Some(latest) => latest.checked_add(1),
None => {
state.segments.clear();
None
}
};
state.switch(track, start)
}
pub(crate) fn release(&mut self) -> Result<()> {
let mut state = self.state.write().map_err(|_| Error::Dropped)?;
if state.finished || state.abort.is_some() {
return Err(Error::Closed);
}
if state.segments.is_empty() {
return Ok(());
}
state.segments.clear();
state.pruned = None;
state.epoch += 1;
Ok(())
}
pub(crate) fn is_spliced(&self) -> bool {
!self.state.read().segments.is_empty()
}
pub fn finish(&mut self) -> Result<()> {
let mut state = self.state.write().map_err(|_| Error::Dropped)?;
if state.finished || state.abort.is_some() {
return Err(Error::Closed);
}
state.finished = true;
state.epoch += 1;
Ok(())
}
pub fn abort(&mut self, err: Error) -> Result<()> {
let mut state = self.state.write().map_err(|_| Error::Dropped)?;
if state.finished || state.abort.is_some() {
return Err(Error::Closed);
}
state.abort = Some(err);
state.epoch += 1;
Ok(())
}
pub(crate) fn is_aborted(&self) -> bool {
self.state.read().abort.is_some()
}
pub(crate) fn latest(&self) -> Option<u64> {
self.state.read().latest()
}
pub fn consume(&self) -> Consumer {
Consumer {
state: self.state.consume(),
}
}
pub fn is_used(&self) -> bool {
self.state.is_used()
}
pub(crate) fn poll_used(&self, waiter: &kio::Waiter) -> Poll<()> {
self.state.poll_used(waiter).map(|_| ())
}
pub(crate) fn poll_unused(&self, waiter: &kio::Waiter) -> Poll<()> {
self.state.poll_unused(waiter).map(|_| ())
}
}
#[derive(Clone)]
pub struct Consumer {
state: kio::Consumer<ResumeState>,
}
impl Consumer {
#[cfg(test)]
pub fn subscribe(&self, subscription: impl Into<Option<Subscription>>) -> Subscriber {
let prefs = kio::Producer::new(subscription.into().unwrap_or_default());
self.subscribe_shared(prefs)
}
pub(crate) fn subscribe_shared(&self, prefs: kio::Producer<Subscription>) -> Subscriber {
let last_prefs = prefs.read().clone();
Subscriber {
state: self.state.clone(),
prefs,
last_prefs,
epoch: 0,
finished: false,
abort: None,
closed: false,
segments: Vec::new(),
next_sequence: 0,
min_sequence: 0,
end_sequence: None,
reading: None,
}
}
pub fn poll_info(&self, waiter: &kio::Waiter) -> Poll<Result<track::Info>> {
let track = match self.state.poll(waiter, |state| {
if state.abort.is_some() || !state.segments.is_empty() {
Poll::Ready(
state
.abort
.clone()
.map_or_else(|| Ok(state.segments[0].track.clone()), Err),
)
} else {
Poll::Pending
}
}) {
Poll::Ready(Ok(res)) => res?,
Poll::Ready(Err(state)) => match (&state.abort, state.segments.first()) {
(Some(err), _) => return Poll::Ready(Err(err.clone())),
(None, Some(segment)) => segment.track.clone(),
(None, None) => return Poll::Ready(Err(Error::Dropped)),
},
Poll::Pending => return Poll::Pending,
};
track.info().poll_ok(waiter)
}
#[cfg(test)]
pub async fn info(&self) -> Result<track::Info> {
kio::wait(|waiter| self.poll_info(waiter)).await
}
pub fn fetch_group(&self, sequence: u64, options: impl Into<Option<group::Fetch>>) -> kio::Pending<Fetching> {
kio::Pending::new(Fetching {
state: self.state.clone(),
sequence,
options: options.into().unwrap_or_default(),
inner: web_async::Lock::new(None),
})
}
pub fn latest(&self) -> Option<u64> {
self.state.read().latest()
}
}
pub struct Fetching {
state: kio::Consumer<ResumeState>,
sequence: u64,
options: group::Fetch,
#[allow(clippy::type_complexity)]
inner: web_async::Lock<Option<(u64, track::Consumer, kio::Pending<track::Fetching>)>>,
}
impl Fetching {
fn poll_latch(&self, waiter: &kio::Waiter, latched: Option<u64>) -> Poll<Result<Option<(u64, track::Consumer)>>> {
let newest = move |segments: &[Segment]| {
segments
.last()
.filter(|segment| latched.is_none_or(|latched| segment.id > latched))
.map(|segment| (segment.id, segment.track.clone()))
};
match self.state.poll(waiter, |s| match (&s.abort, newest(&s.segments)) {
(Some(err), _) => Poll::Ready(Err(err.clone())),
(None, Some(next)) => Poll::Ready(Ok(next)),
(None, None) => Poll::Pending,
}) {
Poll::Ready(Ok(res)) => Poll::Ready(res.map(Some)),
Poll::Ready(Err(state)) => Poll::Ready(match &state.abort {
Some(err) => Err(err.clone()),
None => Ok(newest(&state.segments)),
}),
Poll::Pending => Poll::Pending,
}
}
}
impl kio::Pollable for Fetching {
type Output = Result<group::Consumer>;
fn poll(&self, waiter: &kio::Waiter) -> Poll<Self::Output> {
let mut inner = self.inner.lock();
loop {
if inner.is_none() {
let (id, track) = match ready!(self.poll_latch(waiter, None))? {
Some(next) => next,
None => return Poll::Ready(Err(Error::NotFound)),
};
let fetch = track.fetch_group(self.sequence, self.options.clone());
*inner = Some((id, track, fetch));
}
let (latched, track, fetch) = inner.as_ref().expect("latched above");
let err = match kio::Pollable::poll(&**fetch, waiter) {
Poll::Ready(Err(err)) => err,
Poll::Ready(Ok(group)) => return Poll::Ready(Ok(group)),
Poll::Pending => {
return match self.state.poll(waiter, |s| match &s.abort {
Some(err) => Poll::Ready(err.clone()),
None => Poll::Pending,
}) {
Poll::Ready(Ok(err)) => Poll::Ready(Err(err)),
_ => Poll::Pending,
};
}
};
let next = match self.poll_latch(waiter, Some(*latched)) {
Poll::Ready(res) => res?,
Poll::Pending => match track.poll_complete(&kio::Waiter::noop()) {
Poll::Ready(Err(_)) => return Poll::Pending,
_ => return Poll::Ready(Err(err)),
},
};
let Some((id, track)) = next else {
return Poll::Ready(Err(err));
};
let fetch = track.fetch_group(self.sequence, self.options.clone());
*inner = Some((id, track, fetch));
}
}
}
struct SegmentSub {
id: u64,
start: Option<u64>,
end: Option<u64>,
sub: SubState,
parked: BTreeMap<u64, group::Consumer>,
pruned: bool,
}
impl SegmentSub {
fn retired(&self) -> bool {
self.pruned && (self.end.is_none() || (matches!(self.sub, SubState::Done(_)) && self.parked.is_empty()))
}
}
enum SubState {
Pending(kio::Pending<track::Subscribing>),
Active(track::Subscriber),
Done(Option<u64>),
}
pub struct Subscriber {
state: kio::Consumer<ResumeState>,
prefs: kio::Producer<Subscription>,
last_prefs: Subscription,
epoch: u64,
finished: bool,
abort: Option<Error>,
closed: bool,
segments: Vec<SegmentSub>,
next_sequence: u64,
min_sequence: u64,
end_sequence: Option<u64>,
reading: Option<group::Consumer>,
}
impl Subscriber {
fn poll_sync(&mut self, waiter: &kio::Waiter) {
self.sync(waiter);
self.reap();
}
fn reap(&mut self) {
self.segments.retain(|s| !s.retired());
let mut cut = self
.segments
.iter()
.filter(|s| s.pruned)
.count()
.saturating_sub(MAX_SEGMENTS);
if cut > 0 {
for seg in &mut self.segments {
if cut == 0 {
break;
}
if seg.pruned {
seg.sub = SubState::Done(None);
seg.parked.clear();
cut -= 1;
}
}
self.segments.retain(|s| !s.retired());
}
}
fn sync(&mut self, waiter: &kio::Waiter) {
loop {
let prefs = {
let last = &self.last_prefs;
match self
.prefs
.poll(waiter, |p| if **p != *last { Poll::Ready(()) } else { Poll::Pending })
{
Poll::Ready(Ok(guard)) => (*guard).clone(),
Poll::Ready(Err(_)) | Poll::Pending => break,
}
};
self.last_prefs = prefs;
for seg in &mut self.segments {
if let SubState::Active(sub) = &mut seg.sub {
let _ = sub.update(slice(&self.last_prefs, seg.start, seg.end));
}
}
}
loop {
let epoch = self.epoch;
let (snapshot, closed) = match self.state.poll(waiter, |state| {
if state.epoch != epoch {
Poll::Ready(state.snapshot())
} else {
Poll::Pending
}
}) {
Poll::Ready(Ok(snapshot)) => (Some(snapshot), false),
Poll::Ready(Err(state)) => {
let snapshot = (state.epoch != epoch).then(|| state.snapshot());
(snapshot, true)
}
Poll::Pending => return,
};
if let Some(snapshot) = snapshot {
self.apply(snapshot);
}
if closed {
self.closed = true;
return;
}
}
}
fn apply(&mut self, snapshot: Snapshot) {
let Snapshot {
epoch,
finished,
abort,
segments,
} = snapshot;
self.epoch = epoch;
self.finished = finished;
self.abort = abort;
for s in &mut self.segments {
s.pruned = !segments.iter().any(|n| n.id == s.id);
}
self.segments.retain(|s| !s.retired());
for segment in segments {
match self.segments.iter_mut().find(|s| s.id == segment.id) {
Some(existing) => {
if existing.end != segment.end {
existing.end = segment.end;
if let SubState::Active(sub) = &mut existing.sub {
let _ = sub.update(slice(&self.last_prefs, segment.start, segment.end));
}
}
}
None => {
let sub = segment
.track
.subscribe(slice(&self.last_prefs, segment.start, segment.end));
self.segments.push(SegmentSub {
id: segment.id,
start: segment.start,
end: segment.end,
sub: SubState::Pending(sub),
parked: BTreeMap::new(),
pruned: false,
});
}
}
}
}
fn poll_activate(seg: &mut SegmentSub, prefs: &Subscription, min_sequence: u64, waiter: &kio::Waiter) -> Poll<()> {
if let SubState::Pending(pending) = &mut seg.sub {
match pending.poll_ok(waiter) {
Poll::Ready(Ok(mut sub)) => {
sub.start_at(seg.start.unwrap_or(0).max(min_sequence));
let _ = sub.update(slice(prefs, seg.start, seg.end));
seg.sub = SubState::Active(sub);
}
Poll::Ready(Err(_)) => seg.sub = SubState::Done(None),
Poll::Pending => return Poll::Pending,
}
}
Poll::Ready(())
}
fn poll_segment(
seg: &mut SegmentSub,
prefs: &Subscription,
min_sequence: u64,
waiter: &kio::Waiter,
) -> Poll<Option<group::Consumer>> {
loop {
match &mut seg.sub {
SubState::Pending(_) => {
ready!(Self::poll_activate(seg, prefs, min_sequence, waiter));
}
SubState::Active(sub) => match sub.poll_recv_group(waiter) {
Poll::Ready(Ok(Some(group))) => {
if let Some(end) = seg.end
&& group.sequence > end
{
continue;
}
return Poll::Ready(Some(group));
}
Poll::Ready(Ok(None)) => {
let count = sub.poll_finished(waiter).map(|res| res.ok());
let count = match count {
Poll::Ready(count) => count,
Poll::Pending => None,
};
seg.sub = SubState::Done(count);
return Poll::Ready(None);
}
Poll::Ready(Err(_)) => {
seg.sub = SubState::Done(None);
return Poll::Ready(None);
}
Poll::Pending => return Poll::Pending,
},
SubState::Done(_) => return Poll::Ready(None),
}
}
}
pub fn poll_recv_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
self.poll_sync(waiter);
let end_sequence = self.end_sequence;
let min_sequence = self.min_sequence;
let beyond_cap = |sequence: u64| end_sequence.is_some_and(|end| sequence > end);
let mut all_done = true;
for seg in &mut self.segments {
let watch = |group: &group::Consumer| match group.poll_closed(waiter) {
Poll::Pending => true,
Poll::Ready(()) => !group.is_aborted(),
};
seg.parked
.retain(|sequence, group| *sequence >= min_sequence && watch(group));
if let Some(&sequence) = seg.parked.keys().next()
&& !beyond_cap(sequence)
{
let group = seg.parked.remove(&sequence).expect("parked key just observed");
self.next_sequence = self.next_sequence.max(sequence.saturating_add(1));
return Poll::Ready(Ok(Some(group)));
}
loop {
match Self::poll_segment(seg, &self.last_prefs, min_sequence, waiter) {
Poll::Ready(Some(group)) => {
if beyond_cap(group.sequence) {
if watch(&group) {
seg.parked.insert(group.sequence, group);
}
continue;
}
if group.sequence < min_sequence {
continue;
}
self.next_sequence = self.next_sequence.max(group.sequence.saturating_add(1));
return Poll::Ready(Ok(Some(group)));
}
Poll::Ready(None) => break,
Poll::Pending => break,
}
}
if !seg.parked.is_empty() || !matches!(seg.sub, SubState::Done(_)) {
all_done = false;
}
}
if let Some(err) = &self.abort {
return Poll::Ready(Err(err.clone()));
}
if all_done {
if self.finished {
return Poll::Ready(Ok(None));
}
if self.closed {
return Poll::Ready(Err(Error::Dropped));
}
}
Poll::Pending
}
#[cfg(test)]
pub async fn recv_group(&mut self) -> Result<Option<group::Consumer>> {
kio::wait(|waiter| self.poll_recv_group(waiter)).await
}
pub fn poll_next_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
loop {
let floor = self.next_sequence;
match ready!(self.poll_recv_group(waiter))? {
Some(group) if group.sequence < floor => continue,
res => return Poll::Ready(Ok(res)),
}
}
}
pub fn poll_read_frame(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<frame::Frame>>> {
loop {
if let Some(group) = &mut self.reading {
match group.poll_read_frame(waiter) {
Poll::Ready(Ok(Some(frame))) => {
self.reading = None;
return Poll::Ready(Ok(Some(frame)));
}
Poll::Ready(_) => self.reading = None,
Poll::Pending => return Poll::Pending,
}
continue;
}
match ready!(self.poll_next_group(waiter))? {
Some(group) => self.reading = Some(group),
None => return Poll::Ready(Ok(None)),
}
}
}
#[cfg(test)]
pub async fn read_frame(&mut self) -> Result<Option<frame::Frame>> {
kio::wait(|waiter| self.poll_read_frame(waiter)).await
}
pub fn poll_recv_datagram(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Datagram>>> {
self.poll_sync(waiter);
let mut pending_activation = false;
if let Some(seg) = self.segments.last_mut() {
if Self::poll_activate(seg, &self.last_prefs, self.min_sequence, waiter).is_pending() {
pending_activation = true;
} else if let SubState::Active(sub) = &mut seg.sub {
match sub.poll_recv_datagram(waiter) {
Poll::Ready(Ok(Some(datagram))) => return Poll::Ready(Ok(Some(datagram))),
Poll::Ready(_) => {}
Poll::Pending => return Poll::Pending,
}
}
}
if let Some(err) = &self.abort {
return Poll::Ready(Err(err.clone()));
}
if self.finished {
return Poll::Ready(Ok(None));
}
if self.closed && !pending_activation {
return Poll::Ready(Err(Error::Dropped));
}
Poll::Pending
}
#[cfg(test)]
pub async fn closed(&mut self) -> Result<()> {
kio::wait(|waiter| self.poll_finished(waiter)).await.map(|_| ())
}
pub fn poll_finished(&mut self, waiter: &kio::Waiter) -> Poll<Result<u64>> {
self.poll_sync(waiter);
if let Some(err) = &self.abort {
return Poll::Ready(Err(err.clone()));
}
if !self.finished {
if self.closed {
return Poll::Ready(Err(Error::Dropped));
}
return Poll::Pending;
}
let Some(seg) = self.segments.last_mut() else {
return Poll::Ready(Ok(0));
};
ready!(Self::poll_activate(seg, &self.last_prefs, self.min_sequence, waiter));
match &mut seg.sub {
SubState::Done(count) => Poll::Ready(Ok(count.unwrap_or(0))),
SubState::Active(sub) => match ready!(sub.poll_finished(waiter)) {
Ok(count) => {
seg.sub = SubState::Done(Some(count));
Poll::Ready(Ok(count))
}
Err(_) => {
seg.sub = SubState::Done(None);
Poll::Ready(Ok(0))
}
},
SubState::Pending(_) => unreachable!("poll_activate resolved above"),
}
}
#[cfg(test)]
pub async fn finished(&mut self) -> Result<u64> {
kio::wait(|waiter| self.poll_finished(waiter)).await
}
pub fn start_at(&mut self, sequence: u64) {
self.min_sequence = sequence;
for seg in &mut self.segments {
if let SubState::Active(sub) = &mut seg.sub {
sub.start_at(seg.start.unwrap_or(0).max(sequence));
}
}
}
pub fn end_at(&mut self, sequence: impl Into<Option<u64>>) {
self.end_sequence = sequence.into();
}
pub(crate) fn prefs(&self) -> kio::Producer<Subscription> {
self.prefs.clone()
}
pub fn update(&mut self, subscription: Subscription) {
if let Ok(mut prefs) = self.prefs.write() {
*prefs = subscription;
}
}
pub fn latest(&self) -> Option<u64> {
self.state.read().latest()
}
pub fn is_clone(&self, other: &Self) -> bool {
self.state.same_channel(&other.state)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{Timestamp, broadcast};
use futures::FutureExt;
use std::sync::Arc;
fn track_pair(name: &str) -> (track::Producer, track::Consumer) {
let producer = track::Producer::new(Arc::new(broadcast::Info::default()), name, None);
let consumer = producer.consume();
(producer, consumer)
}
fn write_group(producer: &mut track::Producer, sequence: u64, payload: &str) {
let mut group = producer.create_group(group::Info { sequence }).unwrap();
group.write_frame(Timestamp::ZERO, payload.as_bytes().to_vec()).unwrap();
group.finish().unwrap();
}
fn recv(sub: &mut Subscriber) -> u64 {
sub.recv_group()
.now_or_never()
.expect("should not block")
.expect("should not error")
.expect("should not be finished")
.sequence
}
fn recv_pending(sub: &mut Subscriber) {
assert!(sub.recv_group().now_or_never().is_none(), "should have blocked");
}
struct CountWaker(std::sync::atomic::AtomicUsize);
impl std::task::Wake for CountWaker {
fn wake(self: Arc<Self>) {
self.0.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
}
}
impl CountWaker {
fn new() -> (Arc<Self>, std::task::Waker) {
let counter = Arc::new(Self(std::sync::atomic::AtomicUsize::new(0)));
(counter.clone(), std::task::Waker::from(counter))
}
fn count(&self) -> usize {
self.0.load(std::sync::atomic::Ordering::SeqCst)
}
}
#[tokio::test]
async fn switch_splices_groups() {
let (mut track_a, consumer_a) = track_pair("a");
let (mut track_b, consumer_b) = track_pair("b");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
write_group(&mut track_a, 0, "a0");
write_group(&mut track_a, 1, "a1");
assert_eq!(recv(&mut sub), 0);
assert_eq!(recv(&mut sub), 1);
producer.switch(&consumer_b, 2).unwrap();
write_group(&mut track_a, 2, "a2-over-cap");
write_group(&mut track_b, 2, "b2");
write_group(&mut track_b, 3, "b3");
assert_eq!(recv(&mut sub), 2);
assert_eq!(recv(&mut sub), 3);
recv_pending(&mut sub);
}
#[tokio::test]
async fn demand_reflects_boundaries() {
let (track_a, consumer_a) = track_pair("a");
let (track_b, consumer_b) = track_pair("b");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer
.consume()
.subscribe(Subscription::default().with_group_start(0));
recv_pending(&mut sub);
assert_eq!(track_a.subscription().unwrap().group_end, None);
producer.switch(&consumer_b, 5).unwrap();
recv_pending(&mut sub);
assert_eq!(track_a.subscription().unwrap().group_end, Some(4));
assert_eq!(track_b.subscription().unwrap().group_start, Some(5));
}
#[tokio::test]
async fn update_reslices_demand() {
let (track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
recv_pending(&mut sub);
assert_eq!(track_a.subscription().unwrap().priority, 0);
sub.update(Subscription::default().with_priority(7));
recv_pending(&mut sub);
assert_eq!(track_a.subscription().unwrap().priority, 7);
}
#[tokio::test]
async fn dead_segment_stalls_until_switch() {
let (mut track_a, consumer_a) = track_pair("a");
let (mut track_b, consumer_b) = track_pair("b");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
write_group(&mut track_a, 0, "a0");
assert_eq!(recv(&mut sub), 0);
track_a.abort(Error::Dropped).unwrap();
recv_pending(&mut sub);
producer.switch(&consumer_b, 1).unwrap();
write_group(&mut track_b, 1, "b1");
assert_eq!(recv(&mut sub), 1);
}
#[tokio::test]
async fn takeover_computes_boundary() {
let (mut track_a, consumer_a) = track_pair("a");
let (mut track_b, consumer_b) = track_pair("b");
let mut producer = Producer::new();
producer.takeover(&consumer_a).unwrap();
let mut sub = producer.consume().subscribe(None);
write_group(&mut track_a, 0, "a0");
write_group(&mut track_a, 1, "a1");
assert_eq!(recv(&mut sub), 0);
assert_eq!(recv(&mut sub), 1);
track_a.abort(Error::Dropped).unwrap();
producer.takeover(&consumer_b).unwrap();
write_group(&mut track_b, 2, "b2");
assert_eq!(recv(&mut sub), 2);
}
#[tokio::test]
async fn takeover_replaces_empty_segment() {
let (track_a, consumer_a) = track_pair("a");
let (mut track_b, consumer_b) = track_pair("b");
let mut producer = Producer::new();
producer.takeover(&consumer_a).unwrap();
let mut sub = producer.consume().subscribe(None);
recv_pending(&mut sub);
drop(track_a);
producer.takeover(&consumer_b).unwrap();
write_group(&mut track_b, 0, "b0");
assert_eq!(recv(&mut sub), 0);
}
#[tokio::test]
async fn finish_ends_after_final_segment() {
let (mut track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
write_group(&mut track_a, 0, "a0");
assert_eq!(recv(&mut sub), 0);
producer.finish().unwrap();
recv_pending(&mut sub);
track_a.finish().unwrap();
assert!(
sub.recv_group()
.now_or_never()
.expect("should not block")
.expect("should not error")
.is_none(),
"should be finished"
);
assert_eq!(sub.finished().now_or_never().unwrap().unwrap(), 1);
assert!(sub.closed().now_or_never().unwrap().is_ok());
}
#[tokio::test]
async fn read_frame_across_segments() {
let (mut track_a, consumer_a) = track_pair("a");
let (mut track_b, consumer_b) = track_pair("b");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
write_group(&mut track_a, 0, "a0");
producer.switch(&consumer_b, 1).unwrap();
write_group(&mut track_b, 1, "b1");
let frame = sub.read_frame().now_or_never().unwrap().unwrap().unwrap();
assert_eq!(&frame.payload[..], b"a0");
let frame = sub.read_frame().now_or_never().unwrap().unwrap().unwrap();
assert_eq!(&frame.payload[..], b"b1");
}
#[tokio::test]
async fn info_from_first_segment() {
let (_track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
let consumer = producer.consume();
assert!(consumer.info().now_or_never().is_none());
producer.switch(&consumer_a, None).unwrap();
let info = consumer.info().now_or_never().unwrap().unwrap();
assert_eq!(info.timescale, crate::Timescale::default());
}
#[tokio::test]
async fn fetch_routes_to_newest_segment() {
let (track_a, consumer_a) = track_pair("a");
let (mut track_b, consumer_b) = track_pair("b");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
producer.switch(&consumer_b, 10).unwrap();
write_group(&mut track_b, 3, "b3");
let consumer = producer.consume();
let group = consumer
.fetch_group(3, None)
.now_or_never()
.expect("cached fetch should resolve")
.unwrap();
assert_eq!(group.sequence, 3);
drop(track_a);
}
#[tokio::test]
async fn fetch_waits_for_first_segment() {
let (mut track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
let consumer = producer.consume();
let fetch = consumer.fetch_group(0, None);
let mut fetch = std::pin::pin!(fetch);
assert!(futures::poll!(fetch.as_mut()).is_pending(), "fetch should wait");
write_group(&mut track_a, 0, "a0");
producer.switch(&consumer_a, None).unwrap();
let group = fetch.await.expect("fetch should resolve");
assert_eq!(group.sequence, 0);
}
#[tokio::test]
async fn takeover_survives_dead_empty_segment() {
let (mut track_a, consumer_a) = track_pair("a");
let (track_b, consumer_b) = track_pair("b");
let (mut track_c, consumer_c) = track_pair("c");
let mut producer = Producer::new();
producer.takeover(&consumer_a).unwrap();
let mut sub = producer.consume().subscribe(None);
write_group(&mut track_a, 0, "a0");
assert_eq!(recv(&mut sub), 0);
track_a.abort(Error::Dropped).unwrap();
producer.takeover(&consumer_b).unwrap();
drop(track_b);
producer.takeover(&consumer_c).unwrap();
write_group(&mut track_c, 1, "c1");
assert_eq!(recv(&mut sub), 1);
}
#[tokio::test]
async fn finished_does_not_consume_groups() {
let (mut track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
write_group(&mut track_a, 0, "a0");
producer.finish().unwrap();
assert!(sub.finished().now_or_never().is_none(), "final segment still open");
assert_eq!(recv(&mut sub), 0);
track_a.finish().unwrap();
assert_eq!(sub.finished().now_or_never().unwrap().unwrap(), 1);
}
#[tokio::test]
async fn datagram_only_subscriber_activates() {
let (mut track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
assert!(
kio::wait(|waiter| sub.poll_recv_datagram(waiter))
.now_or_never()
.is_none(),
"no datagram yet"
);
track_a.append_datagram(Timestamp::ZERO, b"d0".as_ref()).unwrap();
let datagram = kio::wait(|waiter| sub.poll_recv_datagram(waiter))
.now_or_never()
.expect("datagram should be ready")
.expect("should not error")
.expect("track should not be finished");
assert_eq!(&datagram.payload[..], b"d0");
}
#[tokio::test]
async fn end_at_parks_at_cap() {
let (mut track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
write_group(&mut track_a, 0, "a0");
write_group(&mut track_a, 1, "a1");
sub.end_at(0);
assert_eq!(recv(&mut sub), 0);
recv_pending(&mut sub);
sub.end_at(1);
assert_eq!(recv(&mut sub), 1);
}
#[tokio::test]
async fn end_at_reoffers_reordered_arrivals() {
let (mut track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
sub.end_at(1);
write_group(&mut track_a, 2, "a2");
write_group(&mut track_a, 0, "a0");
write_group(&mut track_a, 1, "a1");
assert_eq!(recv(&mut sub), 0);
assert_eq!(recv(&mut sub), 1);
recv_pending(&mut sub);
sub.end_at(2);
assert_eq!(recv(&mut sub), 2);
}
#[tokio::test]
async fn evicted_parked_groups_are_dropped() {
let (mut track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
sub.end_at(0);
write_group(&mut track_a, 0, "a0");
assert_eq!(recv(&mut sub), 0);
let straggler = track_a.create_group(group::Info { sequence: 1 }).unwrap();
recv_pending(&mut sub);
straggler.abort(Error::Old).unwrap();
sub.end_at(None);
write_group(&mut track_a, 2, "a2");
assert_eq!(recv(&mut sub), 2, "the evicted parked group is dropped, not re-offered");
}
#[tokio::test]
async fn next_group_skips_boundary_duplicate() {
let (mut track_a, consumer_a) = track_pair("a");
let (mut track_b, consumer_b) = track_pair("b");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
let next = |sub: &mut Subscriber| {
kio::wait(|waiter| sub.poll_next_group(waiter))
.now_or_never()
.expect("should not block")
.expect("should not error")
.expect("should not be finished")
.sequence
};
write_group(&mut track_a, 0, "a0");
write_group(&mut track_a, 1, "a1");
assert_eq!(next(&mut sub), 0);
assert_eq!(next(&mut sub), 1);
producer.switch(&consumer_b, 1).unwrap();
write_group(&mut track_b, 1, "b1");
write_group(&mut track_b, 2, "b2");
assert_eq!(next(&mut sub), 2);
}
#[tokio::test]
async fn consecutive_updates_wake() {
use std::task::Context;
let (track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
let prefs = sub.prefs();
let (counter, waker) = CountWaker::new();
let mut cx = Context::from_waker(&waker);
let mut fut = std::pin::pin!(sub.recv_group());
assert!(fut.as_mut().poll(&mut cx).is_pending());
*prefs.write().ok().unwrap() = Subscription::default().with_priority(1);
assert_eq!(counter.count(), 1);
assert!(fut.as_mut().poll(&mut cx).is_pending());
assert_eq!(track_a.subscription().unwrap().priority, 1);
*prefs.write().ok().unwrap() = Subscription::default().with_priority(2);
assert_eq!(counter.count(), 2, "second update lost its wakeup");
assert!(fut.as_mut().poll(&mut cx).is_pending());
assert_eq!(track_a.subscription().unwrap().priority, 2);
}
#[tokio::test]
async fn prune_bounds_segments_and_keeps_the_boundary() {
let mut producer = Producer::new();
let mut sub = producer.consume().subscribe(None);
let count = 2 * MAX_SEGMENTS as u64;
let mut tracks = Vec::new();
for sequence in 0..count {
let (mut track, consumer) = track_pair("t");
producer.takeover(&consumer).unwrap();
write_group(&mut track, sequence, "g");
assert_eq!(recv(&mut sub), sequence);
tracks.push(track);
}
assert!(producer.state.read().segments.len() <= MAX_SEGMENTS);
assert_eq!(producer.latest(), Some(count - 1));
let (mut track, consumer) = track_pair("next");
producer.takeover(&consumer).unwrap();
write_group(&mut track, count, "g");
assert_eq!(recv(&mut sub), count);
}
#[tokio::test]
async fn reader_drains_a_pruned_segments_copy() {
let mut producer = Producer::new();
let mut sub = producer.consume().subscribe(None);
let count = 2 * MAX_SEGMENTS as u64;
let mut tracks = Vec::new();
for sequence in 0..count {
let (mut track, consumer) = track_pair("t");
producer.takeover(&consumer).unwrap();
assert!(
kio::wait(|waiter| sub.poll_recv_datagram(waiter))
.now_or_never()
.is_none(),
"no datagram expected"
);
write_group(&mut track, sequence, "g");
tracks.push(track);
}
assert!(producer.state.read().segments.len() <= MAX_SEGMENTS);
for sequence in 0..count {
assert_eq!(recv(&mut sub), sequence);
}
}
#[tokio::test]
async fn unpolled_reader_skips_pruned_ranges() {
let mut producer = Producer::new();
let mut sub = producer.consume().subscribe(None);
recv_pending(&mut sub);
let count = 2 * MAX_SEGMENTS as u64;
let mut tracks = Vec::new();
for sequence in 0..count {
let (mut track, consumer) = track_pair("t");
producer.takeover(&consumer).unwrap();
write_group(&mut track, sequence, "g");
tracks.push(track);
}
for sequence in count - MAX_SEGMENTS as u64..count {
assert_eq!(recv(&mut sub), sequence);
}
recv_pending(&mut sub);
}
#[tokio::test]
async fn datagram_poller_bounds_pruned_cursors() {
let mut producer = Producer::new();
let mut sub = producer.consume().subscribe(None);
for sequence in 0..(3 * MAX_SEGMENTS as u64) {
let (mut track, consumer) = track_pair("t");
producer.takeover(&consumer).unwrap();
write_group(&mut track, sequence, "payload");
assert!(
kio::wait(|waiter| sub.poll_recv_datagram(waiter))
.now_or_never()
.is_none(),
"no datagram expected"
);
}
assert_eq!(
sub.segments.len(),
2 * MAX_SEGMENTS,
"the reap must run on the datagram path too"
);
}
#[tokio::test]
async fn fetch_fails_over_to_a_newer_segment() {
let (track_a, consumer_a) = track_pair("a");
let (mut track_b, consumer_b) = track_pair("b");
let mut producer = Producer::new();
producer.takeover(&consumer_a).unwrap();
let consumer = producer.consume();
track_a.abort(Error::Dropped).unwrap();
let fetch = consumer.fetch_group(0, None);
let mut fetch = std::pin::pin!(fetch);
assert!(futures::poll!(fetch.as_mut()).is_pending(), "a dead copy should park");
write_group(&mut track_b, 0, "b0");
producer.takeover(&consumer_b).unwrap();
let group = fetch.await.expect("fetch should fail over");
assert_eq!(group.sequence, 0);
}
#[tokio::test]
async fn fetch_aborts_with_the_track() {
let (track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
producer.takeover(&consumer_a).unwrap();
let consumer = producer.consume();
track_a.abort(Error::Dropped).unwrap();
let fetch = consumer.fetch_group(0, None);
let mut fetch = std::pin::pin!(fetch);
assert!(futures::poll!(fetch.as_mut()).is_pending(), "a dead copy should park");
producer.abort(Error::Cancel).unwrap();
assert!(matches!(fetch.await, Err(Error::Cancel)));
}
#[tokio::test]
async fn fetch_pending_ends_when_the_track_aborts() {
let (track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
producer.takeover(&consumer_a).unwrap();
let consumer = producer.consume();
let handler = track_a.dynamic();
let fetch = consumer.fetch_group(0, None);
let mut fetch = std::pin::pin!(fetch);
assert!(
futures::poll!(fetch.as_mut()).is_pending(),
"unanswered fetch should park"
);
producer.abort(Error::Cancel).unwrap();
assert!(matches!(fetch.await, Err(Error::Cancel)));
drop(handler);
}
#[tokio::test]
async fn fetch_error_from_a_live_copy_is_authoritative() {
let (_track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
producer.takeover(&consumer_a).unwrap();
let consumer = producer.consume();
let result = consumer
.fetch_group(0, None)
.now_or_never()
.expect("a live copy's answer must resolve immediately");
assert!(matches!(result, Err(Error::NotFound)));
}
#[tokio::test]
async fn takeover_after_empty_segment_keeps_live_edge() {
let (track_a, consumer_a) = track_pair("a");
let (track_b, consumer_b) = track_pair("b");
let mut producer = Producer::new();
producer.takeover(&consumer_a).unwrap();
let mut sub = producer.consume().subscribe(None);
recv_pending(&mut sub);
assert_eq!(track_a.subscription().unwrap().group_start, None);
drop(track_a);
producer.takeover(&consumer_b).unwrap();
recv_pending(&mut sub);
assert_eq!(track_b.subscription().unwrap().group_start, None);
}
#[tokio::test]
async fn takeover_after_produced_segment_keeps_live_edge() {
let (mut track_a, consumer_a) = track_pair("a");
let (mut track_b, consumer_b) = track_pair("b");
let mut producer = Producer::new();
producer.takeover(&consumer_a).unwrap();
let mut sub = producer.consume().subscribe(None);
recv_pending(&mut sub);
assert_eq!(track_a.subscription().unwrap().group_start, None);
write_group(&mut track_a, 0, "a0");
write_group(&mut track_a, 1, "a1");
assert_eq!(recv(&mut sub), 0);
assert_eq!(recv(&mut sub), 1);
drop(track_a);
producer.takeover(&consumer_b).unwrap();
recv_pending(&mut sub);
assert_eq!(track_b.subscription().unwrap().group_start, None);
write_group(&mut track_b, 1, "b1");
recv_pending(&mut sub);
write_group(&mut track_b, 5, "b5");
assert_eq!(recv(&mut sub), 5);
}
#[tokio::test]
async fn takeover_keeps_an_explicit_start() {
let (mut track_a, consumer_a) = track_pair("a");
let (track_b, consumer_b) = track_pair("b");
let mut producer = Producer::new();
producer.takeover(&consumer_a).unwrap();
let mut sub = producer
.consume()
.subscribe(Subscription::default().with_group_start(0));
recv_pending(&mut sub);
assert_eq!(track_a.subscription().unwrap().group_start, Some(0));
write_group(&mut track_a, 0, "a0");
write_group(&mut track_a, 1, "a1");
assert_eq!(recv(&mut sub), 0);
assert_eq!(recv(&mut sub), 1);
drop(track_a);
producer.takeover(&consumer_b).unwrap();
recv_pending(&mut sub);
assert_eq!(track_b.subscription().unwrap().group_start, Some(2));
}
#[tokio::test]
async fn switch_validates_boundaries() {
let (mut track_a, consumer_a) = track_pair("a");
let (_track_b, consumer_b) = track_pair("b");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
assert!(producer.switch(&consumer_b, None).is_err());
write_group(&mut track_a, 0, "a0");
assert!(producer.switch(&consumer_b, 0).is_err());
producer.switch(&consumer_b, 1).unwrap();
}
#[tokio::test]
async fn switch_replaces_a_run_of_empty_segments() {
let (mut track_a, consumer_a) = track_pair("a");
let (_track_b, consumer_b) = track_pair("b");
let (_track_c, consumer_c) = track_pair("c");
let (mut track_d, consumer_d) = track_pair("d");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
write_group(&mut track_a, 0, "a0");
assert_eq!(recv(&mut sub), 0);
producer.switch(&consumer_b, 1).unwrap();
producer.switch(&consumer_c, 2).unwrap();
producer.switch(&consumer_d, 1).unwrap();
write_group(&mut track_d, 1, "d1");
assert_eq!(recv(&mut sub), 1);
assert_eq!(producer.state.read().segments.len(), 2);
}
#[tokio::test]
async fn abort_drains_before_erroring() {
let (mut track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
write_group(&mut track_a, 0, "a0");
producer.abort(Error::Cancel).unwrap();
assert!(matches!(sub.finished().now_or_never().unwrap(), Err(Error::Cancel)));
assert_eq!(recv(&mut sub), 0);
assert!(matches!(sub.recv_group().now_or_never().unwrap(), Err(Error::Cancel)));
}
#[tokio::test]
async fn terminal_states_are_exclusive() {
let (_track_a, consumer_a) = track_pair("a");
let (_track_b, consumer_b) = track_pair("b");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
producer.finish().unwrap();
assert!(matches!(producer.abort(Error::Cancel), Err(Error::Closed)));
assert!(matches!(producer.finish(), Err(Error::Closed)));
assert!(matches!(producer.switch(&consumer_b, 1), Err(Error::Closed)));
assert!(matches!(producer.takeover(&consumer_b), Err(Error::Closed)));
assert!(matches!(producer.release(), Err(Error::Closed)));
let mut producer = Producer::new();
producer.abort(Error::Cancel).unwrap();
assert!(matches!(producer.finish(), Err(Error::Closed)));
assert!(matches!(producer.abort(Error::Cancel), Err(Error::Closed)));
assert!(matches!(producer.takeover(&consumer_b), Err(Error::Closed)));
}
#[tokio::test]
async fn dropped_producer_errors_once_drained() {
let (mut track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
write_group(&mut track_a, 0, "a0");
assert_eq!(recv(&mut sub), 0);
track_a.abort(Error::Cancel).unwrap();
drop(producer);
let result = sub.recv_group().now_or_never().expect("must not stall forever");
assert!(matches!(result, Err(Error::Dropped)));
}
#[tokio::test]
async fn dropped_producer_keeps_a_live_segment_serving() {
let (mut track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
drop(producer);
write_group(&mut track_a, 0, "a0");
assert_eq!(recv(&mut sub), 0);
recv_pending(&mut sub);
track_a.finish().unwrap();
let result = sub.recv_group().now_or_never().expect("must not stall forever");
assert!(matches!(result, Err(Error::Dropped)));
}
#[tokio::test]
async fn dropped_producer_fails_finished_waiters() {
let (_track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
recv_pending(&mut sub);
drop(producer);
let result = sub.finished().now_or_never().expect("must not stall forever");
assert!(matches!(result, Err(Error::Dropped)));
}
#[tokio::test]
async fn dropped_producer_ends_datagrams() {
let (track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
drop(track_a);
drop(producer);
let result = kio::wait(|waiter| sub.poll_recv_datagram(waiter))
.now_or_never()
.expect("must not stall forever");
assert!(matches!(result, Err(Error::Dropped)));
}
#[tokio::test]
async fn evicted_parked_group_wakes_the_clean_end() {
use std::task::Context;
let (mut track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
sub.end_at(0);
write_group(&mut track_a, 0, "a0");
let straggler = track_a.create_group(group::Info { sequence: 1 }).unwrap();
assert_eq!(recv(&mut sub), 0);
recv_pending(&mut sub);
track_a.finish().unwrap();
producer.finish().unwrap();
let (counter, waker) = CountWaker::new();
let mut cx = Context::from_waker(&waker);
let mut fut = std::pin::pin!(sub.recv_group());
assert!(
fut.as_mut().poll(&mut cx).is_pending(),
"the parked group holds the end open"
);
straggler.abort(Error::Old).unwrap();
assert!(counter.count() > 0, "the eviction wakeup was lost");
let result = fut.as_mut().poll(&mut cx);
assert!(matches!(result, Poll::Ready(Ok(None))));
}
#[tokio::test]
async fn straggler_parked_after_finish_still_wakes() {
use std::task::Context;
let (mut track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
sub.end_at(0);
write_group(&mut track_a, 0, "a0");
assert_eq!(recv(&mut sub), 0);
let straggler = track_a.create_group(group::Info { sequence: 1 }).unwrap();
track_a.finish().unwrap();
producer.finish().unwrap();
let (counter, waker) = CountWaker::new();
let mut cx = Context::from_waker(&waker);
let mut fut = std::pin::pin!(sub.recv_group());
assert!(
fut.as_mut().poll(&mut cx).is_pending(),
"the parked group holds the end open"
);
straggler.abort(Error::Old).unwrap();
assert!(counter.count() > 0, "the abort wakeup was lost");
assert!(matches!(fut.as_mut().poll(&mut cx), Poll::Ready(Ok(None))));
}
#[tokio::test]
async fn release_restarts_unbounded() {
let (mut track_a, consumer_a) = track_pair("a");
let (mut track_b, consumer_b) = track_pair("b");
let mut producer = Producer::new();
producer.takeover(&consumer_a).unwrap();
write_group(&mut track_a, 7, "a7");
assert!(producer.is_spliced());
producer.release().unwrap();
assert!(!producer.is_spliced());
assert_eq!(producer.latest(), None);
producer.takeover(&consumer_b).unwrap();
let mut sub = producer.consume().subscribe(None);
recv_pending(&mut sub);
assert_eq!(track_b.subscription().unwrap().group_start, None);
write_group(&mut track_b, 2, "b2");
assert_eq!(recv(&mut sub), 2);
}
#[tokio::test]
async fn release_resets_the_pruned_floor() {
let mut producer = Producer::new();
let count = 2 * MAX_SEGMENTS as u64;
let mut tracks = Vec::new();
for sequence in 0..count {
let (mut track, consumer) = track_pair("t");
producer.takeover(&consumer).unwrap();
write_group(&mut track, sequence, "g");
tracks.push(track);
}
producer.release().unwrap();
let (mut track, consumer) = track_pair("fresh");
producer.takeover(&consumer).unwrap();
let mut sub = producer.consume().subscribe(None);
write_group(&mut track, 0, "g0");
assert_eq!(recv(&mut sub), 0);
}
#[tokio::test]
async fn fetch_fails_when_the_producer_dies_segmentless() {
let producer = Producer::new();
let consumer = producer.consume();
let fetch = consumer.fetch_group(0, None);
let mut fetch = std::pin::pin!(fetch);
assert!(futures::poll!(fetch.as_mut()).is_pending(), "fetch should wait");
drop(producer);
assert!(matches!(fetch.await, Err(Error::NotFound)));
}
#[tokio::test]
async fn info_fails_when_the_producer_dies_segmentless() {
let producer = Producer::new();
let consumer = producer.consume();
let info = consumer.info();
let mut info = std::pin::pin!(info);
assert!(futures::poll!(info.as_mut()).is_pending(), "info should wait");
drop(producer);
assert!(matches!(info.await, Err(Error::Dropped)));
}
#[tokio::test]
async fn start_at_drops_parked_groups_below_the_floor() {
let (mut track_a, consumer_a) = track_pair("a");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
sub.end_at(0);
write_group(&mut track_a, 0, "a0");
write_group(&mut track_a, 1, "a1");
write_group(&mut track_a, 2, "a2");
assert_eq!(recv(&mut sub), 0);
recv_pending(&mut sub);
sub.start_at(2);
sub.end_at(None);
assert_eq!(recv(&mut sub), 2, "group 1 was overtaken by start_at");
recv_pending(&mut sub);
}
#[tokio::test]
async fn demand_intersects_subscriber_end_with_boundary() {
let (track_a, consumer_a) = track_pair("a");
let (track_b, consumer_b) = track_pair("b");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer
.consume()
.subscribe(Subscription::default().with_group_start(0).with_group_end(3));
recv_pending(&mut sub);
assert_eq!(track_a.subscription().unwrap().group_end, Some(3));
producer.switch(&consumer_b, 2).unwrap();
recv_pending(&mut sub);
assert_eq!(track_a.subscription().unwrap().group_end, Some(1));
assert_eq!(track_b.subscription().unwrap().group_end, Some(3));
}
#[tokio::test]
async fn subscribers_read_independently() {
let (mut track_a, consumer_a) = track_pair("a");
let (mut track_b, consumer_b) = track_pair("b");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let consumer = producer.consume();
let mut sub1 = consumer.subscribe(None);
let mut sub2 = consumer.subscribe(None);
recv_pending(&mut sub1);
recv_pending(&mut sub2);
write_group(&mut track_a, 0, "a0");
producer.switch(&consumer_b, 1).unwrap();
write_group(&mut track_b, 1, "b1");
assert_eq!(recv(&mut sub1), 0);
assert_eq!(recv(&mut sub1), 1);
assert_eq!(recv(&mut sub2), 0);
assert_eq!(recv(&mut sub2), 1);
assert!(sub1.is_clone(&sub2));
}
#[tokio::test]
async fn latest_clamps_to_segment_bounds() {
let (mut track_a, consumer_a) = track_pair("a");
let (mut track_b, consumer_b) = track_pair("b");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let consumer = producer.consume();
assert_eq!(consumer.latest(), None);
write_group(&mut track_a, 0, "a0");
assert_eq!(consumer.latest(), Some(0));
producer.switch(&consumer_b, 2).unwrap();
write_group(&mut track_a, 5, "a5");
assert_eq!(consumer.latest(), Some(1));
write_group(&mut track_b, 0, "b0");
assert_eq!(consumer.latest(), Some(1));
write_group(&mut track_b, 3, "b3");
assert_eq!(consumer.latest(), Some(3));
}
#[tokio::test]
async fn datagrams_come_from_the_newest_segment() {
let (mut track_a, consumer_a) = track_pair("a");
let (mut track_b, consumer_b) = track_pair("b");
let mut producer = Producer::new();
producer.switch(&consumer_a, None).unwrap();
let mut sub = producer.consume().subscribe(None);
assert!(
kio::wait(|waiter| sub.poll_recv_datagram(waiter))
.now_or_never()
.is_none(),
"no datagram yet"
);
producer.switch(&consumer_b, 1).unwrap();
track_a.append_datagram(Timestamp::ZERO, b"old".as_ref()).unwrap();
assert!(
kio::wait(|waiter| sub.poll_recv_datagram(waiter))
.now_or_never()
.is_none(),
"stale datagram must not surface"
);
track_b.append_datagram(Timestamp::ZERO, b"new".as_ref()).unwrap();
let datagram = kio::wait(|waiter| sub.poll_recv_datagram(waiter))
.now_or_never()
.expect("datagram should be ready")
.expect("should not error")
.expect("track should not be finished");
assert_eq!(&datagram.payload[..], b"new");
}
}