Skip to main content

relay_server/
service.rs

1use std::convert::Infallible;
2use std::sync::Arc;
3use std::time::Duration;
4
5use crate::metrics::MetricOutcomes;
6use crate::services::autoscaling::{AutoscalingMetricService, AutoscalingMetrics};
7use crate::services::buffer::{
8    ObservableEnvelopeBuffer, PartitionedEnvelopeBuffer, ProjectKeyPair,
9};
10use crate::services::cogs::{CogsService, CogsServiceRecorder};
11use crate::services::config_reload::ConfigReloadService;
12use crate::services::global_config::{
13    GlobalConfigHandle, GlobalConfigManager, GlobalConfigService,
14};
15use crate::services::health_check::{HealthCheck, HealthCheckService};
16use crate::services::metrics::RouterService;
17#[cfg(feature = "processing")]
18use crate::services::objectstore::{self, Objectstore};
19use crate::services::outcome::{
20    ClientReportOutcomeProducerService, NullOutcomeProducerService, OutcomeProducerService,
21    TrackOutcome,
22};
23use crate::services::processor::{self, EnvelopeProcessor};
24use crate::services::projects::cache::{ProjectCacheHandle, ProjectCacheService};
25use crate::services::projects::source::ProjectSource;
26use crate::services::proxy_processor::{ProxyAddrs, ProxyProcessorService};
27use crate::services::relays::{RelayCache, RelayCacheService};
28use crate::services::stats::RelayStats;
29#[cfg(feature = "processing")]
30use crate::services::store::{self, create_kafka_client};
31use crate::services::upload::{self, Upload};
32use crate::services::upstream::{UpstreamRelay, UpstreamRelayService};
33use crate::utils::{MemoryChecker, MemoryStat, ThreadKind};
34#[cfg(feature = "processing")]
35use anyhow::Context;
36use anyhow::Result;
37use axum::extract::FromRequestParts;
38use axum::http::request::Parts;
39use relay_cogs::Cogs;
40use relay_config::{Config, ConfigSnapshot, EmitOutcomes, RelayMode};
41#[cfg(feature = "processing")]
42use relay_config::{RedisConfigRef, RedisConfigsRef};
43#[cfg(feature = "processing")]
44use relay_redis::AsyncRedisClient;
45#[cfg(feature = "processing")]
46use relay_redis::redis::Script;
47#[cfg(feature = "processing")]
48use relay_redis::{RedisClients, RedisError, RedisScripts};
49use relay_system::{Addr, AssertEmpty, Service, ServiceSpawn, ServiceSpawnExt as _, channel};
50use relay_threading::ServicePool;
51
52/// Indicates the type of failure of the server.
53#[derive(Debug, thiserror::Error)]
54pub enum ServiceError {
55    /// GeoIp construction failed.
56    #[error("could not load the Geoip Db")]
57    GeoIp,
58
59    /// Initializing the Kafka producer failed.
60    #[cfg(feature = "processing")]
61    #[error("could not initialize kafka producer: {0}")]
62    Kafka(String),
63
64    /// Initializing the Redis client failed.
65    #[cfg(feature = "processing")]
66    #[error("could not initialize redis client during startup")]
67    Redis,
68}
69
70#[derive(Clone, Debug)]
71pub struct Registry {
72    pub cogs: Cogs,
73    pub health_check: Addr<HealthCheck>,
74    pub outcome_aggregator: Addr<TrackOutcome>,
75    pub processor: Addr<EnvelopeProcessor>,
76    pub relay_cache: Addr<RelayCache>,
77    pub global_config: Addr<GlobalConfigManager>,
78    pub upstream_relay: Addr<UpstreamRelay>,
79    pub envelope_buffer: Arc<PartitionedEnvelopeBuffer>,
80    pub project_cache_handle: ProjectCacheHandle,
81    pub autoscaling: Option<Addr<AutoscalingMetrics>>,
82    #[cfg(feature = "processing")]
83    pub objectstore: Option<Addr<Objectstore>>,
84    pub upload: Addr<Upload>,
85    pub global_config_handle: GlobalConfigHandle,
86}
87
88/// Constructs a Tokio [`relay_system::Runtime`] configured for running [services](relay_system::Service).
89pub fn create_runtime(name: &'static str, threads: usize) -> relay_system::Runtime {
90    relay_system::Runtime::builder(name)
91        .worker_threads(threads)
92        // Relay uses `spawn_blocking` only for Redis connections within the project
93        // cache, those should never exceed 100 concurrent connections
94        // (limited by connection pool).
95        //
96        // Relay also does not use other blocking operations from Tokio which require
97        // this pool, no usage of `tokio::fs` and `tokio::io::{Stdin, Stdout, Stderr}`.
98        //
99        // We limit the maximum amount of threads here, we've seen that Tokio
100        // expands this pool very very aggressively and basically never shrinks it
101        // which leads to a massive resource waste.
102        .max_blocking_threads(150)
103        // We also lower down the default (10s) keep alive timeout for blocking
104        // threads to encourage the runtime to not keep too many idle blocking threads
105        // around.
106        .thread_keep_alive(Duration::from_secs(1))
107        .build()
108}
109
110fn create_processor_pool(config: &ConfigSnapshot) -> Result<ServicePool> {
111    // Adjust thread count for small cpu counts to not have too many idle cores
112    // and distribute workload better.
113    let thread_count = match config.cpu_concurrency() {
114        conc @ 0..=2 => conc.max(1),
115        conc @ 3..=4 => conc - 1,
116        conc => conc - 2,
117    };
118    relay_log::info!("starting {thread_count} envelope processing workers");
119
120    let pool = crate::utils::ThreadPoolBuilder::new("processor", tokio::runtime::Handle::current())
121        .num_threads(thread_count)
122        .max_concurrency(config.pool_concurrency())
123        .thread_kind(ThreadKind::Worker)
124        .build()?;
125
126    Ok(pool)
127}
128
129#[cfg(feature = "processing")]
130fn create_store_pool(config: &ConfigSnapshot) -> Result<ServicePool> {
131    // Spawn a store worker for every 12 threads in the processor pool.
132    // This ratio was found empirically and may need adjustments in the future.
133    //
134    // Ideally in the future the store will be single threaded again, after we move
135    // all the heavy processing (de- and re-serialization) into the processor.
136    let thread_count = config.cpu_concurrency().div_ceil(8);
137    relay_log::info!("starting {thread_count} store workers");
138
139    let pool = crate::utils::ThreadPoolBuilder::new("store", tokio::runtime::Handle::current())
140        .num_threads(thread_count)
141        .max_concurrency(config.pool_concurrency())
142        .build()?;
143
144    Ok(pool)
145}
146
147#[derive(Debug)]
148struct StateInner {
149    config: Arc<Config>,
150    memory_checker: MemoryChecker,
151    registry: Registry,
152    _daemons: Vec<Addr<()>>, // services that receive no messages but need to stay alive.
153    _asserter: AssertEmpty<TrackOutcome>,
154}
155
156/// Server state.
157#[derive(Clone, Debug)]
158pub struct ServiceState {
159    inner: Arc<StateInner>,
160}
161
162impl ServiceState {
163    /// Starts all services and returns addresses to all of them.
164    pub async fn start(
165        handle: &relay_system::Handle,
166        services: &dyn ServiceSpawn,
167        config: Arc<Config>,
168    ) -> Result<Self> {
169        let upstream_relay = services.start(UpstreamRelayService::new(config.clone()));
170        let current_config = config.current();
171
172        #[cfg(feature = "processing")]
173        let redis_clients = current_config
174            .redis()
175            .filter(|_| current_config.processing_enabled())
176            .map(create_redis_clients)
177            .transpose()
178            .context(ServiceError::Redis)?;
179
180        // If we have Redis configured, we want to initialize all the scripts by loading them in
181        // the scripts cache if not present. Our custom ConnectionLike implementation relies on this
182        // initialization to work properly since it assumes that scripts are loaded across all Redis
183        // instances.
184        #[cfg(feature = "processing")]
185        if let Some(redis_clients) = &redis_clients {
186            initialize_redis_scripts_for_client(redis_clients)
187                .await
188                .context(ServiceError::Redis)?;
189        }
190
191        // We create an instance of `MemoryStat` which can be supplied composed with any arbitrary
192        // configuration object down the line.
193        let memory_stat = MemoryStat::new(current_config.memory_stat_refresh_frequency_ms());
194
195        // Create an address for the `EnvelopeProcessor`, which can be injected into the
196        // other services.
197        let (envelope_processor, envelope_processor_rx) = match current_config.relay_mode() {
198            RelayMode::Proxy => channel(ProxyProcessorService::name()),
199            RelayMode::Managed => channel("envelope_processor"),
200        };
201
202        let (aggregator, aggregator_rx) = channel(RouterService::name());
203
204        // Use a separate metrics router for outcomes to prevent cycles in the service graph:
205        let (outcome_metrics_router, outcome_metrics_router_rx) = channel("outcome_metrics_router");
206        let (dummy_outcomes, _asserter) = Addr::never(); // outcomes never generate outcomes
207
208        let outcome_aggregator = match current_config.emit_outcomes() {
209            EmitOutcomes::None => services.start(NullOutcomeProducerService::new()),
210            _ => match current_config.relay_mode() {
211                RelayMode::Proxy => services.start(ClientReportOutcomeProducerService::new(
212                    &current_config,
213                    envelope_processor.clone(),
214                )),
215                RelayMode::Managed => services.start(OutcomeProducerService::new(
216                    Arc::clone(&config),
217                    outcome_metrics_router,
218                )),
219            },
220        };
221
222        let (global_config, global_config_rx) =
223            GlobalConfigService::new(config.clone(), upstream_relay.clone());
224        let global_config_handle = global_config.handle();
225        // The global config service must start before dependant services are
226        // started. Messages like subscription requests to the global config
227        // service fail if the service is not running.
228        let global_config = services.start(global_config);
229
230        let project_source = ProjectSource::start_in(
231            services,
232            Arc::clone(&config),
233            upstream_relay.clone(),
234            #[cfg(feature = "processing")]
235            redis_clients.clone(),
236        )
237        .await;
238        let project_cache_handle =
239            ProjectCacheService::new(Arc::clone(&config), project_source).start_in(services);
240
241        let cogs = CogsService::new(&current_config);
242        let cogs = Cogs::new(CogsServiceRecorder::new(
243            &current_config,
244            services.start(cogs),
245        ));
246
247        let metric_outcomes = MetricOutcomes::new(outcome_aggregator.clone());
248
249        #[cfg(feature = "processing")]
250        let store_pool = create_store_pool(&current_config)?;
251
252        #[cfg(feature = "processing")]
253        let mut maybe_kafka_client = None;
254
255        #[cfg(feature = "processing")]
256        let store = current_config
257            .processing_enabled()
258            .then(|| {
259                let kafka_client = create_kafka_client(&current_config)?;
260                maybe_kafka_client = Some(kafka_client.clone());
261                let service = store::create_service(
262                    store_pool.clone(),
263                    kafka_client.clone(),
264                    config.clone(),
265                    global_config_handle.clone(),
266                    metric_outcomes.clone(),
267                );
268                Ok::<_, anyhow::Error>(services.start(service))
269            })
270            .transpose()?;
271
272        #[cfg(feature = "processing")]
273        let objectstore = objectstore::create_service(current_config.objectstore(), store.clone())?
274            .map(|s| services.start(s));
275
276        let envelope_buffer = PartitionedEnvelopeBuffer::create(
277            current_config.spool_partitions(),
278            config.clone(),
279            memory_stat.clone(),
280            global_config_rx.clone(),
281            project_cache_handle.clone(),
282            envelope_processor.clone(),
283            outcome_aggregator.clone(),
284            services,
285        );
286
287        let (processor_pool, aggregator_handle, autoscaling) = match current_config.relay_mode() {
288            RelayMode::Proxy => {
289                services.start_with(
290                    ProxyProcessorService::new(
291                        config.clone(),
292                        project_cache_handle.clone(),
293                        ProxyAddrs {
294                            outcome_aggregator: outcome_aggregator.clone(),
295                            upstream_relay: upstream_relay.clone(),
296                        },
297                    ),
298                    envelope_processor_rx,
299                );
300                (None, None, None)
301            }
302            RelayMode::Managed => {
303                let processor_pool = create_processor_pool(&current_config)?;
304                // Share the quota cache between envelope and bucket processing.
305                #[cfg(feature = "processing")]
306                let rate_limiter =
307                    processor::create_rate_limiter(&current_config, redis_clients.clone());
308
309                let bucket_processor = services.start(processor::bucket::create_service(
310                    processor_pool.clone(),
311                    config.clone(),
312                    global_config_handle.clone(),
313                    project_cache_handle.clone(),
314                    cogs.clone(),
315                    #[cfg(feature = "processing")]
316                    rate_limiter.clone(),
317                    processor::bucket::Addrs {
318                        outcome_aggregator: outcome_aggregator.clone(),
319                        upstream_relay: upstream_relay.clone(),
320                        #[cfg(feature = "processing")]
321                        store_forwarder: store.clone(),
322                    },
323                ));
324
325                let router = RouterService::new(
326                    handle.clone(),
327                    current_config.default_aggregator_config().clone(),
328                    current_config.secondary_aggregator_configs().clone(),
329                    Some(bucket_processor.recipient()),
330                    project_cache_handle.clone(),
331                );
332                let router_handle = router.handle();
333                services.start_with(router, aggregator_rx);
334
335                // Start the secondary outcome pipeline to prevent cycles in the service graph.
336                // +-------------------+
337                // | EnvelopeProcessor |------------------------+
338                // +-------------------+                        |
339                //           |                                  |
340                //           v                                  v
341                // +-------------------+          +---------------------------+
342                // | MetricsAggregator |     +--->|      OutcomeProducer      |
343                // +-------------------+     |    +---------------------------+
344                //           |               |                  |
345                //           v               |                  v
346                // +-------------------+     |    +---------------------------+
347                // |  BucketProcessor  |-----+    | outcome MetricsAggregator |
348                // +-------------------+     |    +---------------------------+
349                //           |               |                  |
350                //           v               |                  v
351                // +-------------------+     |    +---------------------------+
352                // |       Store       |-----+    |  outcome BucketProcessor  |
353                // +-------------------+          +---------------------------+
354                //                                              |
355                //                                              v
356                //                                +---------------------------+
357                //                                |       outcome Store       |
358                //                                +---------------------------+
359                {
360                    #[cfg(feature = "processing")]
361                    let outcome_store = {
362                        if current_config.processing_enabled() {
363                            let (addr, rx) = channel("outcome_store");
364                            let service = store::create_service(
365                                store_pool.clone(),
366                                maybe_kafka_client
367                                    .clone()
368                                    .expect("kafka client created by store"),
369                                config.clone(),
370                                global_config_handle.clone(),
371                                MetricOutcomes::new(dummy_outcomes.clone()),
372                            );
373                            services.start_with(service, rx);
374                            Some(addr)
375                        } else {
376                            None
377                        }
378                    };
379
380                    let (outcome_bucket_processor, outcome_bucket_rx) =
381                        channel("outcome_bucket_processor");
382                    let outcome_bucket_service = processor::bucket::create_service(
383                        processor_pool.clone(),
384                        config.clone(),
385                        global_config_handle.clone(),
386                        project_cache_handle.clone(),
387                        cogs.clone(),
388                        #[cfg(feature = "processing")]
389                        rate_limiter.clone(),
390                        processor::bucket::Addrs {
391                            outcome_aggregator: dummy_outcomes,
392                            upstream_relay: upstream_relay.clone(),
393                            #[cfg(feature = "processing")]
394                            store_forwarder: outcome_store,
395                        },
396                    );
397                    services.start_with(outcome_bucket_service, outcome_bucket_rx);
398
399                    // Give the outcome metrics router the full config, because the user
400                    // may have configured specifics for outcome metrics.
401                    let outcome_metrics_router = RouterService::prefixed(
402                        handle.clone(),
403                        "outcomes_",
404                        current_config.default_aggregator_config().clone(),
405                        current_config.secondary_aggregator_configs().clone(),
406                        Some(outcome_bucket_processor.recipient()),
407                        project_cache_handle.clone(),
408                    );
409                    services.start_with(outcome_metrics_router, outcome_metrics_router_rx);
410                }
411
412                services.start_with(
413                    processor::envelope::create_service(
414                        processor_pool.clone(),
415                        config.clone(),
416                        global_config_handle.clone(),
417                        project_cache_handle.clone(),
418                        cogs.clone(),
419                        #[cfg(feature = "processing")]
420                        rate_limiter,
421                        processor::envelope::Addrs {
422                            outcome_aggregator: outcome_aggregator.clone(),
423                            upstream_relay: upstream_relay.clone(),
424                            #[cfg(feature = "processing")]
425                            objectstore: objectstore.clone(),
426                            #[cfg(feature = "processing")]
427                            store_forwarder: store,
428                            aggregator: aggregator.clone(),
429                        },
430                        metric_outcomes.clone(),
431                    ),
432                    envelope_processor_rx,
433                );
434
435                let autoscaling = services.start(AutoscalingMetricService::new(
436                    memory_stat.clone(),
437                    envelope_buffer.clone(),
438                    handle.clone(),
439                    processor_pool.clone(),
440                ));
441
442                (Some(processor_pool), Some(router_handle), Some(autoscaling))
443            }
444        };
445
446        let health_check = services.start(HealthCheckService::new(
447            config.clone(),
448            MemoryChecker::new(memory_stat.clone(), config.clone()),
449            aggregator_handle,
450            upstream_relay.clone(),
451            envelope_buffer.clone(),
452        ));
453
454        let stats = services.start(RelayStats::new(
455            config.clone(),
456            handle.clone(),
457            upstream_relay.clone(),
458            #[cfg(feature = "processing")]
459            redis_clients.clone(),
460            processor_pool,
461            #[cfg(feature = "processing")]
462            store_pool,
463        ));
464
465        let relay_cache = services.start(RelayCacheService::new(
466            config.clone(),
467            upstream_relay.clone(),
468        ));
469
470        let upload = services.start(upload::create_service(
471            &config,
472            &upstream_relay,
473            #[cfg(feature = "processing")]
474            &objectstore,
475        ));
476
477        let config_reload = services.start(ConfigReloadService::new(
478            config.clone(),
479            #[cfg(feature = "processing")]
480            maybe_kafka_client,
481        ));
482
483        let registry = Registry {
484            cogs,
485            processor: envelope_processor,
486            health_check,
487            outcome_aggregator,
488            relay_cache,
489            global_config,
490            project_cache_handle,
491            upstream_relay,
492            envelope_buffer,
493            autoscaling,
494            #[cfg(feature = "processing")]
495            objectstore,
496            upload,
497            global_config_handle,
498        };
499
500        let state = StateInner {
501            config: config.clone(),
502            memory_checker: MemoryChecker::new(memory_stat, config.clone()),
503            registry,
504            _daemons: vec![stats, config_reload],
505            _asserter,
506        };
507
508        Ok(ServiceState {
509            inner: Arc::new(state),
510        })
511    }
512
513    /// Returns a snapshot of the Relay configuration.
514    pub fn config(&self) -> ConfigSnapshot {
515        self.inner.config.current()
516    }
517
518    /// Returns a reference to the [`Cogs`] tracking.
519    pub fn cogs(&self) -> &Cogs {
520        &self.inner.registry.cogs
521    }
522
523    /// Returns a reference to the [`MemoryChecker`] which is a [`Config`] aware wrapper on the
524    /// [`MemoryStat`] which gives utility methods to determine whether memory usage is above
525    /// thresholds set in the [`Config`].
526    pub fn memory_checker(&self) -> &MemoryChecker {
527        &self.inner.memory_checker
528    }
529
530    pub fn autoscaling(&self) -> Option<&Addr<AutoscalingMetrics>> {
531        self.inner.registry.autoscaling.as_ref()
532    }
533
534    /// Returns `true` if all envelope buffer partitions are empty, in memory and on disk.
535    pub fn envelope_buffer_is_empty(&self) -> bool {
536        self.inner.registry.envelope_buffer.is_empty()
537    }
538
539    /// Returns the V2 envelope buffer, if present.
540    pub fn envelope_buffer(&self, project_key_pair: ProjectKeyPair) -> &ObservableEnvelopeBuffer {
541        self.inner.registry.envelope_buffer.buffer(project_key_pair)
542    }
543
544    /// Returns a [`ProjectCacheHandle`].
545    pub fn project_cache_handle(&self) -> &ProjectCacheHandle {
546        &self.inner.registry.project_cache_handle
547    }
548
549    /// Returns the address of the [`RelayCache`] service.
550    pub fn relay_cache(&self) -> &Addr<RelayCache> {
551        &self.inner.registry.relay_cache
552    }
553
554    /// Returns the address of the [`HealthCheck`] service.
555    pub fn health_check(&self) -> &Addr<HealthCheck> {
556        &self.inner.registry.health_check
557    }
558
559    /// Returns the address of the [`UpstreamRelay`] service.
560    pub fn upstream_relay(&self) -> &Addr<UpstreamRelay> {
561        &self.inner.registry.upstream_relay
562    }
563
564    /// Returns the address of the [`EnvelopeProcessor`] service.
565    pub fn processor(&self) -> &Addr<EnvelopeProcessor> {
566        &self.inner.registry.processor
567    }
568
569    /// Returns the address of the [`GlobalConfigService`] service.
570    pub fn global_config(&self) -> &Addr<GlobalConfigManager> {
571        &self.inner.registry.global_config
572    }
573
574    /// Returns the address of the [`TrackOutcome`] service.
575    pub fn outcome_aggregator(&self) -> &Addr<TrackOutcome> {
576        &self.inner.registry.outcome_aggregator
577    }
578
579    #[cfg(feature = "processing")]
580    /// Returns the address of the [`Objectstore`] service.
581    pub fn objectstore(&self) -> Option<&Addr<Objectstore>> {
582        self.inner.registry.objectstore.as_ref()
583    }
584
585    /// Returns the address of the [`Upload`] service.
586    pub fn upload(&self) -> &Addr<Upload> {
587        &self.inner.registry.upload
588    }
589
590    pub fn global_config_handle(&self) -> &GlobalConfigHandle {
591        &self.inner.registry.global_config_handle
592    }
593}
594
595/// Creates Redis clients from the given `configs`.
596///
597/// If `configs` is [`Unified`](RedisConfigsRef::Unified), one client is created and then cloned
598/// for project configs, cardinality, and quotas, meaning that they really use the same client.
599///
600/// If it is [`Individual`](RedisConfigsRef::Individual), an actual separate client
601/// is created for each use case.
602#[cfg(feature = "processing")]
603pub fn create_redis_clients(configs: RedisConfigsRef<'_>) -> Result<RedisClients, RedisError> {
604    const PROJECT_CONFIG_REDIS_CLIENT: &str = "projectconfig";
605    const QUOTA_REDIS_CLIENT: &str = "quotas";
606    const UNIFIED_REDIS_CLIENT: &str = "unified";
607
608    match configs {
609        RedisConfigsRef::Unified(unified) => {
610            let client = create_async_redis_client(UNIFIED_REDIS_CLIENT, &unified)?;
611
612            Ok(RedisClients {
613                project_configs: client.clone(),
614                quotas: client,
615            })
616        }
617        RedisConfigsRef::Individual {
618            project_configs,
619            quotas,
620        } => {
621            let project_configs =
622                create_async_redis_client(PROJECT_CONFIG_REDIS_CLIENT, &project_configs)?;
623            let quotas = create_async_redis_client(QUOTA_REDIS_CLIENT, &quotas)?;
624
625            Ok(RedisClients {
626                project_configs,
627                quotas,
628            })
629        }
630    }
631}
632
633#[cfg(feature = "processing")]
634fn create_async_redis_client(
635    name: &'static str,
636    config: &RedisConfigRef<'_>,
637) -> Result<AsyncRedisClient, RedisError> {
638    match config {
639        RedisConfigRef::Cluster {
640            cluster_nodes,
641            options,
642        } => AsyncRedisClient::cluster(name, cluster_nodes.iter().map(|s| s.as_str()), options),
643        RedisConfigRef::Single { server, options } => {
644            AsyncRedisClient::single(name, server, options)
645        }
646    }
647}
648
649#[cfg(feature = "processing")]
650async fn initialize_redis_scripts_for_client(
651    redis_clients: &RedisClients,
652) -> Result<(), RedisError> {
653    let scripts = RedisScripts::all();
654
655    let RedisClients {
656        project_configs,
657        quotas,
658    } = redis_clients;
659
660    initialize_redis_scripts(project_configs, &scripts).await?;
661    initialize_redis_scripts(quotas, &scripts).await?;
662
663    Ok(())
664}
665
666#[cfg(feature = "processing")]
667async fn initialize_redis_scripts(
668    client: &AsyncRedisClient,
669    scripts: &[&Script],
670) -> Result<(), RedisError> {
671    let mut connection = client.get_connection().await?;
672
673    for script in scripts {
674        // We load on all instances without checking if the script is already in cache because of a
675        // limitation in the connection implementation.
676        script
677            .prepare_invoke()
678            .load_async(&mut connection)
679            .await
680            .map_err(RedisError::Redis)?;
681    }
682
683    Ok(())
684}
685
686impl FromRequestParts<Self> for ServiceState {
687    type Rejection = Infallible;
688
689    async fn from_request_parts(_: &mut Parts, state: &Self) -> Result<Self, Self::Rejection> {
690        Ok(state.clone())
691    }
692}