xmtp_mls/subscriptions/local_delivery/
acknowledgement.rs1use parking_lot::Mutex;
2use std::sync::{
3 Arc,
4 atomic::{AtomicBool, Ordering},
5};
6use tokio::sync::Notify;
7use tokio_util::sync::CancellationToken;
8use xmtp_common::time::now_ns;
9use xmtp_db::{
10 StorageError,
11 delivery::{DeliveryCursor, DeliveryOwner, QueryDelivery, acknowledgement::DeliveryAckRequest},
12};
13use xmtp_proto::types::GroupId;
14
15use super::{LocalDeliveryControl, LocalDeliveryError, Result};
16use crate::context::XmtpSharedContext;
17
18pub(super) struct DeliverySession<Context: XmtpSharedContext> {
19 pub(super) context: Context,
20 pub(super) owner: Option<DeliveryOwner>,
21 database_id: [u8; 16],
22 pub(super) cancel: CancellationToken,
23 closed: AtomicBool,
24 failure: Mutex<Option<Arc<LocalDeliveryError>>>,
25}
26
27impl<Context: XmtpSharedContext> DeliverySession<Context> {
28 pub(super) fn new(
29 context: Context,
30 owner: Option<DeliveryOwner>,
31 database_id: [u8; 16],
32 ) -> Self {
33 let cancel = context.cancellation_token().child_token();
34 Self {
35 context,
36 owner,
37 database_id,
38 cancel,
39 closed: AtomicBool::new(false),
40 failure: Mutex::new(None),
41 }
42 }
43
44 pub(super) fn is_closed(&self) -> bool {
45 self.closed.load(Ordering::Acquire) || self.cancel.is_cancelled()
46 }
47
48 pub(super) fn check_owner(&self) -> Result<()> {
49 if self.is_closed() {
50 return Err(self
51 .background_error()
52 .unwrap_or(LocalDeliveryError::Closed));
53 }
54 if self.context.db().stream_database_id()? != self.database_id {
55 return Err(StorageError::from(
56 xmtp_db::stream_storage::StreamStorageError::ForeignCursor,
57 )
58 .into());
59 }
60 if let Some(owner) = self.owner {
61 self.context
62 .db()
63 .check_delivery_owner_with_clock(owner, now_ns)?;
64 }
65 Ok(())
66 }
67
68 pub(super) fn owner(&self) -> Option<DeliveryOwner> {
69 self.owner
70 }
71
72 pub(super) fn renew(&self, lease_ns: i64) -> std::result::Result<(), StorageError> {
73 if let Some(owner) = self.owner {
74 self.context
75 .db()
76 .renew_delivery_owner_with_clock(owner, lease_ns, now_ns)?;
77 }
78 Ok(())
79 }
80
81 pub(super) fn close(&self) {
82 if self.closed.swap(true, Ordering::AcqRel) {
83 return;
84 }
85 self.cancel.cancel();
86 if let Some(owner) = self.owner {
87 let mut registered = self.context.delivery_owner().lock();
88 match self.context.db().release_delivery_owner(owner) {
89 Ok(()) => {
90 if *registered == Some(owner) {
91 *registered = None;
92 }
93 }
94 Err(error) => {
95 if *registered == Some(owner) {
96 *self
97 .context
98 .incoming_runtime()
99 .retired_delivery_owner
100 .lock() = Some(owner);
101 }
102 tracing::warn!(%error, "Failed to release the message delivery owner")
103 }
104 }
105 }
106 }
107
108 pub(super) fn fail(&self, error: LocalDeliveryError) -> LocalDeliveryError {
109 let error = Arc::clone(self.failure.lock().get_or_insert_with(|| Arc::new(error)));
110 self.close();
111 LocalDeliveryError::SessionFailure(error)
112 }
113
114 pub(super) fn background_error(&self) -> Option<LocalDeliveryError> {
115 self.failure
116 .lock()
117 .as_ref()
118 .map(|error| LocalDeliveryError::SessionFailure(Arc::clone(error)))
119 }
120
121 pub(super) fn end_result<T>(&self) -> Result<Option<T>> {
122 match self.background_error() {
123 Some(error) => Err(error),
124 None => Ok(None),
125 }
126 }
127}
128
129#[derive(Clone, Copy)]
130pub(super) enum AcknowledgementState {
131 Waiting,
132 Dispatched,
133 Reselect,
134 Acknowledged,
135 Rejected,
136 Failed,
137}
138
139pub(super) struct PendingAcknowledgement {
140 pub(super) cursor: DeliveryCursor,
141 pub(super) revision: u64,
142 pub(super) state: Mutex<AcknowledgementState>,
143 pub(super) changed: Notify,
144}
145
146impl PendingAcknowledgement {
147 pub(super) fn new(cursor: DeliveryCursor, revision: u64) -> Self {
148 Self {
149 cursor,
150 revision,
151 state: Mutex::new(AcknowledgementState::Waiting),
152 changed: Notify::new(),
153 }
154 }
155}
156
157pub struct DeliveryAcknowledgement<Context: XmtpSharedContext> {
159 session: Arc<DeliverySession<Context>>,
160 pending: Arc<PendingAcknowledgement>,
161 group_id: GroupId,
162 control: LocalDeliveryControl,
163 message_id: Vec<u8>,
164}
165
166impl<Context: XmtpSharedContext> DeliveryAcknowledgement<Context> {
167 pub(super) fn new(
168 session: Arc<DeliverySession<Context>>,
169 pending: Arc<PendingAcknowledgement>,
170 group_id: GroupId,
171 control: LocalDeliveryControl,
172 message_id: Vec<u8>,
173 ) -> Self {
174 Self {
175 session,
176 pending,
177 group_id,
178 control,
179 message_id,
180 }
181 }
182
183 pub fn check_owner(&self) -> Result<()> {
186 self.check_dispatch(true)
187 }
188
189 pub fn check_owner_cancellable(&self, request: &DeliveryAckRequest) -> Result<bool> {
191 self.check_dispatch_with_request(true, Some(request))
192 }
193
194 fn check_dispatch(&self, dispatch: bool) -> Result<()> {
195 self.check_dispatch_with_request(dispatch, None).map(|_| ())
196 }
197
198 fn check_dispatch_with_request(
199 &self,
200 dispatch: bool,
201 request: Option<&DeliveryAckRequest>,
202 ) -> Result<bool> {
203 if let Some(error) = self.session.background_error() {
204 return Err(error);
205 }
206 let result = self.begin_dispatch(&mut self.pending.state.lock(), dispatch, request);
207 match result {
208 Err(LocalDeliveryError::SelectionChanged) => Err(LocalDeliveryError::SelectionChanged),
209 Err(error) => Err(self.session.fail(error)),
210 result => result,
211 }
212 }
213
214 fn begin_dispatch(
215 &self,
216 state: &mut AcknowledgementState,
217 dispatch: bool,
218 request: Option<&DeliveryAckRequest>,
219 ) -> Result<bool> {
220 match *state {
221 AcknowledgementState::Reselect => return Err(LocalDeliveryError::SelectionChanged),
222 AcknowledgementState::Rejected => {
223 return Err(LocalDeliveryError::AcknowledgementRejected);
224 }
225 AcknowledgementState::Failed => return Err(LocalDeliveryError::AcknowledgementFailed),
226 AcknowledgementState::Dispatched | AcknowledgementState::Acknowledged => {
227 self.session.check_owner()?;
228 return Ok(request.is_none_or(DeliveryAckRequest::admit_handoff));
229 }
230 AcknowledgementState::Waiting => {}
231 }
232 let selection = self.control.selection.lock();
234 self.session.check_owner()?;
235 let retained = self.session.context.db().delivery_message_is_retained(
236 &self.message_id,
237 self.pending.cursor,
238 now_ns(),
239 )?;
240 if selection.revision != self.pending.revision
241 || !retained
242 || !super::matches_filter(
243 &self.session.context,
244 self.group_id,
245 &selection.scope,
246 &selection.filter,
247 )?
248 {
249 *state = AcknowledgementState::Reselect;
250 self.pending.changed.notify_one();
251 return Err(LocalDeliveryError::SelectionChanged);
252 }
253 if dispatch {
254 if !request.is_none_or(DeliveryAckRequest::admit_handoff) {
255 return Ok(false);
256 }
257 *state = AcknowledgementState::Dispatched;
258 }
259 Ok(true)
260 }
261
262 pub fn acknowledge(&self) -> Result<()> {
265 self.acknowledge_with_request(None).map(|_| ())
266 }
267
268 pub fn acknowledge_cancellable(&self, request: &DeliveryAckRequest) -> Result<bool> {
270 if request.is_cancelled() {
271 return Ok(false);
272 }
273 self.acknowledge_with_request(Some(request))
274 }
275
276 fn acknowledge_with_request(&self, request: Option<&DeliveryAckRequest>) -> Result<bool> {
277 let mut state = self.pending.state.lock();
278 if matches!(*state, AcknowledgementState::Acknowledged) {
279 return Ok(true);
280 }
281 if let Some(error) = self.session.background_error() {
282 return Err(error);
283 }
284 match *state {
285 AcknowledgementState::Acknowledged => return Ok(true),
286 AcknowledgementState::Rejected => {
287 return Err(LocalDeliveryError::AcknowledgementRejected);
288 }
289 AcknowledgementState::Failed => return Err(LocalDeliveryError::AcknowledgementFailed),
290 AcknowledgementState::Reselect => return Err(LocalDeliveryError::SelectionChanged),
291 AcknowledgementState::Waiting | AcknowledgementState::Dispatched => {}
292 }
293 let result = self.begin_dispatch(&mut state, true, None).and_then(|_| {
294 self.session.check_owner()?;
295 if let Some(owner) = self.session.owner {
296 let db = self.session.context.db();
297 if let Some(request) = request {
298 return Ok(db.acknowledge_delivery_cancellable_with_clock(
299 owner,
300 self.group_id,
301 self.pending.cursor,
302 now_ns,
303 request,
304 )?);
305 }
306 db.acknowledge_delivery_with_clock(
307 owner,
308 self.group_id,
309 self.pending.cursor,
310 now_ns,
311 )?;
312 }
313 Ok(request.is_none_or(DeliveryAckRequest::admit_replay_acknowledgement))
314 });
315 match result {
316 Err(LocalDeliveryError::SelectionChanged) => Err(LocalDeliveryError::SelectionChanged),
317 Ok(false) => Ok(false),
318 Ok(true) => {
319 *state = AcknowledgementState::Acknowledged;
320 self.pending.changed.notify_one();
321 Ok(true)
322 }
323 Err(error) => {
324 *state = AcknowledgementState::Failed;
325 self.pending.changed.notify_one();
326 Err(self.session.fail(error))
327 }
328 }
329 }
330
331 pub fn enriched_message(&self) -> Result<crate::messages::decoded_message::DecodedMessage> {
334 use crate::messages::enrichment::{EnrichMessageError, enrich_messages};
335 use xmtp_db::group_message::QueryGroupMessage;
336 let result = (|| {
337 self.check_dispatch(false)?;
338 let db = self.session.context.db();
339 let message = db
340 .get_app_visible_group_message(&self.message_id, xmtp_common::time::now_ns())
341 .map_err(StorageError::from)?;
342 let message = match message {
343 Some(message) => message,
344 None => {
345 self.check_dispatch(false)?;
346 return Err(LocalDeliveryError::EnrichedMessageUnavailable);
347 }
348 };
349 let mut messages =
353 enrich_messages(db, &self.group_id, vec![message]).map_err(|error| {
354 LocalDeliveryError::Storage(match error {
355 EnrichMessageError::DbConnection(error) => StorageError::Connection(error),
356 EnrichMessageError::Storage(error) => error,
357 })
358 })?;
359 messages
360 .pop()
361 .ok_or(LocalDeliveryError::EnrichedMessageUnavailable)
362 })();
363 match result {
364 Err(LocalDeliveryError::SelectionChanged) => Err(LocalDeliveryError::SelectionChanged),
365 Err(error) => Err(self.session.fail(error)),
366 Ok(message) => Ok(message),
367 }
368 }
369
370 pub fn reject(&self) {
372 let mut state = self.pending.state.lock();
373 if matches!(*state, AcknowledgementState::Waiting)
374 && self.control.selection.lock().revision != self.pending.revision
375 {
376 *state = AcknowledgementState::Reselect;
377 self.pending.changed.notify_one();
378 } else if matches!(
379 *state,
380 AcknowledgementState::Waiting | AcknowledgementState::Dispatched
381 ) {
382 *state = AcknowledgementState::Rejected;
383 self.pending.changed.notify_one();
384 self.session.close();
385 }
386 }
387}
388
389impl<Context: XmtpSharedContext> Drop for DeliveryAcknowledgement<Context> {
390 fn drop(&mut self) {
391 self.reject();
392 }
393}