use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
use sonic_rs;
use crate::bridge::envelope::{PhysicalPlan, Priority, Request, Response};
use crate::control::state::SharedState;
use crate::types::{DatabaseId, Lsn, ReadConsistency, RequestId, TenantId, TraceId, VShardId};
static BROADCAST_CALLS: AtomicU64 = AtomicU64::new(0);
pub fn broadcast_call_count() -> u64 {
BROADCAST_CALLS.load(Ordering::Relaxed)
}
pub(crate) fn broadcast_call_count_increment() {
BROADCAST_CALLS.fetch_add(1, Ordering::Relaxed);
}
fn broadcast_admission(plan: &PhysicalPlan) -> crate::bridge::envelope::Admission {
if crate::control::server::shared::write_admission::plan_is_write(plan) {
crate::bridge::envelope::Admission::Admitted
} else {
crate::bridge::envelope::Admission::Exempt(crate::bridge::envelope::ExemptReason::Read)
}
}
pub async fn broadcast_to_all_cores(
shared: &SharedState,
tenant_id: TenantId,
database_id: DatabaseId,
plan: PhysicalPlan,
trace_id: TraceId,
) -> crate::Result<Response> {
broadcast_to_all_cores_txn(shared, tenant_id, database_id, plan, trace_id, None).await
}
pub async fn broadcast_to_all_cores_txn(
shared: &SharedState,
tenant_id: TenantId,
database_id: DatabaseId,
plan: PhysicalPlan,
trace_id: TraceId,
txn_id: Option<crate::types::TxnId>,
) -> crate::Result<Response> {
let outcome = crate::control::server::exchange::gather_all_cores(
shared,
tenant_id,
database_id,
plan,
trace_id,
txn_id,
)
.await?;
Ok(Response {
request_id: RequestId::new(0),
status: crate::bridge::envelope::Status::Ok,
attempt: 1,
partial: false,
payload: crate::bridge::envelope::Payload::from_vec(outcome.merged_array),
watermark_lsn: outcome.watermark_lsn,
error_code: None,
read_set_valid: None,
read_version_lsn: crate::types::Lsn::ZERO,
write_set: Vec::new(),
})
}
pub async fn broadcast_count_to_all_cores(
shared: &SharedState,
tenant_id: TenantId,
database_id: DatabaseId,
plan: PhysicalPlan,
trace_id: TraceId,
count_key: &str,
) -> crate::Result<Response> {
BROADCAST_CALLS.fetch_add(1, Ordering::Relaxed);
let num_cores = shared
.dispatcher
.lock()
.unwrap_or_else(|p| p.into_inner())
.num_cores();
let mut receivers = Vec::with_capacity(num_cores);
for core_id in 0..num_cores {
let request_id = shared.next_request_id();
let vshard_id = VShardId::new(core_id as u32);
let admission = broadcast_admission(&plan);
let request = Request {
request_id,
tenant_id,
database_id,
vshard_id,
plan: plan.clone(),
deadline: Instant::now()
+ Duration::from_secs(shared.tuning.network.default_deadline_secs),
priority: Priority::Normal,
trace_id,
consistency: ReadConsistency::Strong,
idempotency_key: None,
event_source: crate::event::EventSource::User,
user_roles: Vec::new(),
user_id: None,
statement_digest: None,
txn_id: None,
wal_lsn: None,
resolved_now_ms: None,
admission,
};
let rx = shared.tracker.register(request_id);
shared
.dispatcher
.lock()
.unwrap_or_else(|p| p.into_inner())
.dispatch_to_core(core_id, request)?;
receivers.push(rx);
}
let mut total = 0usize;
let mut max_lsn = Lsn::ZERO;
let mut had_error = false;
let mut error_msg = String::new();
for mut rx in receivers {
let resp = tokio::time::timeout(
Duration::from_secs(shared.tuning.network.default_deadline_secs),
async { rx.recv().await.ok_or(()) },
)
.await
.map_err(|_| crate::Error::Dispatch {
detail: "broadcast count timeout".into(),
})?
.map_err(|_| crate::Error::Dispatch {
detail: "broadcast count channel closed".into(),
})?;
if resp.status == crate::bridge::envelope::Status::Error {
had_error = true;
if let Some(ref ec) = resp.error_code {
error_msg = format!("{ec:?}");
}
continue;
}
if resp.watermark_lsn > max_lsn {
max_lsn = resp.watermark_lsn;
}
total += decode_count_field(&resp.payload, count_key).unwrap_or(0);
}
if had_error && total == 0 {
return Err(crate::Error::Dispatch { detail: error_msg });
}
let mut map = std::collections::BTreeMap::new();
map.insert(count_key, total);
let payload = zerompk::to_msgpack_vec(&map).map_err(|e| crate::Error::Codec {
detail: format!("count response serialization: {e}"),
})?;
Ok(Response {
request_id: RequestId::new(0),
status: crate::bridge::envelope::Status::Ok,
attempt: 1,
partial: false,
payload: crate::bridge::envelope::Payload::from_vec(payload),
watermark_lsn: max_lsn,
error_code: None,
read_set_valid: None,
read_version_lsn: crate::types::Lsn::ZERO,
write_set: Vec::new(),
})
}
pub async fn broadcast_register_to_all_cores(
shared: &SharedState,
tenant_id: TenantId,
database_id: DatabaseId,
plan: PhysicalPlan,
trace_id: TraceId,
) -> crate::Result<()> {
let num_cores = shared
.dispatcher
.lock()
.unwrap_or_else(|p| p.into_inner())
.num_cores();
let mut receivers = Vec::with_capacity(num_cores);
for core_id in 0..num_cores {
let request_id = shared.next_request_id();
let vshard_id = VShardId::new(core_id as u32);
let admission = broadcast_admission(&plan);
let request = Request {
request_id,
tenant_id,
database_id,
vshard_id,
plan: plan.clone(),
deadline: Instant::now()
+ Duration::from_secs(shared.tuning.network.default_deadline_secs),
priority: Priority::Normal,
trace_id,
consistency: ReadConsistency::Strong,
idempotency_key: None,
event_source: crate::event::EventSource::User,
user_roles: Vec::new(),
user_id: None,
statement_digest: None,
txn_id: None,
wal_lsn: None,
resolved_now_ms: None,
admission,
};
let rx = shared.tracker.register(request_id);
shared
.dispatcher
.lock()
.unwrap_or_else(|p| p.into_inner())
.dispatch_to_core(core_id, request)?;
receivers.push((core_id, rx));
}
for (core_id, mut rx) in receivers {
let resp = tokio::time::timeout(
Duration::from_secs(shared.tuning.network.default_deadline_secs),
async { rx.recv().await.ok_or(()) },
)
.await
.map_err(|_| crate::Error::Dispatch {
detail: format!("schema register barrier timeout on core {core_id}"),
})?
.map_err(|_| crate::Error::Dispatch {
detail: format!("schema register barrier channel closed on core {core_id}"),
})?;
if resp.status == crate::bridge::envelope::Status::Error {
let code_detail = resp
.error_code
.map(|ec| format!("{ec:?}"))
.unwrap_or_else(|| "unknown".to_string());
return Err(crate::Error::Dispatch {
detail: format!(
"schema register barrier: core {core_id} returned error: {code_detail}"
),
});
}
}
tracing::info!(
target: "nodedb::schema_barrier",
num_cores,
tenant = tenant_id.as_u64(),
"schema_version_barrier_acquired",
);
Ok(())
}
fn decode_count_field(payload: &[u8], key: &str) -> Option<usize> {
if payload.is_empty() {
return Some(0);
}
let json = nodedb_types::json_from_msgpack(payload)
.ok()
.or_else(|| sonic_rs::from_slice::<serde_json::Value>(payload).ok())?;
json.get(key).and_then(|v| v.as_u64()).map(|v| v as usize)
}
#[cfg(test)]
mod tests {
use super::broadcast_call_count;
#[test]
fn call_count_readable() {
let before = broadcast_call_count();
assert!(before < u64::MAX);
}
}