44//! after `types_registry` has switched to ready mode.
55
66use std:: sync:: Arc ;
7+ use std:: time:: Duration ;
78
89use modkit:: client_hub:: { ClientHub , ClientScope } ;
910use modkit:: gts:: BaseModkitPluginV1 ;
11+ use modkit:: plugins:: GtsPluginSelector ;
12+ use modkit:: telemetry:: ThrottledLog ;
1013use modkit_odata:: { ODataQuery , Page } ;
1114use modkit_security:: SecurityContext ;
1215use tenant_resolver_sdk:: {
1316 AccessOptions , GetParentsResponse , Tenant , TenantFilter , TenantResolverPluginClient ,
1417 TenantResolverPluginSpecV1 ,
1518} ;
16- use tokio:: sync:: OnceCell ;
1719use tracing:: info;
1820use types_registry_sdk:: { GtsEntity , ListQuery , TypesRegistryClient } ;
1921
@@ -22,21 +24,20 @@ use types_registry_sdk::{GtsEntity, ListQuery, TypesRegistryClient};
2224
2325use crate :: domain:: error:: DomainError ;
2426
25- /// Cached result of plugin resolution.
26- struct ResolvedPlugin {
27- gts_id : String ,
28- scope : ClientScope ,
29- }
27+ /// Throttle interval for unavailable plugin warnings.
28+ const UNAVAILABLE_LOG_THROTTLE : Duration = Duration :: from_secs ( 10 ) ;
3029
3130/// Tenant Resolver Gateway service.
3231///
3332/// Holds a reference to `ClientHub` and the configured vendor.
34- /// Plugin discovery is lazy and cached via `OnceCell `.
33+ /// Plugin discovery is lazy and cached via `GtsPluginSelector `.
3534pub struct Service {
3635 hub : Arc < ClientHub > ,
3736 vendor : String ,
38- /// Lazily resolved plugin (cached after first call).
39- resolved : OnceCell < ResolvedPlugin > ,
37+ /// Shared selector for plugin instance IDs.
38+ selector : GtsPluginSelector ,
39+ /// Throttle for plugin unavailable warnings.
40+ unavailable_log_throttle : ThrottledLog ,
4041}
4142
4243impl Service {
@@ -46,7 +47,8 @@ impl Service {
4647 Self {
4748 hub,
4849 vendor,
49- resolved : OnceCell :: new ( ) ,
50+ selector : GtsPluginSelector :: new ( ) ,
51+ unavailable_log_throttle : ThrottledLog :: new ( UNAVAILABLE_LOG_THROTTLE ) ,
5052 }
5153 }
5254
@@ -55,21 +57,32 @@ impl Service {
5557 /// On first call, queries `types_registry` to find the plugin instance
5658 /// matching the configured vendor. Result is cached for subsequent calls.
5759 async fn get_plugin ( & self ) -> Result < Arc < dyn TenantResolverPluginClient > , DomainError > {
58- let resolved = self
59- . resolved
60- . get_or_try_init ( || self . resolve_plugin ( ) )
61- . await ?;
60+ let instance_id = self . selector . get_or_init ( || self . resolve_plugin ( ) ) . await ?;
61+ let scope = ClientScope :: gts_id ( instance_id. as_ref ( ) ) ;
6262
63- self . hub
64- . get_scoped :: < dyn TenantResolverPluginClient > ( & resolved. scope )
65- . map_err ( |_| DomainError :: PluginClientNotFound {
66- gts_id : resolved. gts_id . clone ( ) ,
63+ if let Some ( client) = self
64+ . hub
65+ . try_get_scoped :: < dyn TenantResolverPluginClient > ( & scope)
66+ {
67+ Ok ( client)
68+ } else {
69+ if self . unavailable_log_throttle . should_log ( ) {
70+ tracing:: warn!(
71+ plugin_gts_id = %instance_id,
72+ vendor = %self . vendor,
73+ "Plugin client not registered yet"
74+ ) ;
75+ }
76+ Err ( DomainError :: PluginUnavailable {
77+ gts_id : instance_id. to_string ( ) ,
78+ reason : "client not registered yet" . into ( ) ,
6779 } )
80+ }
6881 }
6982
7083 /// Resolves the plugin instance from `types_registry`.
7184 #[ tracing:: instrument( skip_all, fields( vendor = %self . vendor) ) ]
72- async fn resolve_plugin ( & self ) -> Result < ResolvedPlugin , DomainError > {
85+ async fn resolve_plugin ( & self ) -> Result < String , DomainError > {
7386 info ! ( "Resolving tenant resolver plugin" ) ;
7487
7588 let registry = self
@@ -90,8 +103,7 @@ impl Service {
90103 let gts_id = choose_plugin_instance ( & self . vendor , & instances) ?;
91104 info ! ( plugin_gts_id = %gts_id, "Selected tenant resolver plugin instance" ) ;
92105
93- let scope = ClientScope :: gts_id ( & gts_id) ;
94- Ok ( ResolvedPlugin { gts_id, scope } )
106+ Ok ( gts_id)
95107 }
96108
97109 /// Returns the root tenant.
0 commit comments