use std::time::{Duration, Instant};
use crate::bridge::envelope::{PhysicalPlan, Priority, Request, Response, Status};
use crate::control::state::SharedState;
use crate::types::{DatabaseId, ReadConsistency, TenantId, TraceId, VShardId};
pub async fn dispatch_async(
state: &SharedState,
tenant_id: TenantId,
database_id: DatabaseId,
collection: &str,
plan: PhysicalPlan,
timeout: Duration,
) -> crate::Result<Vec<u8>> {
dispatch_async_with_source(
state,
tenant_id,
database_id,
collection,
plan,
timeout,
crate::event::EventSource::User,
)
.await
}
pub async fn dispatch_async_with_source(
state: &SharedState,
tenant_id: TenantId,
database_id: DatabaseId,
collection: &str,
plan: PhysicalPlan,
timeout: Duration,
event_source: crate::event::EventSource,
) -> crate::Result<Vec<u8>> {
let resp = dispatch_async_response_with_source(
state,
tenant_id,
database_id,
collection,
plan,
timeout,
event_source,
)
.await?;
if resp.status != Status::Ok {
let detail = resp
.error_code
.as_ref()
.map(|c| format!("{c:?}"))
.unwrap_or_else(|| String::from_utf8_lossy(&resp.payload).into_owned());
return Err(crate::Error::Internal { detail });
}
state.advance_tenant_write_hlc(tenant_id.as_u64());
Ok(resp.payload.to_vec())
}
pub(crate) async fn dispatch_async_response_with_source(
state: &SharedState,
tenant_id: TenantId,
database_id: DatabaseId,
collection: &str,
plan: PhysicalPlan,
timeout: Duration,
event_source: crate::event::EventSource,
) -> crate::Result<Response> {
let vshard_id = VShardId::from_collection_in_database(database_id, collection);
let request_id = state.next_request_id();
let request = Request {
request_id,
tenant_id,
database_id,
vshard_id,
plan,
deadline: Instant::now() + timeout,
priority: Priority::Normal,
trace_id: TraceId::generate(),
consistency: ReadConsistency::Strong,
idempotency_key: None,
event_source,
user_roles: Vec::new(),
user_id: None,
statement_digest: None,
txn_id: None,
wal_lsn: None,
resolved_now_ms: None,
admission: crate::bridge::envelope::Admission::Exempt(
crate::bridge::envelope::ExemptReason::AlreadyOrdered,
),
};
let mut rx = state.tracker.register(request_id);
match state.dispatcher.lock() {
Ok(mut d) => d.dispatch(request).map_err(|e| crate::Error::Internal {
detail: e.to_string(),
})?,
Err(p) => p
.into_inner()
.dispatch(request)
.map_err(|e| crate::Error::Internal {
detail: e.to_string(),
})?,
};
tokio::time::timeout(timeout, async { rx.recv().await.ok_or(()) })
.await
.map_err(|_| crate::Error::Internal {
detail: format!("dispatch timeout after {}ms", timeout.as_millis()),
})?
.map_err(|_| crate::Error::Internal {
detail: "response channel closed".into(),
})
}