Skip to content

Commit abd911a

Browse files
pjenveyJR Conlin
andauthored
feat: record a couple more state changes, particularly Delivered (#853)
and generalize report_handler's error reporting Closes SYNC-4644 --------- Co-authored-by: JR Conlin <jr+git@mozilla.com>
1 parent 26e374f commit abd911a

9 files changed

Lines changed: 82 additions & 43 deletions

File tree

autoconnect/autoconnect-common/src/protocol.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,13 @@ impl FromStr for ClientMessage {
8585
///
8686
#[derive(Debug, Deserialize)]
8787
pub struct ClientAck {
88-
// The channel_id which received messages
88+
/// The channel_id which received messages
8989
#[serde(rename = "channelID")]
9090
pub channel_id: Uuid,
91-
// The corresponding version number for the message.
91+
/// The corresponding version number for the message.
9292
pub version: String,
93+
/// An optional code categorizing the status of the ACK
94+
pub code: Option<u16>,
9395
}
9496

9597
#[derive(Debug, Serialize)]

autoconnect/autoconnect-ws/autoconnect-ws-sm/src/identified/mod.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,20 @@ impl WebPushClient {
237237
let uaid = self.uaid;
238238
let connected_at = self.connected_at;
239239
rt::spawn(async move {
240+
#[cfg(not(feature = "reliable_report"))]
240241
app_state.db.save_messages(&uaid, notifs).await?;
241-
// XXX: record reliability
242+
#[cfg(feature = "reliable_report")]
243+
{
244+
app_state.db.save_messages(&uaid, notifs.clone()).await?;
245+
for mut notif in notifs {
246+
notif
247+
.record_reliability(
248+
&app_state.reliability,
249+
autopush_common::reliability::ReliabilityState::Stored,
250+
)
251+
.await;
252+
}
253+
}
242254
debug!("Finished saving unacked direct notifs, checking for reconnect");
243255
let Some(user) = app_state.db.get_user(&uaid).await? else {
244256
return Err(SMErrorKind::Internal(format!(
@@ -342,6 +354,10 @@ struct AckState {
342354
unacked_direct_notifs: Vec<Notification>,
343355
/// List of unAck'd sent notifications from storage
344356
unacked_stored_notifs: Vec<Notification>,
357+
/// List of Ack'd timestamp notifications from storage, cleared
358+
/// via `increment_storage`
359+
#[cfg(feature = "reliable_report")]
360+
acked_stored_timestamp_notifs: Vec<Notification>,
345361
/// Either the `current_timestamp` value in storage (returned from
346362
/// `fetch_messages`) or the last unAck'd timestamp Message's
347363
/// `sortkey_timestamp` (returned from `fetch_timestamp_messages`).

autoconnect/autoconnect-ws/autoconnect-ws-sm/src/identified/on_client_msg.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -214,16 +214,15 @@ impl WebPushClient {
214214
"version" => &notif.version
215215
);
216216
// Get the stored notification record.
217-
let n = &self.ack_state.unacked_stored_notifs[pos];
217+
let n = &mut self.ack_state.unacked_stored_notifs[pos];
218+
let is_topic = n.topic.is_some();
218219
debug!("✅ Ack notif: {:?}", &n);
219-
// TODO: Record "ack'd" reliability_id, if present.
220220
// Only force delete Topic messages, since they don't have a timestamp.
221221
// Other messages persist in the database, to be, eventually, cleaned up by their
222222
// TTL. We will need to update the `CurrentTimestamp` field for the channel
223223
// record. Use that field to set the baseline timestamp for when to pull messages
224224
// in the future.
225-
// Topic/legacy messages have no sortkey_timestamp
226-
if n.sortkey_timestamp.is_none() {
225+
if is_topic {
227226
debug!(
228227
"✅ WebPushClient:ack removing Stored, sort_key: {}",
229228
&n.chidmessageid()
@@ -232,8 +231,21 @@ impl WebPushClient {
232231
.db
233232
.remove_message(&self.uaid, &n.chidmessageid())
234233
.await?;
234+
// NOTE: timestamp messages may still be in state of flux: they're not fully
235+
// ack'd (removed/unable to be resurrected) until increment_storage is called,
236+
// so their reliability is recorded there
237+
#[cfg(feature = "reliable_report")]
238+
n.record_reliability(
239+
&self.app_state.reliability,
240+
autopush_common::reliability::ReliabilityState::Delivered,
241+
)
242+
.await;
243+
}
244+
let n = self.ack_state.unacked_stored_notifs.remove(pos);
245+
#[cfg(feature = "reliable_report")]
246+
if !is_topic {
247+
self.ack_state.acked_stored_timestamp_notifs.push(n);
235248
}
236-
self.ack_state.unacked_stored_notifs.remove(pos);
237249
self.stats.stored_acked += 1;
238250
continue;
239251
};

autoconnect/autoconnect-ws/autoconnect-ws-sm/src/identified/on_server_notif.rs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::mem;
2+
13
use cadence::{Counted, CountedExt};
24

35
use autoconnect_common::protocol::{ServerMessage, ServerNotification};
@@ -116,24 +118,34 @@ impl WebPushClient {
116118
// Filter out TTL expired messages
117119
let now_sec = sec_since_epoch();
118120
// Topic messages require immediate deletion from the db
119-
let mut expired_topic_sort_keys = vec![];
121+
let mut expired_messages = vec![];
122+
// NOTE: Vec::extract_if (stabilizing soon) can negate the need for the
123+
// inner msg.clone()
120124
messages.retain(|msg| {
121125
if !msg.expired(now_sec) {
122126
return true;
123127
}
124128
if msg.sortkey_timestamp.is_none() {
125-
expired_topic_sort_keys.push(msg.chidmessageid());
129+
expired_messages.push(msg.clone());
126130
}
127131
// XXX: record ReliabilityState::Expired?
128132
false
129133
});
130134
// TODO: A batch remove_messages would be nicer
131-
for sort_key in expired_topic_sort_keys {
132-
trace!("🉑 removing expired topic sort key: {sort_key}");
135+
#[allow(unused_mut)]
136+
for mut msg in expired_messages {
137+
let chidmessageid = msg.chidmessageid();
138+
trace!("🉑 removing expired topic chidmessageid: {chidmessageid}");
133139
self.app_state
134140
.db
135-
.remove_message(&self.uaid, &sort_key)
141+
.remove_message(&self.uaid, &chidmessageid)
136142
.await?;
143+
#[cfg(feature = "reliable_report")]
144+
msg.record_reliability(
145+
&self.app_state.reliability,
146+
autopush_common::reliability::ReliabilityState::Expired,
147+
)
148+
.await;
137149
}
138150

139151
self.flags.increment_storage = !include_topic && timestamp.is_some();
@@ -311,6 +323,15 @@ impl WebPushClient {
311323
.db
312324
.increment_storage(&self.uaid, timestamp)
313325
.await?;
326+
#[cfg(feature = "reliable_report")]
327+
{
328+
let mut notifs = mem::take(&mut self.ack_state.acked_stored_timestamp_notifs);
329+
self.record_state(
330+
&mut notifs,
331+
autopush_common::reliability::ReliabilityState::Delivered,
332+
)
333+
.await;
334+
}
314335
self.flags.increment_storage = false;
315336
Ok(())
316337
}
Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
use actix_web::{web::Data, HttpResponse};
22

3-
use crate::server::AppState;
3+
use crate::{
4+
error::{ApiErrorKind, ApiResult},
5+
server::AppState,
6+
};
47

5-
pub async fn report_handler(app_state: Data<AppState>) -> HttpResponse {
8+
pub async fn report_handler(app_state: Data<AppState>) -> ApiResult<HttpResponse> {
69
let reliability = &app_state.reliability;
710

8-
autopush_common::reliability::report_handler(reliability).await
11+
autopush_common::reliability::report_handler(reliability)
12+
.await
13+
.map_err(|e| ApiErrorKind::General(format!("Reliability report error: {e}")).into())
914
}

autopush-common/src/db/client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ pub trait DbClient: Send + Sync {
8686
async fn increment_storage(&self, uaid: &Uuid, timestamp: u64) -> DbResult<()>;
8787

8888
/// Delete a notification
89-
async fn remove_message(&self, uaid: &Uuid, sort_key: &str) -> DbResult<()>;
89+
async fn remove_message(&self, uaid: &Uuid, chidmessageid: &str) -> DbResult<()>;
9090

9191
/// Check if the router table exists
9292
async fn router_table_exists(&self) -> DbResult<bool>;

autopush-common/src/db/mock.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ impl DbClient for Arc<MockDbClient> {
9191
Arc::as_ref(self).increment_storage(uaid, timestamp).await
9292
}
9393

94-
async fn remove_message(&self, uaid: &Uuid, sort_key: &str) -> DbResult<()> {
95-
Arc::as_ref(self).remove_message(uaid, sort_key).await
94+
async fn remove_message(&self, uaid: &Uuid, chidmessageid: &str) -> DbResult<()> {
95+
Arc::as_ref(self).remove_message(uaid, chidmessageid).await
9696
}
9797

9898
#[cfg(feature = "reliable_report")]

autopush-common/src/notification.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub struct Notification {
2828
#[serde(skip_serializing_if = "Option::is_none")]
2929
pub reliability_id: Option<String>,
3030
#[cfg(feature = "reliable_report")]
31+
#[serde(skip_serializing_if = "Option::is_none")]
3132
pub reliable_state: Option<crate::reliability::ReliabilityState>,
3233
}
3334

autopush-common/src/reliability.rs

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -285,30 +285,12 @@ pub fn gen_report(values: HashMap<String, i32>) -> Result<String> {
285285
Ok(encoded)
286286
}
287287

288-
pub async fn report_handler(reliability: &Arc<PushReliability>) -> HttpResponse {
289-
if let Err(err) = reliability.gc().await {
290-
error!("🔍🟥 Reporting, Error {:?}", &err);
291-
return HttpResponse::InternalServerError()
292-
.content_type("text/plain")
293-
.body(format!("# ERROR: {err}\n"));
294-
};
295-
match reliability.report().await {
296-
Ok(values) => match gen_report(values) {
297-
Ok(report) => HttpResponse::Ok()
298-
.content_type("application/openmetrics-text; version=1.0.0; charset=utf-8")
299-
.body(report),
300-
Err(e) => HttpResponse::InternalServerError()
301-
.content_type("text/plain")
302-
.body(format!("# ERROR: {e}\n")),
303-
},
304-
Err(e) => {
305-
error!("🔍🟥 Reporting, Error {:?}", &e);
306-
// NOTE: This will NOT be read by Prometheus, but serves as a diagnostic message.
307-
HttpResponse::InternalServerError()
308-
.content_type("text/plain")
309-
.body(format!("# ERROR: {e}\n"))
310-
}
311-
}
288+
pub async fn report_handler(reliability: &Arc<PushReliability>) -> Result<HttpResponse> {
289+
reliability.gc().await?;
290+
let report = gen_report(reliability.report().await?)?;
291+
Ok(HttpResponse::Ok()
292+
.content_type("application/openmetrics-text; version=1.0.0; charset=utf-8")
293+
.body(report))
312294
}
313295

314296
#[cfg(test)]

0 commit comments

Comments
 (0)