1mod tests;
74
75use std::time::{Duration, Instant};
76
77use async_nats::{
78 Message, Subscriber,
79 jetstream::{
80 consumer::{self, Consumer},
81 stream::{self, Stream},
82 },
83};
84use futures::{FutureExt as _, StreamExt as _, TryStreamExt as _};
85use rand::RngExt as _;
86use tokio::{runtime::Handle, task::block_in_place};
87
88use crate::client::room::ROOMSERVER_STREAM_PARTITION_PARTS;
89
90#[derive(Debug, Clone)]
92pub struct ConsumerClientConfig {
93 pub consumer_name: String,
97 pub subject: String,
101 pub num_parts: usize,
107 pub inactive_threshold: Duration,
112 pub repartition_attempts: usize,
117 pub repartition_delay: Duration,
122 pub fetch_timeout: Duration,
127}
128
129impl Default for ConsumerClientConfig {
130 fn default() -> Self {
131 ConsumerClientConfig {
132 consumer_name: rand::rng().random_range(0..10000).to_string(),
133 subject: "consumer_subject".to_owned(),
134 num_parts: 100,
135 inactive_threshold: Duration::from_secs(30),
136 repartition_attempts: 30,
137 repartition_delay: Duration::from_secs(1),
138 fetch_timeout: Duration::from_secs(60),
139 }
140 }
141}
142
143impl ConsumerClientConfig {
144 #[must_use]
146 pub fn for_rooms(name: String) -> Self {
147 Self {
148 consumer_name: name,
149 subject: "rooms".to_owned(),
150 num_parts: ROOMSERVER_STREAM_PARTITION_PARTS,
151 inactive_threshold: Duration::from_secs(10),
155 ..Default::default()
156 }
157 }
158}
159
160#[tracing::instrument(skip(config, stream))]
165async fn update_or_recreate_consumer(
166 config: &ConsumerClientConfig,
167 num_partitions: usize,
168 stream: &Stream<stream::Info>,
169) -> Result<Consumer<consumer::pull::Config>, async_nats::Error> {
170 for _ in 0..config.repartition_attempts {
171 let consumers: Vec<String> = stream.consumer_names().try_collect().await?;
173
174 tracing::trace!(
175 "{} trying to get filters with list of consumers: {:?}",
176 config.consumer_name,
177 consumers
178 );
179
180 let Some(worker_index) =
181 consumers.iter().position(|consumer| consumer == &config.consumer_name)
182 else {
183 tracing::trace!("Recreating decayed consumer {}", config.consumer_name);
184 _ = stream
185 .create_consumer(consumer::pull::Config {
186 name: Some(config.consumer_name.clone()),
187 filter_subjects: vec![format!("placeholder_filter.{}", config.consumer_name)],
188 inactive_threshold: config.inactive_threshold,
189 max_ack_pending: 1,
190 ..Default::default()
191 })
192 .await?;
193 continue;
194 };
195
196 let num_workers = consumers.len();
197
198 let filter_subjects: Vec<String> = (0..num_partitions)
199 .skip(worker_index)
200 .step_by(num_workers)
201 .map(|i| format!("{}.{}.>", config.subject, i))
202 .collect();
203
204 if let Ok(consumer) = stream
205 .update_consumer(consumer::pull::Config {
206 name: Some(config.consumer_name.clone()),
207 filter_subjects: filter_subjects.clone(),
209 inactive_threshold: config.inactive_threshold,
210 max_ack_pending: 1,
211 ..Default::default()
212 })
213 .await
214 {
215 tracing::trace!("{} successfully updated filter_subjects", config.consumer_name);
216
217 return Ok(consumer);
218 }
219
220 tokio::time::sleep(config.repartition_delay).await;
221 }
222
223 Ok(stream
230 .update_consumer(consumer::pull::Config {
231 name: Some(config.consumer_name.clone()),
232 filter_subjects: vec![format!("placeholder_filter.{}", config.consumer_name)],
233 inactive_threshold: config.inactive_threshold,
234 max_ack_pending: 1,
235 ..Default::default()
236 })
237 .await?)
238}
239
240#[derive(Debug)]
242pub struct ConsumerClient {
243 config: ConsumerClientConfig,
246 stream: Stream<stream::Info>,
248 consumer: Consumer<consumer::pull::Config>,
250 consumer_list: Vec<String>,
252 consumer_list_last_update: Instant,
254 new_worker_subscription: Subscriber,
256 consumer_deleted_subscription: Subscriber,
258}
259
260impl Drop for ConsumerClient {
262 fn drop(&mut self) {
263 block_in_place(move || {
266 Handle::current().block_on(async move {
267 if let Err(err) = self.stream.delete_consumer(&self.config.consumer_name).await {
268 tracing::warn!("Failed to delete consumer: {err}");
269 }
270 });
271 });
272
273 tracing::info!("Finished deleting consumer");
274 }
275}
276
277impl ConsumerClient {
278 pub async fn new(
280 config: ConsumerClientConfig,
281 client: async_nats::Client,
282 stream: Stream<stream::Info>,
283 ) -> Result<Self, async_nats::Error> {
284 let stream_name = &stream.cached_info().config.name;
285 let new_worker_subscription = client
286 .subscribe(format!("$JS.EVENT.ADVISORY.CONSUMER.CREATED.{stream_name}.*"))
287 .await?;
288 let consumer_deleted_subscription = client
289 .subscribe(format!("$JS.EVENT.ADVISORY.CONSUMER.DELETED.{stream_name}.*"))
290 .await?;
291
292 let consumer = stream
293 .create_consumer_strict(consumer::pull::Config {
294 name: Some(config.consumer_name.clone()),
295 filter_subjects: vec![format!("placeholder_filter.{}", config.consumer_name)],
296 inactive_threshold: config.inactive_threshold,
297 max_ack_pending: 1,
298 ..Default::default()
299 })
300 .await?;
301
302 Ok(ConsumerClient {
303 config,
304 stream,
305 consumer,
306 consumer_list: Vec::new(),
307 consumer_list_last_update: Instant::now() - Duration::from_secs(90),
308 new_worker_subscription,
309 consumer_deleted_subscription,
310 })
311 }
312
313 async fn update_consumer_list(&mut self) -> Result<(), async_nats::Error> {
316 self.consumer_list = self.stream.consumer_names().try_collect().await?;
317 self.consumer_list_last_update = Instant::now();
318
319 Ok(())
320 }
321
322 #[tracing::instrument(skip(self))]
324 async fn update_filter_subjects(&mut self) -> Result<(), async_nats::Error> {
325 tracing::trace!("{}: Rebuilding consumer", self.config.consumer_name);
326 match self
327 .stream
328 .update_consumer(consumer::pull::Config {
329 name: Some(self.config.consumer_name.clone()),
330 filter_subjects: vec![format!("placeholder_filter.{}", self.config.consumer_name)],
331 inactive_threshold: self.config.inactive_threshold,
332 max_ack_pending: 1,
333 ..Default::default()
334 })
335 .await
336 {
337 Ok(_) => Ok(()),
338 Err(err) => match err.kind() {
339 stream::ConsumerUpdateErrorKind::DoesNotExist => Ok(()),
341 _ => Err(err),
342 },
343 }?;
344
345 self.consumer =
346 update_or_recreate_consumer(&self.config, self.config.num_parts, &self.stream).await?;
347
348 self.update_consumer_list().await?;
349
350 Ok(())
351 }
352
353 #[tracing::instrument(skip(self))]
356 async fn handle_new_worker_message(
357 &mut self,
358 msg: Option<Message>,
359 ) -> Result<(), async_nats::Error> {
360 if let Some(msg) = msg {
361 let pending_worker = msg.subject.rsplit(".").next().unwrap_or("UNKNOWN");
362 if !self.consumer_list.iter().any(|w| w == pending_worker) {
363 tracing::trace!("{}: New worker {}!", self.config.consumer_name, pending_worker);
364 self.update_filter_subjects().await?;
365 }
366 }
367 Ok(())
368 }
369
370 #[tracing::instrument(skip(self))]
373 async fn handle_worker_deleted_message(
374 &mut self,
375 msg: Option<Message>,
376 ) -> Result<(), async_nats::Error> {
377 if let Some(msg) = msg {
378 let offline_worker = msg.subject.rsplit(".").next().unwrap_or("UNKNOWN");
380 tracing::trace!("{}: {} went offline!", self.config.consumer_name, offline_worker);
381 if self.consumer_list.iter().any(|w| w == offline_worker) {
382 self.update_filter_subjects().await?;
383 }
384 }
385 Ok(())
386 }
387
388 #[tracing::instrument(skip(self))]
396 pub async fn get_message(
397 &mut self,
398 ) -> Result<Option<async_nats::jetstream::Message>, async_nats::Error> {
399 loop {
401 if self.consumer_list_last_update < Instant::now() - Duration::from_secs(90) {
402 tracing::trace!("Rebuilding consumer due to timeout");
403 self.update_filter_subjects().await?;
404 continue;
405 }
406
407 if let Some(msg) = self.new_worker_subscription.next().now_or_never() {
408 self.handle_new_worker_message(msg).await?;
409 continue;
410 }
411 if let Some(msg) = self.consumer_deleted_subscription.next().now_or_never() {
412 self.handle_worker_deleted_message(msg).await?;
413 continue;
414 }
415
416 break;
417 }
418
419 let consumer_clone = self.consumer.clone();
421 consumer_clone
422 .fetch()
423 .max_messages(1)
424 .expires(self.config.fetch_timeout)
425 .messages()
426 .await?
427 .try_next()
428 .await
429 }
430}