use std::{sync::Arc, time::Duration};
use crate::{
request::Request,
storage::{
AddOptions, BatchAddHandle, Lease, LeaseId, QueueOpInfo, ReclaimOptions, RequestQueue,
RequestSource, StorageError, StorageResult,
},
};
use super::SitemapRequestList;
pub struct RequestQueueWithSitemap {
inner: Arc<dyn RequestQueue>,
list: SitemapRequestList,
batch: usize,
drain: tokio::sync::Mutex<DrainState>,
}
#[derive(Default)]
struct DrainState {
pending_add: Option<Request>,
}
impl RequestQueueWithSitemap {
pub fn new(inner: Arc<dyn RequestQueue>, list: SitemapRequestList) -> Self {
Self {
inner,
list,
batch: 32,
drain: tokio::sync::Mutex::new(DrainState::default()),
}
}
pub fn batch_size(mut self, n: usize) -> Self {
self.batch = n.max(1);
self
}
async fn persist_after_batch(&self) {
if let Err(error) = self.list.persist().await {
tracing::warn!(%error, "sitemap tandem checkpoint failed");
}
}
}
#[async_trait::async_trait]
impl RequestQueue for RequestQueueWithSitemap {
async fn add(&self, req: Request, opts: AddOptions) -> StorageResult<QueueOpInfo> {
self.inner.add(req, opts).await
}
async fn add_batch(
&self,
reqs: Vec<RequestSource>,
opts: AddOptions,
) -> StorageResult<BatchAddHandle> {
self.inner.add_batch(reqs, opts).await
}
async fn fetch_next(&self) -> StorageResult<Option<Lease>> {
let mut drain = self.drain.lock().await;
loop {
if !self.inner.is_empty().await? {
return self.inner.fetch_next().await;
}
if drain.pending_add.is_none() && self.list.is_finished().await {
return self.inner.fetch_next().await;
}
for _ in 0..self.batch {
if drain.pending_add.is_none() {
drain.pending_add = match self.list.fetch_next_for_tandem().await {
Ok(request) => request,
Err(error) => {
self.persist_after_batch().await;
return Err(StorageError::Backend(anyhow::Error::new(error)));
}
};
if drain.pending_add.is_none() {
break;
}
}
let request = drain
.pending_add
.as_ref()
.expect("pending sitemap request exists")
.clone();
let _ = self.inner.add(request, AddOptions::default()).await?;
drain.pending_add = None;
}
self.persist_after_batch().await;
if drain.pending_add.is_none() && self.list.is_finished().await {
return self.inner.fetch_next().await;
}
}
}
async fn mark_handled(&self, lease: Lease) -> StorageResult<()> {
self.inner.mark_handled(lease).await
}
async fn reclaim(&self, lease: Lease, opts: ReclaimOptions) -> StorageResult<()> {
self.inner.reclaim(lease, opts).await
}
async fn renew(&self, lease_id: &LeaseId, extend_by: Duration) -> StorageResult<()> {
self.inner.renew(lease_id, extend_by).await
}
async fn abandon(&self, lease: Lease) -> StorageResult<()> {
self.inner.abandon(lease).await
}
async fn is_empty(&self) -> StorageResult<bool> {
let drain = self.drain.lock().await;
if drain.pending_add.is_some() {
return Ok(false);
}
if !self.list.is_finished().await {
return Ok(false);
}
self.inner.is_empty().await
}
async fn is_finished(&self) -> StorageResult<bool> {
let drain = self.drain.lock().await;
if drain.pending_add.is_some() {
return Ok(false);
}
if !self.list.is_finished().await {
return Ok(false);
}
self.inner.is_finished().await
}
async fn handled_count(&self) -> StorageResult<u64> {
self.inner.handled_count().await
}
async fn pending_count(&self) -> StorageResult<u64> {
self.inner.pending_count().await
}
}