From 6e0445865913517732d25961526357fe1aed6d94 Mon Sep 17 00:00:00 2001 From: wei <1415121722@qq.com> Date: Sun, 7 Sep 2025 12:24:28 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9Atemporal=20add=20keep-alive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/swqos/temporal.rs | 113 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 107 insertions(+), 6 deletions(-) diff --git a/src/swqos/temporal.rs b/src/swqos/temporal.rs index 924c26c..e14202b 100755 --- a/src/swqos/temporal.rs +++ b/src/swqos/temporal.rs @@ -14,6 +14,9 @@ use crate::swqos::SwqosClientTrait; use crate::{common::SolanaRpcClient, constants::swqos::NOZOMI_TIP_ACCOUNTS}; +use tokio::task::JoinHandle; +use std::sync::atomic::{AtomicBool, Ordering}; + #[derive(Clone)] pub struct TemporalClient { @@ -21,6 +24,8 @@ pub struct TemporalClient { pub endpoint: String, pub auth_token: String, pub http_client: Client, + pub ping_handle: Arc>>>, + pub stop_ping: Arc, } #[async_trait::async_trait] @@ -47,15 +52,93 @@ impl TemporalClient { pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self { let rpc_client = SolanaRpcClient::new(rpc_url); let http_client = Client::builder() - .pool_idle_timeout(Duration::from_secs(60)) - .pool_max_idle_per_host(64) - .tcp_keepalive(Some(Duration::from_secs(1200))) - .http2_keep_alive_interval(Duration::from_secs(15)) - .timeout(Duration::from_secs(10)) + // Due to ping mechanism, can extend connection pool idle timeout + .pool_idle_timeout(Duration::from_secs(300)) // 5 minutes, longer than ping interval + .pool_max_idle_per_host(32) // Reduce connections as they will be more stable + // TCP keepalive can be set longer as ping will actively maintain connections + .tcp_keepalive(Some(Duration::from_secs(300))) // 5 minutes + // HTTP/2 keepalive interval can be longer + .http2_keep_alive_interval(Duration::from_secs(30)) // 30 seconds + // Request timeout can be appropriately extended as connections are more stable + .timeout(Duration::from_secs(15)) // 15 seconds .connect_timeout(Duration::from_secs(5)) .build() .unwrap(); - Self { rpc_client: Arc::new(rpc_client), endpoint, auth_token, http_client } + + let client = Self { + rpc_client: Arc::new(rpc_client), + endpoint, + auth_token, + http_client, + ping_handle: Arc::new(tokio::sync::Mutex::new(None)), + stop_ping: Arc::new(AtomicBool::new(false)), + }; + + // Start ping task + let client_clone = client.clone(); + tokio::spawn(async move { + client_clone.start_ping_task().await; + }); + + client + } + + /// Start periodic ping task to keep connections active + async fn start_ping_task(&self) { + let endpoint = self.endpoint.clone(); + let auth_token = self.auth_token.clone(); + let http_client = self.http_client.clone(); + let stop_ping = self.stop_ping.clone(); + + let handle = tokio::spawn(async move { + let mut interval = tokio::time::interval(Duration::from_secs(60)); // Ping every 60 seconds + + loop { + interval.tick().await; + + if stop_ping.load(Ordering::Relaxed) { + break; + } + + // Send ping request + if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await { + eprintln!("Temporal ping request failed: {}", e); + } + } + }); + + // Update ping_handle - use Mutex to safely update + { + let mut ping_guard = self.ping_handle.lock().await; + if let Some(old_handle) = ping_guard.as_ref() { + old_handle.abort(); + } + *ping_guard = Some(handle); + } + } + + /// Send ping request to /ping endpoint + async fn send_ping_request(http_client: &Client, endpoint: &str, _auth_token: &str) -> Result<()> { + // Build ping URL (no auth token required for ping endpoint) + let ping_url = if endpoint.ends_with('/') { + format!("{}ping", endpoint) + } else { + format!("{}/ping", endpoint) + }; + + // Send GET request to /ping endpoint + let response = http_client.get(&ping_url) + .send() + .await?; + + if response.status().is_success() { + // ping successful, connection remains active + // Can optionally log, but to reduce noise, not printing here + } else { + eprintln!("Temporal ping request returned non-success status: {}", response.status()); + } + + Ok(()) } pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> { @@ -118,4 +201,22 @@ impl TemporalClient { } Ok(()) } +} + +impl Drop for TemporalClient { + fn drop(&mut self) { + // Ensure ping task stops when client is destroyed + self.stop_ping.store(true, Ordering::Relaxed); + + // Try to stop ping task immediately + // Use tokio::spawn to avoid blocking Drop + let ping_handle = self.ping_handle.clone(); + tokio::spawn(async move { + let mut ping_guard = ping_handle.lock().await; + if let Some(handle) = ping_guard.as_ref() { + handle.abort(); + } + *ping_guard = None; + }); + } } \ No newline at end of file