xmtp_mls/server_configuration/
worker.rs1use xmtp_common::{RetryableError, time::Duration};
10use xmtp_configuration::{
11 CONFIGURATION_REFRESH_ATTEMPTS, CONFIGURATION_REFRESH_BACKOFF, CONFIGURATION_REFRESH_INTERVAL,
12 CONFIGURATION_REFRESH_JITTER,
13};
14
15use crate::client::ClientError;
16use crate::context::XmtpSharedContext;
17use crate::worker::{
18 BoxedWorker, NeedsDbReconnect, Worker, WorkerFactory, WorkerKind, WorkerResult,
19};
20
21use super::refresh;
22
23#[derive(Clone)]
24pub struct Factory<Context> {
25 context: Context,
26}
27
28impl<Context> WorkerFactory for Factory<Context>
29where
30 Context: XmtpSharedContext + 'static,
31{
32 fn kind(&self) -> WorkerKind {
33 WorkerKind::ConfigurationRefresh
34 }
35
36 fn create(
37 &self,
38 metrics: Option<crate::worker::DynMetrics>,
39 ) -> (BoxedWorker, Option<crate::worker::DynMetrics>) {
40 (
41 Box::new(ConfigurationWorker::new(self.context.clone())) as Box<_>,
42 metrics,
43 )
44 }
45}
46
47#[derive(Debug, thiserror::Error)]
51#[error("the configuration refresh worker stopped")]
52pub struct ConfigurationWorkerError;
53
54impl NeedsDbReconnect for ConfigurationWorkerError {
55 fn needs_db_reconnect(&self) -> bool {
56 false
57 }
58}
59
60pub struct ConfigurationWorker<Context> {
61 context: Context,
62}
63
64impl<Context> ConfigurationWorker<Context> {
65 pub fn new(context: Context) -> Self {
66 Self { context }
67 }
68}
69
70#[xmtp_common::async_trait]
71impl<Context> Worker for ConfigurationWorker<Context>
72where
73 Context: XmtpSharedContext + 'static,
74{
75 fn kind(&self) -> WorkerKind {
76 WorkerKind::ConfigurationRefresh
77 }
78
79 async fn run_tasks(&mut self) -> WorkerResult<()> {
80 self.run().await;
81 Ok(())
82 }
83
84 fn factory<C>(context: C) -> impl WorkerFactory + 'static
85 where
86 C: XmtpSharedContext + 'static,
87 {
88 Factory { context }
89 }
90}
91
92impl<Context> ConfigurationWorker<Context>
93where
94 Context: XmtpSharedContext + 'static,
95{
96 async fn run(&mut self) {
97 loop {
98 let (base, jitter) = self.schedule();
99 xmtp_common::time::sleep(base + xmtp_common::time::rand_offset(jitter)).await;
100 if self
102 .context
103 .server_configuration()
104 .blocked_connection()
105 .is_some()
106 {
107 return;
108 }
109 self.tick().await;
110 if self
113 .context
114 .server_configuration()
115 .blocked_connection()
116 .is_some()
117 {
118 self.context.cancellation_token().cancel();
119 return;
120 }
121 }
122 }
123
124 fn schedule(&self) -> (Duration, Duration) {
127 let kind = WorkerKind::ConfigurationRefresh;
128 let (base, jitter) = self
129 .context
130 .worker_interval(kind, CONFIGURATION_REFRESH_INTERVAL);
131 let jitter = if self
132 .context
133 .worker_config()
134 .jitter_overrides
135 .contains_key(&kind)
136 {
137 jitter
138 } else {
139 CONFIGURATION_REFRESH_JITTER
140 };
141 (base, jitter)
142 }
143
144 #[tracing::instrument(
146 skip_all,
147 fields(worker = "ConfigurationRefresh", operation = "worker_turn")
148 )]
149 pub(crate) async fn tick(&mut self) {
150 for attempt in 1..=CONFIGURATION_REFRESH_ATTEMPTS {
151 match self.attempt().await {
152 Ok(()) => return,
153 Err(error) => {
154 tracing::warn!(
157 attempt,
158 %error,
159 "server configuration refresh attempt failed; keeping the stored copy"
160 );
161 if !error.is_retryable() {
162 return;
163 }
164 match CONFIGURATION_REFRESH_BACKOFF.get(attempt - 1) {
165 Some(wait) => xmtp_common::time::sleep(*wait).await,
166 None => return,
167 }
168 }
169 }
170 }
171 }
172
173 async fn attempt(&mut self) -> Result<(), ClientError> {
175 refresh(&self.context).await.map(|_| ())
176 }
177}