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, bound) => bound,
};
sub.group_end = min_some(prefs.group_end, end);
sub
}
struct ResumeState {
segments: Vec<Segment>,
epoch: u64,
finished: bool,
abort: Option<Error>,
}
impl Default for ResumeState {
fn default() -> Self {
Self {
segments: Vec::new(),
epoch: 1,
finished: false,
abort: None,
}
}
}
impl ResumeState {
fn latest(&self) -> Option<u64> {
self.segments.iter().filter_map(Segment::produced).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;
Ok(())
}
}
#[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 = if state.segments.is_empty() {
None
} else {
match state.latest() {
Some(latest) => latest.checked_add(1),
None => Some(0),
}
};
state.switch(track, start)
}
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 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) {
let _ = self.state.poll_used(waiter);
}
pub(crate) fn poll_unused(&self, waiter: &kio::Waiter) {
let _ = self.state.poll_unused(waiter);
}
}
#[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,
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,
inner: web_async::Lock<Option<kio::Pending<track::Fetching>>>,
}
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();
if inner.is_none() {
let track = match self.state.poll(waiter, |s| {
if s.abort.is_some() || !s.segments.is_empty() {
Poll::Ready(match &s.abort {
Some(err) => Err(err.clone()),
None => Ok(s.segments.last().expect("nonempty").track.clone()),
})
} else {
Poll::Pending
}
}) {
Poll::Ready(Ok(res)) => res?,
Poll::Ready(Err(state)) => match (&state.abort, state.segments.last()) {
(Some(err), _) => return Poll::Ready(Err(err.clone())),
(None, Some(segment)) => segment.track.clone(),
(None, None) => return Poll::Ready(Err(Error::NotFound)),
},
Poll::Pending => return Poll::Pending,
};
*inner = Some(track.fetch_group(self.sequence, self.options.clone()));
}
kio::Pollable::poll(&**inner.as_ref().expect("latched above"), waiter)
}
}
struct SegmentSub {
id: u64,
start: Option<u64>,
end: Option<u64>,
sub: SubState,
parked: Option<group::Consumer>,
}
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>,
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) {
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.epoch, state.finished, state.abort.clone(), state.segments.clone()))
} else {
Poll::Pending
}
}) {
Poll::Ready(Ok(snapshot)) => (Some(snapshot), false),
Poll::Ready(Err(state)) => {
let snapshot = (state.epoch != epoch)
.then(|| (state.epoch, state.finished, state.abort.clone(), state.segments.clone()));
(snapshot, true)
}
Poll::Pending => return,
};
if let Some(snapshot) = snapshot {
self.apply(snapshot);
}
if closed {
return;
}
}
}
fn apply(&mut self, snapshot: (u64, bool, Option<Error>, Vec<Segment>)) {
let (epoch, finished, abort, segments) = snapshot;
self.epoch = epoch;
self.finished = finished;
self.abort = abort;
self.segments.retain(|s| segments.iter().any(|n| n.id == s.id));
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 {
sub.end_at(min_some(segment.end, self.end_sequence));
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: None,
});
}
}
}
}
fn poll_activate(
seg: &mut SegmentSub,
prefs: &Subscription,
min_sequence: u64,
end_sequence: Option<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));
sub.end_at(min_some(seg.end, end_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,
end_sequence: Option<u64>,
waiter: &kio::Waiter,
) -> Poll<Option<group::Consumer>> {
loop {
match &mut seg.sub {
SubState::Pending(_) => {
ready!(Self::poll_activate(seg, prefs, min_sequence, end_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 beyond_cap = |sequence: u64| end_sequence.is_some_and(|end| sequence > end);
let mut all_done = true;
for seg in &mut self.segments {
if let Some(group) = seg.parked.take_if(|group| !beyond_cap(group.sequence))
&& group.sequence >= self.min_sequence
{
self.next_sequence = self.next_sequence.max(group.sequence.saturating_add(1));
return Poll::Ready(Ok(Some(group)));
}
if seg.parked.is_some() {
all_done = false;
continue;
}
loop {
match Self::poll_segment(seg, &self.last_prefs, self.min_sequence, end_sequence, waiter) {
Poll::Ready(Some(group)) => {
if beyond_cap(group.sequence) {
seg.parked = Some(group);
all_done = false;
break;
}
if group.sequence < self.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 => {
all_done = false;
break;
}
}
}
}
if let Some(err) = &self.abort {
return Poll::Ready(Err(err.clone()));
}
if self.finished && all_done {
return Poll::Ready(Ok(None));
}
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);
if let Some(seg) = self.segments.last_mut()
&& Self::poll_activate(seg, &self.last_prefs, self.min_sequence, self.end_sequence, waiter).is_ready()
&& 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));
}
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 {
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,
self.end_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();
for seg in &mut self.segments {
if let SubState::Active(sub) = &mut seg.sub {
sub.end_at(min_some(seg.end, self.end_sequence));
}
}
}
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");
}
#[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 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::sync::atomic::{AtomicUsize, Ordering};
use std::task::{Context, Wake, Waker};
struct CountWaker(AtomicUsize);
impl Wake for CountWaker {
fn wake(self: Arc<Self>) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
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 = Arc::new(CountWaker(AtomicUsize::new(0)));
let waker = Waker::from(counter.clone());
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.0.load(Ordering::SeqCst), 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.0.load(Ordering::SeqCst), 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 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();
}
}