use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, MutexGuard, OnceLock, PoisonError};
use std::task::{Context, Poll};
use futures::task::AtomicWaker;
use ruststream::RawMessage;
use ruststream::testing::Coordinator;
use sea_streamer_types::{SharedMessage, StreamKey, Timestamp};
use crate::error::SeaFileError;
use crate::in_process::queue::{
Entry, EntryReceiver, EntrySender, Registry, Release, SubscriptionId, raw, release_one,
release_queued, stamp,
};
use crate::message::{FilePosition, SeaMessage};
pub(crate) struct MemoryFile {
inner: Mutex<FileInner>,
coordinator: OnceLock<Coordinator>,
end_with_eos: bool,
}
#[derive(Default)]
struct FileInner {
registry: Registry,
streams: HashMap<String, Vec<SharedMessage>>,
marked: bool,
closed: bool,
}
impl std::fmt::Debug for MemoryFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MemoryFile")
.field("end_with_eos", &self.end_with_eos)
.finish_non_exhaustive()
}
}
impl MemoryFile {
pub(crate) fn new(end_with_eos: bool) -> Arc<Self> {
Arc::new(Self {
inner: Mutex::default(),
coordinator: OnceLock::new(),
end_with_eos,
})
}
fn lock(&self) -> MutexGuard<'_, FileInner> {
self.inner.lock().unwrap_or_else(PoisonError::into_inner)
}
pub(crate) fn install(&self, coordinator: Coordinator) {
let _ = self.coordinator.set(coordinator);
}
fn coordinator(&self) -> Option<&Coordinator> {
self.coordinator.get()
}
pub(crate) fn append(&self, key: StreamKey, bytes: Vec<u8>) {
let mut inner = self.lock();
let entries = inner.streams.entry(key.name().to_owned()).or_default();
let message = stamp(key, entries.len() as u64 + 1, bytes);
entries.push(message.clone());
inner.registry.fan_out(
message.header().stream_key().name(),
&message,
self.coordinator(),
);
}
pub(crate) fn published(&self, name: &str) -> Vec<RawMessage> {
self.lock()
.streams
.get(name)
.map(|entries| entries.iter().map(raw).collect())
.unwrap_or_default()
}
pub(crate) fn subscribe(self: &Arc<Self>, key: &str, replay: bool) -> FileQueue {
let mut inner = self.lock();
let (id, requeue, rx) = inner.registry.open(key);
if replay {
for message in inner.streams.get(key).into_iter().flatten() {
if requeue.send(Entry::Message(message.clone())).is_ok()
&& let Some(coordinator) = self.coordinator()
{
coordinator.enqueued();
}
}
}
drop(inner);
FileQueue {
file: Arc::clone(self),
id,
stream: Arc::from(key),
rx,
requeue,
seek: Arc::new(SeekControl::default()),
replay,
}
}
pub(crate) fn finish(&self) {
let mut inner = self.lock();
inner.closed = true;
if self.end_with_eos {
inner.marked = true;
inner.registry.end_all();
}
}
fn request_seek(
&self,
id: SubscriptionId,
stream: &str,
to: FilePosition,
control: &SeekControl,
replay: bool,
) -> Result<(), SeaFileError> {
let refused = |why: &str| SeaFileError::Seek {
stream: stream.to_owned(),
source: Box::from(why),
};
let mut inner = self.lock();
if inner.closed {
return Err(refused("the broker has shut down"));
}
if control.ended() {
return Err(refused("the subscription has been closed"));
}
if !replay && inner.marked {
return Err(refused("the stream ended at its end-of-stream mark"));
}
let entries = inner
.streams
.get(stream)
.map(Vec::as_slice)
.unwrap_or_default();
let target = match resolve(entries, to) {
Ok(target) => target,
Err(Unresolved::Invalid(why)) => return Err(SeaFileError::Invalid(why)),
Err(Unresolved::Refused(why)) if replay => return Err(refused(why)),
Err(Unresolved::Refused(why)) => {
inner.registry.close(id);
drop(inner);
control.ended.store(true, Ordering::Release);
control.waker.wake();
return Err(refused(why));
}
};
let replay: Vec<SharedMessage> = entries
.iter()
.skip(usize::try_from(target.saturating_sub(1)).unwrap_or(usize::MAX))
.cloned()
.collect();
if let Some(coordinator) = self.coordinator() {
for _ in &replay {
coordinator.enqueued();
}
}
control.watermark.store(target, Ordering::Release);
*control
.pending
.lock()
.unwrap_or_else(PoisonError::into_inner) = Some(replay);
drop(inner);
control.waker.wake();
Ok(())
}
fn finish_replay(
&self,
id: SubscriptionId,
rx: &mut EntryReceiver,
control: &SeekControl,
) -> ReplayEnd {
let mut inner = self.lock();
if control.has_pending() {
return ReplayEnd::Seek;
}
if let Ok(entry) = rx.try_recv() {
return ReplayEnd::Next(entry);
}
inner.registry.close(id);
ReplayEnd::Done
}
fn unsubscribe(&self, id: SubscriptionId) {
self.lock().registry.close(id);
}
}
enum ReplayEnd {
Next(Entry),
Seek,
Done,
}
#[derive(Default)]
struct SeekControl {
pending: Mutex<Option<Vec<SharedMessage>>>,
watermark: AtomicU64,
ended: AtomicBool,
waker: AtomicWaker,
}
impl SeekControl {
fn watermark(&self) -> u64 {
self.watermark.load(Ordering::Acquire)
}
fn ended(&self) -> bool {
self.ended.load(Ordering::Acquire)
}
fn has_pending(&self) -> bool {
self.pending
.lock()
.unwrap_or_else(PoisonError::into_inner)
.is_some()
}
fn take_pending(&self) -> Option<Vec<SharedMessage>> {
self.pending
.lock()
.unwrap_or_else(PoisonError::into_inner)
.take()
}
}
#[derive(Clone)]
pub(crate) struct LogSeeker {
file: Arc<MemoryFile>,
id: SubscriptionId,
stream: Arc<str>,
control: Arc<SeekControl>,
replay: bool,
}
impl LogSeeker {
pub(crate) fn request(&self, to: FilePosition) -> Result<(), SeaFileError> {
self.file
.request_seek(self.id, &self.stream, to, &self.control, self.replay)
}
}
pub(crate) struct FileQueue {
file: Arc<MemoryFile>,
id: SubscriptionId,
stream: Arc<str>,
rx: EntryReceiver,
requeue: EntrySender,
seek: Arc<SeekControl>,
replay: bool,
}
impl FileQueue {
pub(crate) fn stream(&self) -> &Arc<str> {
&self.stream
}
pub(crate) fn seeker(&self) -> LogSeeker {
LogSeeker {
file: Arc::clone(&self.file),
id: self.id,
stream: Arc::clone(&self.stream),
control: Arc::clone(&self.seek),
replay: self.replay,
}
}
pub(crate) fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<SeaMessage>> {
self.seek.waker.register(cx.waker());
if self.seek.ended() {
return Poll::Ready(None);
}
self.apply_pending_seek();
loop {
let entry = match self.rx.poll_recv(cx) {
Poll::Ready(Some(entry)) => entry,
Poll::Ready(None) => return Poll::Ready(None),
Poll::Pending if self.replay => {
match self.file.finish_replay(self.id, &mut self.rx, &self.seek) {
ReplayEnd::Next(entry) => entry,
ReplayEnd::Seek => {
self.apply_pending_seek();
continue;
}
ReplayEnd::Done => return Poll::Ready(None),
}
}
Poll::Pending => return Poll::Pending,
};
match entry {
Entry::End => return Poll::Ready(None),
Entry::Message(message) if *message.header().sequence() < self.seek.watermark() => {
release_one(self.file.coordinator());
}
Entry::Message(message) => {
return Poll::Ready(Some(
SeaMessage::new(&message).released_by(Release::of(self.file.coordinator())),
));
}
}
}
}
fn apply_pending_seek(&mut self) {
let Some(replay) = self.seek.take_pending() else {
return;
};
let cutoff = replay.last().map_or_else(
|| self.seek.watermark(),
|last| *last.header().sequence() + 1,
);
let mut arrived_after = Vec::new();
while let Ok(entry) = self.rx.try_recv() {
match entry {
Entry::Message(message) if *message.header().sequence() < cutoff => {
release_one(self.file.coordinator());
}
entry => arrived_after.push(entry),
}
}
for entry in replay.into_iter().map(Entry::Message).chain(arrived_after) {
let _ = self.requeue.send(entry);
}
}
}
impl Drop for FileQueue {
fn drop(&mut self) {
self.seek.ended.store(true, Ordering::Release);
self.file.unsubscribe(self.id);
release_queued(&mut self.rx, self.file.coordinator());
}
}
enum Unresolved {
Refused(&'static str),
Invalid(String),
}
fn resolve(entries: &[SharedMessage], to: FilePosition) -> Result<u64, Unresolved> {
let retained = entries.len() as u64;
match to {
FilePosition::Beginning => Ok(1),
FilePosition::End => Ok(retained + 1),
FilePosition::Sequence(sequence) if sequence <= retained + 1 => Ok(sequence.max(1)),
FilePosition::Sequence(_) => Err(Unresolved::Refused("no message carries that sequence")),
FilePosition::Timestamp(millis) => {
let instant = Timestamp::from_unix_timestamp_nanos(i128::from(millis) * 1_000_000)
.map_err(|err| {
Unresolved::Invalid(format!("'{millis}' is not a valid timestamp: {err}"))
})?;
entries
.iter()
.position(|message| *message.header().timestamp() > instant)
.map(|index| index as u64 + 1)
.ok_or(Unresolved::Refused("no message is later than that instant"))
}
}
}