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::general_purpose::STANDARD as BASE64_STANDARD;
 | 
			
		||||
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::{
 | 
			
		||||
    clients::{Destination, MyceliumClient, SupervisorClient},
 | 
			
		||||
@@ -23,6 +25,88 @@ pub struct RouterConfig {
 | 
			
		||||
    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.
 | 
			
		||||
/// Each loop:
 | 
			
		||||
/// - 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 {
 | 
			
		||||
                // Pop next message key (blocking with timeout)
 | 
			
		||||
                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();
 | 
			
		||||
                        tokio::spawn({
 | 
			
		||||
                            let mycelium = mycelium.clone();
 | 
			
		||||
                            let cache = cache.clone();
 | 
			
		||||
                            async move {
 | 
			
		||||
                                // Ensure permit is dropped at end of task
 | 
			
		||||
                                let _permit = permit;
 | 
			
		||||
                                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
 | 
			
		||||
                                {
 | 
			
		||||
                                    error!(context_id=ctx_id, key=%key, error=%e, "Delivery error");
 | 
			
		||||
@@ -104,6 +191,7 @@ async fn deliver_one(
 | 
			
		||||
    context_id: u32,
 | 
			
		||||
    msg_key: &str,
 | 
			
		||||
    mycelium: Arc<MyceliumClient>,
 | 
			
		||||
    cache: Arc<SupervisorClientCache>,
 | 
			
		||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
 | 
			
		||||
    // Parse "message:{caller_id}:{id}"
 | 
			
		||||
    let (caller_id, id) = parse_message_key(msg_key)
 | 
			
		||||
@@ -143,12 +231,14 @@ async fn deliver_one(
 | 
			
		||||
    let dest_for_poller = dest.clone();
 | 
			
		||||
    let topic_for_poller = cfg.topic.clone();
 | 
			
		||||
    let secret_for_poller = runner.secret.clone();
 | 
			
		||||
    let client = SupervisorClient::new_with_client(
 | 
			
		||||
        mycelium.clone(),
 | 
			
		||||
        dest.clone(),
 | 
			
		||||
        cfg.topic.clone(),
 | 
			
		||||
        runner.secret.clone(),
 | 
			
		||||
    );
 | 
			
		||||
    let client = cache
 | 
			
		||||
        .get_or_create(
 | 
			
		||||
            mycelium.clone(),
 | 
			
		||||
            dest.clone(),
 | 
			
		||||
            cfg.topic.clone(),
 | 
			
		||||
            runner.secret.clone(),
 | 
			
		||||
        )
 | 
			
		||||
        .await;
 | 
			
		||||
 | 
			
		||||
    // Build supervisor method and params from Message
 | 
			
		||||
    let method = msg.message.clone();
 | 
			
		||||
@@ -251,12 +341,14 @@ async fn deliver_one(
 | 
			
		||||
                            // if matches!(s, TransportStatus::Read)
 | 
			
		||||
                            // && let Some(job_id) = job_id_opt
 | 
			
		||||
                            if let Some(job_id) = job_id_opt {
 | 
			
		||||
                                let sup = SupervisorClient::new_with_client(
 | 
			
		||||
                                    client.clone(),
 | 
			
		||||
                                    sup_dest.clone(),
 | 
			
		||||
                                    sup_topic.clone(),
 | 
			
		||||
                                    secret_for_poller.clone(),
 | 
			
		||||
                                );
 | 
			
		||||
                                let sup = cache
 | 
			
		||||
                                    .get_or_create(
 | 
			
		||||
                                        client.clone(),
 | 
			
		||||
                                        sup_dest.clone(),
 | 
			
		||||
                                        sup_topic.clone(),
 | 
			
		||||
                                        secret_for_poller.clone(),
 | 
			
		||||
                                    )
 | 
			
		||||
                                    .await;
 | 
			
		||||
                                match sup.job_status_with_ids(job_id.to_string()).await {
 | 
			
		||||
                                    Ok((_out_id, inner_id)) => {
 | 
			
		||||
                                        // Correlate this status request to the message/job
 | 
			
		||||
@@ -425,6 +517,8 @@ pub fn start_inbound_listener(
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let cache = Arc::new(SupervisorClientCache::new());
 | 
			
		||||
 | 
			
		||||
        loop {
 | 
			
		||||
            // Poll for inbound supervisor messages on the configured topic
 | 
			
		||||
            match mycelium.pop_message(Some(false), Some(20), None).await {
 | 
			
		||||
@@ -599,12 +693,14 @@ pub fn start_inbound_listener(
 | 
			
		||||
                                                                } else {
 | 
			
		||||
                                                                    Destination::Ip(runner.address)
 | 
			
		||||
                                                                };
 | 
			
		||||
                                                                let sup = SupervisorClient::new_with_client(
 | 
			
		||||
                                                                    mycelium.clone(),
 | 
			
		||||
                                                                    dest,
 | 
			
		||||
                                                                    cfg.topic.clone(),
 | 
			
		||||
                                                                    runner.secret.clone(),
 | 
			
		||||
                                                                );
 | 
			
		||||
                                                                let sup = cache
 | 
			
		||||
                                                                    .get_or_create(
 | 
			
		||||
                                                                        mycelium.clone(),
 | 
			
		||||
                                                                        dest,
 | 
			
		||||
                                                                        cfg.topic.clone(),
 | 
			
		||||
                                                                        runner.secret.clone(),
 | 
			
		||||
                                                                    )
 | 
			
		||||
                                                                    .await;
 | 
			
		||||
                                                                match sup
 | 
			
		||||
                                                                    .job_result_with_ids(
 | 
			
		||||
                                                                        job_id.to_string(),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user