use std::collections::VecDeque;
use std::sync::Arc;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::time::Duration;
use async_nats::jetstream;
use futures::StreamExt;
use leptoskit_db::Db;
use leptoskit_db::DbError;
use leptoskit_db::live::LogEntry;
use super::Batch;
use super::Names;
use super::Relay;
use super::RelayError;
use super::entry_version;
const FILL: Duration = Duration::from_secs(10);
const RESUBSCRIBE: Duration = Duration::from_millis(500);
pub(crate) struct Feed {
relay: Relay,
db: Db,
names: Names,
version: i64,
ready: VecDeque<LogEntry>,
queued: i64,
messages: Option<jetstream::consumer::pull::Ordered>,
sequence: u64,
behind_since: Option<tokio::time::Instant>,
filled: Arc<AtomicU64>,
}
impl Feed {
pub(crate) async fn start(
relay: Relay,
db: Db,
version: i64,
filled: Arc<AtomicU64>,
) -> Result<Self, RelayError> {
let names = relay.names(&db).await?.clone();
let mut stream = relay.stream(&names).await?;
let sequence = stream
.info()
.await
.map_err(RelayError::new)?
.state
.last_sequence;
Ok(Self {
relay,
db,
names,
version,
ready: VecDeque::new(),
queued: version,
messages: None,
sequence,
behind_since: None,
filled,
})
}
pub(crate) fn version(&self) -> i64 {
self.version
}
pub(crate) fn next_ready(&mut self) -> Option<LogEntry> {
let entry = self.ready.pop_front()?;
self.version = entry_version(&entry);
Some(entry)
}
pub(crate) async fn next(&mut self) -> Result<LogEntry, DbError> {
loop {
if let Some(entry) = self.next_ready() {
return Ok(entry);
}
if self.messages.is_none() {
if let Ok(messages) = subscribe(&self.relay, &self.names, self.sequence).await {
self.messages = Some(messages);
} else {
self.check_database().await?;
tokio::time::sleep(self.relay.settings().stall.min(RESUBSCRIBE)).await;
continue;
}
}
let Some(messages) = &mut self.messages else {
continue;
};
let next = tokio::time::timeout(self.relay.settings().stall, messages.next()).await;
match next {
Ok(Some(Ok(message))) => {
self.behind_since = None;
if let Ok(info) = message.info() {
self.sequence = info.stream_sequence;
}
if let Ok(batch) = serde_json::from_slice::<Batch>(&message.payload) {
self.take(batch).await?;
}
}
Ok(Some(Err(_)) | None) => self.messages = None,
Err(_) => self.check_database().await?,
}
}
}
async fn take(&mut self, batch: Batch) -> Result<(), DbError> {
if batch.to <= self.queued {
return Ok(());
}
if batch.from > self.queued {
self.fill(batch.from).await?;
}
match batch.entries {
Some(entries) => {
for entry in entries {
if entry_version(&entry) > self.queued {
self.queued = entry_version(&entry);
self.ready.push_back(entry);
}
}
}
None => self.fill(batch.to).await?,
}
Ok(())
}
async fn fill(&mut self, to: i64) -> Result<(), DbError> {
let mut after = self.queued;
while after < to {
let page = tokio::time::timeout(FILL, self.db.change_log_after(after))
.await
.map_err(|_| {
DbError::Connection(Box::new(RelayError::new("reading a gap timed out")))
})??;
if page.is_empty() {
self.push(LogEntry::Pruned { through: to });
break;
}
for entry in page {
let version = entry_version(&entry);
if version > to {
return Ok(());
}
after = version;
self.push(match entry {
LogEntry::Pruned { through } => {
LogEntry::Pruned {
through: through.min(to),
}
}
commit @ LogEntry::Commit(_) => commit,
});
}
}
Ok(())
}
fn push(&mut self, entry: LogEntry) {
let version = entry_version(&entry);
if version > self.queued {
self.queued = version;
self.filled.fetch_add(1, Ordering::Relaxed);
self.ready.push_back(entry);
}
}
async fn check_database(&mut self) -> Result<(), DbError> {
let newest = self.db.live_version().await?;
if newest <= self.queued {
self.behind_since = None;
return Ok(());
}
let now = tokio::time::Instant::now();
let since = *self.behind_since.get_or_insert(now);
if now.duration_since(since) >= self.relay.settings().stall {
self.behind_since = None;
self.fill(newest).await?;
}
Ok(())
}
}
async fn subscribe(
relay: &Relay,
names: &Names,
sequence: u64,
) -> Result<jetstream::consumer::pull::Ordered, RelayError> {
let stream = relay.stream(names).await?;
let consumer = stream
.create_consumer(jetstream::consumer::pull::OrderedConfig {
filter_subject: names.subject.clone(),
deliver_policy: jetstream::consumer::DeliverPolicy::ByStartSequence {
start_sequence: sequence + 1,
},
..Default::default()
})
.await
.map_err(RelayError::new)?;
consumer.messages().await.map_err(RelayError::new)
}
impl std::fmt::Debug for Feed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Feed")
.field("version", &self.version)
.field("sequence", &self.sequence)
.finish_non_exhaustive()
}
}