diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f4cfcbbc..a9c22573 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -156,6 +156,7 @@ async fn cmd_grpc_reflect( request_id: &str, environment_id: Option<&str>, proto_files: Vec, + skip_cache: Option, window: WebviewWindow, app_handle: AppHandle, grpc_handle: State<'_, Mutex>, @@ -196,6 +197,7 @@ async fn cmd_grpc_reflect( &proto_files.iter().map(|p| PathBuf::from_str(p).unwrap()).collect(), &metadata, workspace.setting_validate_certificates, + skip_cache.unwrap_or(false), ) .await .map_err(|e| GenericError(e.to_string()))?) diff --git a/src-tauri/yaak-grpc/src/manager.rs b/src-tauri/yaak-grpc/src/manager.rs index 4aed8050..90f95f5d 100644 --- a/src-tauri/yaak-grpc/src/manager.rs +++ b/src-tauri/yaak-grpc/src/manager.rs @@ -7,7 +7,7 @@ use crate::{MethodDefinition, ServiceDefinition, json_schema}; use hyper_rustls::HttpsConnector; use hyper_util::client::legacy::Client; use hyper_util::client::legacy::connect::HttpConnector; -use log::warn; +use log::{info, warn}; pub use prost_reflect::DynamicMessage; use prost_reflect::{DescriptorPool, MethodDescriptor, ServiceDescriptor}; use serde_json::Deserializer; @@ -244,6 +244,12 @@ impl GrpcHandle { } impl GrpcHandle { + /// Remove cached descriptor pool for the given key, if present. + pub fn invalidate_pool(&mut self, id: &str, uri: &str, proto_files: &Vec) { + let key = make_pool_key(id, uri, proto_files); + self.pools.remove(&key); + } + pub async fn reflect( &mut self, id: &str, @@ -253,6 +259,13 @@ impl GrpcHandle { validate_certificates: bool, ) -> Result { let server_reflection = proto_files.is_empty(); + let key = make_pool_key(id, uri, proto_files); + + // If we already have a pool for this key, reuse it and avoid re-reflection + if self.pools.contains_key(&key) { + return Ok(server_reflection); + } + let pool = if server_reflection { let full_uri = uri_from_str(uri)?; fill_pool_from_reflection(&full_uri, metadata, validate_certificates).await @@ -260,7 +273,7 @@ impl GrpcHandle { fill_pool_from_files(&self.app_handle, proto_files).await }?; - self.pools.insert(make_pool_key(id, uri, proto_files), pool.clone()); + self.pools.insert(key, pool.clone()); Ok(server_reflection) } @@ -271,9 +284,13 @@ impl GrpcHandle { proto_files: &Vec, metadata: &BTreeMap, validate_certificates: bool, + skip_cache: bool, ) -> Result, String> { - // Ensure reflection is up-to-date - self.reflect(id, uri, proto_files, metadata, validate_certificates).await?; + // Ensure we have a pool; reflect only if missing + if skip_cache || self.get_pool(id, uri, proto_files).is_none() { + info!("Reflecting gRPC services for {} at {}", id, uri); + self.reflect(id, uri, proto_files, metadata, validate_certificates).await?; + } let pool = self.get_pool(id, uri, proto_files).ok_or("Failed to get pool".to_string())?; Ok(self.services_from_pool(&pool)) @@ -312,8 +329,10 @@ impl GrpcHandle { metadata: &BTreeMap, validate_certificates: bool, ) -> Result { - let use_reflection = + let use_reflection = proto_files.is_empty(); + if self.get_pool(id, uri, proto_files).is_none() { self.reflect(id, uri, proto_files, metadata, validate_certificates).await?; + } let pool = self.get_pool(id, uri, proto_files).ok_or("Failed to get pool")?.clone(); let uri = uri_from_str(uri)?; let conn = get_transport(validate_certificates); diff --git a/src-web/hooks/useGrpc.ts b/src-web/hooks/useGrpc.ts index ac91fdae..cf2859ad 100644 --- a/src-web/hooks/useGrpc.ts +++ b/src-web/hooks/useGrpc.ts @@ -47,10 +47,14 @@ export function useGrpc( const reflect = useQuery({ enabled: req != null, queryKey: ['grpc_reflect', req?.id ?? 'n/a', debouncedUrl, protoFiles], + staleTime: Infinity, + refetchOnMount: false, + refetchOnWindowFocus: false, + refetchOnReconnect: false, queryFn: () => { const environmentId = jotaiStore.get(activeEnvironmentIdAtom); return minPromiseMillis( - invokeCmd('cmd_grpc_reflect', { requestId, protoFiles, environmentId }), + invokeCmd('cmd_grpc_reflect', { requestId, protoFiles, environmentId, skipCache: true }), 300, ); },