use crate::bus::QueryFailure;
use crate::bus::{BusHandle, IncomingQuery};
use crate::participant::api::Participant;
use crate::participant::context::QueryContext;
use crate::participant::managed::{ManagedTaskPolicy, ManagedTasks};
use crate::participant::query::{QueryRegistration, ServerOutcome};
use std::time::Duration;
use tokio::sync::mpsc;
const REQUEST_QUEUE_DEPTH: usize = 64;
const REPLY_QUEUE_DEPTH: usize = 256;
pub(crate) struct QuerySurface<R: Participant> {
registrations: Vec<QueryRegistration<R>>,
requests: mpsc::Receiver<(usize, IncomingQuery)>,
replies: mpsc::Sender<PendingReply>,
}
struct PendingReply {
incoming: IncomingQuery,
outcome: ServerOutcome,
}
impl<R: Participant> QuerySurface<R> {
pub(crate) async fn declare(
bus: &BusHandle,
registrations: Vec<QueryRegistration<R>>,
managed_tasks: &mut ManagedTasks,
reply_delay: Option<Duration>,
) -> crate::Result<Option<Self>> {
if registrations.is_empty() {
return Ok(None);
}
let (sender, requests) = mpsc::channel(REQUEST_QUEUE_DEPTH);
let (reply_sender, mut reply_receiver) = mpsc::channel(REPLY_QUEUE_DEPTH);
let reply_bus = bus.clone();
managed_tasks.spawn("query-reply", ManagedTaskPolicy::Critical, async move {
while let Some(PendingReply { incoming, outcome }) = reply_receiver.recv().await {
if let Some(delay) = reply_delay {
tokio::time::sleep(delay).await;
}
let result = match outcome {
Ok(reply) => incoming.reply(&reply_bus, reply.payload).await,
Err(failure) => incoming.reply_err(&failure).await,
};
if let Err(error) = result {
tracing::debug!(
target: "phoxal.runtime",
error = %error,
"query reply transport failed"
);
}
}
Ok::<(), anyhow::Error>(())
});
for (index, registration) in registrations.iter().enumerate() {
let queryable = match bus.declare_server(registration.topic()).await {
Ok(queryable) => queryable,
Err(error) => {
return Err(error.into());
}
};
let sender = sender.clone();
let topic = registration.topic().to_string();
managed_tasks.spawn(
format!("query-ingest-{index}"),
ManagedTaskPolicy::Critical,
async move {
loop {
let incoming = queryable.recv().await.map_err(|error| {
anyhow::anyhow!("query ingest for {topic} terminated: {error}")
})?;
if sender.send((index, incoming)).await.is_err() {
return Ok::<(), anyhow::Error>(());
}
}
},
);
}
Ok(Some(QuerySurface {
registrations,
requests,
replies: reply_sender,
}))
}
pub(crate) async fn next_request(&mut self) -> (usize, IncomingQuery) {
match self.requests.recv().await {
Some(request) => request,
None => std::future::pending().await,
}
}
pub(crate) fn serve(
&self,
request: (usize, IncomingQuery),
participant: &R,
api: &R::Api,
state: &mut R::State,
) -> crate::Result<()> {
let (index, incoming) = request;
let Some(registration) = self.registrations.get(index) else {
return self.enqueue(
incoming,
Err(QueryFailure::internal("invalid query registration")),
);
};
let metadata = match incoming.request_metadata() {
Ok(metadata) => metadata,
Err(error) => {
return self.enqueue(
incoming,
Err(QueryFailure::invalid_argument(error.to_string())),
);
}
};
if metadata.codec_id().is_none() {
return self.enqueue(
incoming,
Err(QueryFailure::invalid_argument(format!(
"unsupported request codec id {}",
metadata.codec
))),
);
}
let query_context = QueryContext::new(metadata.source.producer());
let body = match incoming.request_bytes() {
Ok(bytes) => bytes,
Err(error) => {
return self.enqueue(
incoming,
Err(QueryFailure::invalid_argument(error.to_string())),
);
}
};
let outcome = registration.dispatch(participant, api, query_context, state, body);
self.enqueue(incoming, outcome)
}
fn enqueue(&self, incoming: IncomingQuery, outcome: ServerOutcome) -> crate::Result<()> {
self.replies.try_send(PendingReply { incoming, outcome }).map_err(|error| {
anyhow::anyhow!(
"query reply queue is saturated or closed; refusing to stall the serialized runner: {error}"
)
})
}
pub(crate) fn close(self) {}
}