1mod acknowledgement;
4mod types;
5
6pub use acknowledgement::DeliveryAcknowledgement;
7pub(crate) use types::LocalDeliveryConfig;
8pub use types::{LocalDeliveryError, LocalDeliveryFilter};
9pub use xmtp_db::delivery::{DeliveryCursor, DeliveryScope, DeliverySnapshot};
10
11use acknowledgement::{AcknowledgementState, DeliverySession, PendingAcknowledgement};
12use futures::{Stream, StreamExt};
13use parking_lot::Mutex;
14use std::{collections::VecDeque, sync::Arc};
15use tokio::sync::{Notify, broadcast};
16use xmtp_common::{
17 StreamHandle,
18 time::{now_ns, sleep},
19};
20use xmtp_db::{
21 Fetch, StorageError,
22 consent_record::{ConsentState, ConsentType},
23 delivery::{DeliveryMessage, QueryDelivery},
24 group::StoredGroup,
25 group_message::StoredGroupMessage,
26 prelude::*,
27};
28use xmtp_proto::types::GroupId;
29
30use super::{LocalEvents, SubscribeError};
31use crate::context::XmtpSharedContext;
32
33type Result<T> = std::result::Result<T, LocalDeliveryError>;
34
35pub struct LocalDeliveryItem<Context: XmtpSharedContext> {
37 pub message: StoredGroupMessage,
39 pub cursor: DeliveryCursor,
41 pub acknowledgement: DeliveryAcknowledgement<Context>,
43}
44
45#[derive(Clone)]
46struct Selection {
47 scope: DeliveryScope,
48 filter: LocalDeliveryFilter,
49 revision: u64,
50}
51
52#[derive(Clone)]
54pub struct LocalDeliveryControl {
55 selection: Arc<Mutex<Selection>>,
56 changed: Arc<Notify>,
57 closer: Arc<dyn CloseDelivery>,
58}
59
60trait CloseDelivery: xmtp_common::MaybeSend + xmtp_common::MaybeSync {
61 fn close_delivery(&self);
62}
63
64impl<C: XmtpSharedContext> CloseDelivery for DeliverySession<C> {
65 fn close_delivery(&self) {
66 self.close();
67 }
68}
69
70impl LocalDeliveryControl {
71 pub fn close(&self) {
72 self.closer.close_delivery();
73 }
74
75 pub fn update_scope(&self, scope: DeliveryScope) {
77 let mut selection = self.selection.lock();
78 selection.scope = scope;
79 selection.revision = selection.revision.wrapping_add(1);
80 self.changed.notify_one();
81 }
82
83 pub fn update_filter(&self, filter: LocalDeliveryFilter) {
85 let mut selection = self.selection.lock();
86 selection.filter = filter;
87 selection.revision = selection.revision.wrapping_add(1);
88 self.changed.notify_one();
89 }
90
91 pub fn scope(&self) -> DeliveryScope {
92 self.selection.lock().scope.clone()
93 }
94}
95
96pub struct LocalDelivery<Context: XmtpSharedContext> {
98 session: Arc<DeliverySession<Context>>,
99 config: LocalDeliveryConfig,
100 control: LocalDeliveryControl,
101 events: broadcast::Receiver<LocalEvents>,
102 candidates: VecDeque<DeliveryMessage>,
103 candidate_revision: u64,
104 pending: Option<Arc<PendingAcknowledgement>>,
106 replay_position: Option<DeliveryCursor>,
108 renewal: Option<Box<dyn StreamHandle<StreamOutput = ()>>>,
109}
110
111impl<Context> LocalDelivery<Context>
112where
113 Context: XmtpSharedContext + 'static,
114{
115 pub(crate) fn new(
117 context: Context,
118 scope: DeliveryScope,
119 filter: LocalDeliveryFilter,
120 from: Option<DeliveryCursor>,
121 config: LocalDeliveryConfig,
122 ) -> Result<Self> {
123 config.validate()?;
124 if context.is_closed() {
125 return Err(LocalDeliveryError::Closed);
126 }
127 let events = context.local_events().subscribe();
129 let now = now_ns();
130 let owner = if let Some(cursor) = from {
131 context
132 .db()
133 .replay_delivery_messages(cursor, &scope, now, 0)?;
134 None
135 } else {
136 let mut registered = context.delivery_owner().lock();
137 if context.is_closed() {
138 return Err(LocalDeliveryError::Closed);
139 }
140 let owner = context
141 .db()
142 .acquire_delivery_owner_with_clock(config.lease_duration_ns()?, now_ns)?;
143 *registered = Some(owner);
144 Some(owner)
145 };
146 let session = Arc::new(DeliverySession::new(
147 context,
148 owner,
149 from.map(|cursor| cursor.database_id),
150 ));
151 let renewal = if owner.is_some() {
152 let session = Arc::clone(&session);
153 Some(Box::new(xmtp_common::spawn(None, async move {
154 loop {
155 tokio::select! {
156 _ = session.cancel.cancelled() => { session.close(); break; },
157 _ = sleep(config.renew_interval) => {}
158 }
159 let result = config
160 .lease_duration_ns()
161 .and_then(|lease_ns| session.renew(lease_ns).map_err(Into::into));
162 if let Err(error) = result {
163 session.fail(error);
164 break;
165 }
166 }
167 })) as Box<dyn StreamHandle<StreamOutput = ()>>)
168 } else {
169 None
170 };
171 Ok(Self {
172 session: Arc::clone(&session),
173 config,
174 control: LocalDeliveryControl {
175 selection: Arc::new(Mutex::new(Selection {
176 scope,
177 filter,
178 revision: 0,
179 })),
180 changed: Arc::new(Notify::new()),
181 closer: session,
182 },
183 events,
184 candidates: VecDeque::new(),
185 candidate_revision: 0,
186 pending: None,
187 replay_position: from,
188 renewal,
189 })
190 }
191
192 pub fn control(&self) -> LocalDeliveryControl {
193 self.control.clone()
194 }
195
196 pub fn close(&mut self) {
198 self.session.close();
199 }
200
201 pub async fn next_delivery(&mut self) -> Result<Option<LocalDeliveryItem<Context>>> {
204 let result = self.next_inner().await;
205 if result.is_err() {
206 self.session.close();
207 }
208 result
209 }
210
211 async fn next_inner(&mut self) -> Result<Option<LocalDeliveryItem<Context>>> {
212 if let Some(pending) = self.pending.clone() {
213 loop {
214 let notified = pending.changed.notified();
215 {
216 let mut state = pending.state.lock();
217 if matches!(*state, AcknowledgementState::Waiting)
218 && self.control.selection.lock().revision != pending.revision
219 {
220 *state = AcknowledgementState::Reselect;
221 }
222 }
223 match *pending.state.lock() {
224 AcknowledgementState::Acknowledged => {
225 if self.replay_position.is_some() {
226 self.replay_position = Some(pending.cursor);
227 }
228 self.pending = None;
229 break;
230 }
231 AcknowledgementState::Reselect => {
232 self.pending = None;
233 self.candidates.clear();
234 break;
235 }
236 AcknowledgementState::Rejected => {
237 return Err(LocalDeliveryError::AcknowledgementRejected);
238 }
239 AcknowledgementState::Failed => {
240 return Err(LocalDeliveryError::AcknowledgementFailed);
241 }
242 AcknowledgementState::Waiting | AcknowledgementState::Dispatched => {}
243 }
244 tokio::select! {
245 _ = notified => {},
246 _ = self.control.changed.notified() => {},
247 _ = self.session.cancel.cancelled() => return self.session.end_result(),
248 }
249 }
250 }
251 loop {
252 if self.session.is_closed() {
253 return self.session.end_result();
254 }
255 let selection = self.control.selection.lock().clone();
256 if selection.revision != self.candidate_revision {
257 self.candidates.clear();
258 self.candidate_revision = selection.revision;
259 }
260 if self.candidates.is_empty() {
261 let now = now_ns();
262 self.candidates = {
263 let db = self.session.context.db();
264 if let Some(position) = self.replay_position {
265 db.replay_delivery_messages_bounded(
266 position,
267 &selection.scope,
268 now,
269 self.config.batch_size,
270 self.config.max_bytes,
271 )?
272 } else {
273 db.default_delivery_messages_bounded(
274 self.session.owner.ok_or(LocalDeliveryError::Closed)?,
275 &selection.scope,
276 now,
277 self.config.batch_size,
278 self.config.max_bytes,
279 )?
280 }
281 .into()
282 };
283 if self.candidates.is_empty() {
284 tokio::select! {
285 _ = self.session.cancel.cancelled() => return self.session.end_result(),
286 _ = self.control.changed.notified() => {},
287 _ = self.events.recv() => {},
288 _ = sleep(self.config.poll_interval) => {},
289 }
290 continue;
291 }
292 }
293 let mut scanned = 0;
294 while let Some(candidate) = self.candidates.pop_front() {
295 let selection = self.control.selection.lock().clone();
297 if selection.revision != self.candidate_revision {
298 self.candidates.clear();
299 self.candidate_revision = selection.revision;
300 break;
301 }
302 self.session.check_owner()?;
303 let retained = self.session.context.db().delivery_message_is_retained(
304 &candidate.message.id,
305 candidate.cursor,
306 now_ns(),
307 )?;
308 if !retained
309 || !matches_filter(
310 &self.session.context,
311 candidate.message.group_id,
312 &selection.filter,
313 )?
314 {
315 if !self.skip_candidate(&candidate, selection.revision)? {
316 self.candidates.clear();
317 break;
318 }
319 scanned += 1;
320 if scanned >= self.config.batch_size {
321 break;
322 }
323 continue;
324 }
325 self.session.check_owner()?;
327 let pending = Arc::new(PendingAcknowledgement::new(
328 candidate.cursor,
329 selection.revision,
330 ));
331 let acknowledgement = DeliveryAcknowledgement::new(
332 Arc::clone(&self.session),
333 Arc::clone(&pending),
334 candidate.message.group_id,
335 self.control.clone(),
336 candidate.message.id.clone(),
337 );
338 self.pending = Some(pending);
339 return Ok(Some(LocalDeliveryItem {
340 message: candidate.message,
341 cursor: candidate.cursor,
342 acknowledgement,
343 }));
344 }
345 tokio::task::yield_now().await;
346 }
347 }
348
349 fn skip_candidate(&mut self, candidate: &DeliveryMessage, revision: u64) -> Result<bool> {
351 let selection = self.control.selection.lock();
352 if selection.revision != revision {
353 return Ok(false);
354 }
355 self.session.check_owner()?;
356 if let Some(owner) = self.session.owner {
357 self.session.context.db().acknowledge_delivery_with_clock(
358 owner,
359 candidate.message.group_id,
360 candidate.cursor,
361 now_ns,
362 )?;
363 } else {
364 self.replay_position = Some(candidate.cursor);
365 }
366 Ok(true)
367 }
368
369 pub fn into_stream(self) -> impl Stream<Item = super::Result<StoredGroupMessage>> {
372 self.into_cursor_stream()
373 .map(|item| item.map(|item| item.message).map_err(SubscribeError::from))
374 }
375
376 pub fn into_cursor_stream(self) -> impl Stream<Item = Result<DeliveryMessage>> {
378 futures::stream::unfold(
379 Some((self, None::<DeliveryAcknowledgement<Context>>)),
380 |state| async move {
381 let (mut reader, previous) = state?;
382 if let Some(previous) = previous
383 && let Err(error) = previous.acknowledge()
384 {
385 return Some((Err(error), None));
386 }
387 loop {
388 match reader.next_delivery().await {
389 Ok(Some(LocalDeliveryItem {
390 message,
391 cursor,
392 acknowledgement,
393 })) => match acknowledgement.check_owner() {
394 Ok(()) => {
395 return Some((
396 Ok(DeliveryMessage { message, cursor }),
397 Some((reader, Some(acknowledgement))),
398 ));
399 }
400 Err(LocalDeliveryError::SelectionChanged) => continue,
401 Err(error) => return Some((Err(error), None)),
402 },
403 Ok(None) => return None,
404 Err(error) => return Some((Err(error), None)),
405 }
406 }
407 },
408 )
409 }
410
411 pub fn history_snapshot(
413 context: &Context,
414 scope: &DeliveryScope,
415 filter: &LocalDeliveryFilter,
416 limit: u32,
417 ) -> Result<DeliverySnapshot> {
418 let settings = context.incoming_runtime().policy();
419 Ok(context.db().delivery_history_snapshot_filtered(
420 scope,
421 filter,
422 now_ns(),
423 limit.min(settings.max_local_read_rows),
424 settings.max_local_read_bytes,
425 )?)
426 }
427}
428
429impl<Context: XmtpSharedContext> Drop for LocalDelivery<Context> {
430 fn drop(&mut self) {
431 self.session.close();
432 if let Some(task) = &self.renewal {
433 task.end();
434 }
435 }
436}
437
438fn matches_filter<Context: XmtpSharedContext>(
439 context: &Context,
440 group_id: GroupId,
441 filter: &LocalDeliveryFilter,
442) -> Result<bool> {
443 let db = context.db();
444 if let Some(kind) = filter.conversation_type {
445 let group: Option<StoredGroup> = db.fetch(&group_id)?;
446 if group.is_none_or(|group| group.conversation_type != kind) {
447 return Ok(false);
448 }
449 }
450 if let Some(states) = &filter.consent_states {
451 let consent = db
452 .get_consent_record(hex::encode(group_id), ConsentType::ConversationId)
453 .map_err(StorageError::from)?
454 .map_or(ConsentState::Unknown, |record| record.state);
455 if !states.contains(&consent) {
456 return Ok(false);
457 }
458 }
459 Ok(true)
460}
461
462#[cfg(test)]
463mod tests;