Use single cached supervisorclient
Signed-off-by: Lee Smet <lee.smet@hotmail.com>
This commit is contained in:
138
src/router.rs
138
src/router.rs
@@ -1,9 +1,11 @@
|
|||||||
use std::{collections::HashSet, sync::Arc};
|
use std::{collections::{HashSet, HashMap}, sync::Arc};
|
||||||
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
use tokio::sync::Semaphore;
|
use tokio::sync::{Semaphore, Mutex};
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
clients::{Destination, MyceliumClient, SupervisorClient},
|
clients::{Destination, MyceliumClient, SupervisorClient},
|
||||||
@@ -23,6 +25,88 @@ pub struct RouterConfig {
|
|||||||
pub transport_poll_timeout_secs: u64, // e.g. 300 (5 minutes)
|
pub transport_poll_timeout_secs: u64, // e.g. 300 (5 minutes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
SupervisorClient reuse cache (Router-local):
|
||||||
|
|
||||||
|
Rationale:
|
||||||
|
- SupervisorClient maintains an internal JSON-RPC id_counter per instance.
|
||||||
|
- Rebuilding a client for each message resets this counter, causing inner JSON-RPC ids to restart at 1.
|
||||||
|
- We reuse one SupervisorClient per (destination, topic, secret) to preserve monotonically increasing ids.
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- Cache is per Router loop (and a separate one for the inbound listener).
|
||||||
|
- If cross-loop/process reuse becomes necessary later, promote to a process-global cache.
|
||||||
|
|
||||||
|
Keying:
|
||||||
|
- Key: destination + topic + secret-presence (secret content hashed; not stored in plaintext).
|
||||||
|
|
||||||
|
Concurrency:
|
||||||
|
- tokio::Mutex protects a HashMap<String, Arc<SupervisorClient>>.
|
||||||
|
- Values are Arc so call sites clone cheaply and share the same id_counter.
|
||||||
|
*/
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct SupervisorClientCache {
|
||||||
|
map: Arc<Mutex<HashMap<String, Arc<SupervisorClient>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SupervisorClientCache {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
map: Arc::new(Mutex::new(HashMap::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_key(dest: &Destination, topic: &str, secret: &Option<String>) -> String {
|
||||||
|
let dst = match dest {
|
||||||
|
Destination::Ip(ip) => format!("ip:{ip}"),
|
||||||
|
Destination::Pk(pk) => format!("pk:{pk}"),
|
||||||
|
};
|
||||||
|
// Hash the secret to avoid storing plaintext in keys while still differentiating values
|
||||||
|
let sec_hash = match secret {
|
||||||
|
Some(s) if !s.is_empty() => {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
s.hash(&mut hasher);
|
||||||
|
format!("s:{}", hasher.finish())
|
||||||
|
}
|
||||||
|
_ => "s:none".to_string(),
|
||||||
|
};
|
||||||
|
format!("{dst}|t:{topic}|{sec_hash}")
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_or_create(
|
||||||
|
&self,
|
||||||
|
mycelium: Arc<MyceliumClient>,
|
||||||
|
dest: Destination,
|
||||||
|
topic: String,
|
||||||
|
secret: Option<String>,
|
||||||
|
) -> Arc<SupervisorClient> {
|
||||||
|
let key = Self::make_key(&dest, &topic, &secret);
|
||||||
|
|
||||||
|
{
|
||||||
|
let guard = self.map.lock().await;
|
||||||
|
if let Some(existing) = guard.get(&key) {
|
||||||
|
tracing::debug!(target: "router", cache="supervisor", hit=true, %topic, secret = %if secret.is_some() { "set" } else { "none" }, "SupervisorClient cache lookup");
|
||||||
|
return existing.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut guard = self.map.lock().await;
|
||||||
|
if let Some(existing) = guard.get(&key) {
|
||||||
|
tracing::debug!(target: "router", cache="supervisor", hit=true, %topic, secret = %if secret.is_some() { "set" } else { "none" }, "SupervisorClient cache lookup (double-checked)");
|
||||||
|
return existing.clone();
|
||||||
|
}
|
||||||
|
let client = Arc::new(SupervisorClient::new_with_client(
|
||||||
|
mycelium,
|
||||||
|
dest,
|
||||||
|
topic.clone(),
|
||||||
|
secret.clone(),
|
||||||
|
));
|
||||||
|
guard.insert(key, client.clone());
|
||||||
|
tracing::debug!(target: "router", cache="supervisor", hit=false, %topic, secret = %if secret.is_some() { "set" } else { "none" }, "SupervisorClient cache insert");
|
||||||
|
client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Start background router loops, one per context.
|
/// Start background router loops, one per context.
|
||||||
/// Each loop:
|
/// Each loop:
|
||||||
/// - BRPOP msg_out with 1s timeout
|
/// - BRPOP msg_out with 1s timeout
|
||||||
@@ -49,6 +133,8 @@ pub fn start_router(service: AppService, cfg: RouterConfig) -> Vec<tokio::task::
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let cache = Arc::new(SupervisorClientCache::new());
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Pop next message key (blocking with timeout)
|
// Pop next message key (blocking with timeout)
|
||||||
match service_cloned.brpop_msg_out(ctx_id, 1).await {
|
match service_cloned.brpop_msg_out(ctx_id, 1).await {
|
||||||
@@ -69,11 +155,12 @@ pub fn start_router(service: AppService, cfg: RouterConfig) -> Vec<tokio::task::
|
|||||||
let cfg_task = cfg_cloned.clone();
|
let cfg_task = cfg_cloned.clone();
|
||||||
tokio::spawn({
|
tokio::spawn({
|
||||||
let mycelium = mycelium.clone();
|
let mycelium = mycelium.clone();
|
||||||
|
let cache = cache.clone();
|
||||||
async move {
|
async move {
|
||||||
// Ensure permit is dropped at end of task
|
// Ensure permit is dropped at end of task
|
||||||
let _permit = permit;
|
let _permit = permit;
|
||||||
if let Err(e) =
|
if let Err(e) =
|
||||||
deliver_one(&service_task, &cfg_task, ctx_id, &key, mycelium)
|
deliver_one(&service_task, &cfg_task, ctx_id, &key, mycelium, cache.clone())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
error!(context_id=ctx_id, key=%key, error=%e, "Delivery error");
|
error!(context_id=ctx_id, key=%key, error=%e, "Delivery error");
|
||||||
@@ -104,6 +191,7 @@ async fn deliver_one(
|
|||||||
context_id: u32,
|
context_id: u32,
|
||||||
msg_key: &str,
|
msg_key: &str,
|
||||||
mycelium: Arc<MyceliumClient>,
|
mycelium: Arc<MyceliumClient>,
|
||||||
|
cache: Arc<SupervisorClientCache>,
|
||||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
// Parse "message:{caller_id}:{id}"
|
// Parse "message:{caller_id}:{id}"
|
||||||
let (caller_id, id) = parse_message_key(msg_key)
|
let (caller_id, id) = parse_message_key(msg_key)
|
||||||
@@ -143,12 +231,14 @@ async fn deliver_one(
|
|||||||
let dest_for_poller = dest.clone();
|
let dest_for_poller = dest.clone();
|
||||||
let topic_for_poller = cfg.topic.clone();
|
let topic_for_poller = cfg.topic.clone();
|
||||||
let secret_for_poller = runner.secret.clone();
|
let secret_for_poller = runner.secret.clone();
|
||||||
let client = SupervisorClient::new_with_client(
|
let client = cache
|
||||||
mycelium.clone(),
|
.get_or_create(
|
||||||
dest.clone(),
|
mycelium.clone(),
|
||||||
cfg.topic.clone(),
|
dest.clone(),
|
||||||
runner.secret.clone(),
|
cfg.topic.clone(),
|
||||||
);
|
runner.secret.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
// Build supervisor method and params from Message
|
// Build supervisor method and params from Message
|
||||||
let method = msg.message.clone();
|
let method = msg.message.clone();
|
||||||
@@ -251,12 +341,14 @@ async fn deliver_one(
|
|||||||
// if matches!(s, TransportStatus::Read)
|
// if matches!(s, TransportStatus::Read)
|
||||||
// && let Some(job_id) = job_id_opt
|
// && let Some(job_id) = job_id_opt
|
||||||
if let Some(job_id) = job_id_opt {
|
if let Some(job_id) = job_id_opt {
|
||||||
let sup = SupervisorClient::new_with_client(
|
let sup = cache
|
||||||
client.clone(),
|
.get_or_create(
|
||||||
sup_dest.clone(),
|
client.clone(),
|
||||||
sup_topic.clone(),
|
sup_dest.clone(),
|
||||||
secret_for_poller.clone(),
|
sup_topic.clone(),
|
||||||
);
|
secret_for_poller.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
match sup.job_status_with_ids(job_id.to_string()).await {
|
match sup.job_status_with_ids(job_id.to_string()).await {
|
||||||
Ok((_out_id, inner_id)) => {
|
Ok((_out_id, inner_id)) => {
|
||||||
// Correlate this status request to the message/job
|
// Correlate this status request to the message/job
|
||||||
@@ -425,6 +517,8 @@ pub fn start_inbound_listener(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let cache = Arc::new(SupervisorClientCache::new());
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Poll for inbound supervisor messages on the configured topic
|
// Poll for inbound supervisor messages on the configured topic
|
||||||
match mycelium.pop_message(Some(false), Some(20), None).await {
|
match mycelium.pop_message(Some(false), Some(20), None).await {
|
||||||
@@ -599,12 +693,14 @@ pub fn start_inbound_listener(
|
|||||||
} else {
|
} else {
|
||||||
Destination::Ip(runner.address)
|
Destination::Ip(runner.address)
|
||||||
};
|
};
|
||||||
let sup = SupervisorClient::new_with_client(
|
let sup = cache
|
||||||
mycelium.clone(),
|
.get_or_create(
|
||||||
dest,
|
mycelium.clone(),
|
||||||
cfg.topic.clone(),
|
dest,
|
||||||
runner.secret.clone(),
|
cfg.topic.clone(),
|
||||||
);
|
runner.secret.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
match sup
|
match sup
|
||||||
.job_result_with_ids(
|
.job_result_with_ids(
|
||||||
job_id.to_string(),
|
job_id.to_string(),
|
||||||
|
Reference in New Issue
Block a user