Skip to content

Commit ba59944

Browse files
liledingclaude
andcommitted
Persist retained messages to storage
Move retained messages from in-memory HashMap to Storage backend. This ensures retained messages survive server restarts. - Router now takes Storage reference in constructor - save_retained_message/delete_retained_message used in route() - load_all_retained_messages used in subscribe() for matching - Broker passes Storage to Router Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent f5198b2 commit ba59944

2 files changed

Lines changed: 65 additions & 39 deletions

File tree

src/broker.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ pub struct Broker {
1515
named_clients: DashMap<String, TakeoverAction>,
1616
router: Router,
1717
config: Config,
18-
#[allow(dead_code)] // Will be used for session persistence
1918
storage: Arc<dyn Storage>,
2019
}
2120

@@ -25,7 +24,7 @@ impl Broker {
2524
Arc::new(Self {
2625
sessions: Mutex::new(HashMap::new()),
2726
named_clients: DashMap::new(),
28-
router: Router::new(retained_message_limit),
27+
router: Router::new(retained_message_limit, Arc::clone(&storage)),
2928
config,
3029
storage,
3130
})
@@ -45,7 +44,6 @@ impl Broker {
4544
&self.config
4645
}
4746

48-
#[allow(dead_code)] // Will be used for session persistence
4947
pub fn storage(&self) -> &Arc<dyn Storage> {
5048
&self.storage
5149
}

src/router.rs

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,34 @@ use crate::protocol::packet::{PublishPacket, QoS};
22
use crate::protocol::v3::subscribe_return_codes::{FAILURE, MAXIMUM_QOS_0};
33
use crate::protocol::Packet;
44
use crate::session::Mailbox;
5+
use crate::storage::{PersistedRetainedMessage, Storage, StoredQoS};
6+
use chrono::Utc;
57
use std::collections::{HashMap, HashSet};
8+
use std::sync::Arc;
69
use tokio::sync::RwLock;
7-
use tracing::info;
10+
use tracing::{info, warn};
811

912
struct RouterInternal {
1013
// FILTER -> (SESSION_ID -> (SENDER, QoS))
1114
filters: HashMap<String, HashMap<String, (Mailbox, QoS)>>,
1215
// SESSION_ID -> FILTER
1316
sessions: HashMap<String, HashSet<String>>,
14-
// TOPIC -> RETAINED MESSAGE
15-
retained_messages: HashMap<String, PublishPacket>,
1617
}
1718

1819
pub struct Router {
1920
data: RwLock<RouterInternal>,
21+
storage: Arc<dyn Storage>,
2022
retained_message_limit: usize,
2123
}
2224

2325
impl Router {
24-
pub fn new(retained_message_limit: usize) -> Self {
26+
pub fn new(retained_message_limit: usize, storage: Arc<dyn Storage>) -> Self {
2527
Self {
2628
data: RwLock::new(RouterInternal {
2729
filters: HashMap::new(),
2830
sessions: HashMap::new(),
29-
retained_messages: HashMap::new(),
3031
}),
32+
storage,
3133
retained_message_limit,
3234
}
3335
}
@@ -93,6 +95,15 @@ impl Router {
9395
let mut return_codes = Vec::new();
9496
let mut retained_to_send = Vec::new();
9597

98+
// Load all retained messages from storage
99+
let retained_messages = match self.storage.load_all_retained_messages() {
100+
Ok(msgs) => msgs,
101+
Err(e) => {
102+
warn!("Failed to load retained messages: {}", e);
103+
Vec::new()
104+
}
105+
};
106+
96107
{
97108
let mut router = self.data.write().await;
98109

@@ -130,10 +141,11 @@ impl Router {
130141
);
131142

132143
// Find retained messages matching this subscription
133-
for (topic, retained_packet) in router.retained_messages.iter() {
134-
if Self::topic_matches(topic, &filter.0) {
144+
for retained in &retained_messages {
145+
if Self::topic_matches(&retained.topic, &filter.0) {
146+
let retained_qos = QoS::from(retained.qos);
135147
// Apply QoS downgrade for retained messages too
136-
let effective_qos = match (retained_packet.qos, filter.1) {
148+
let effective_qos = match (retained_qos, filter.1) {
137149
(QoS::AtMostOnce, _) => QoS::AtMostOnce,
138150
(_, QoS::AtMostOnce) => QoS::AtMostOnce,
139151
(QoS::AtLeastOnce, QoS::AtLeastOnce) => QoS::AtLeastOnce,
@@ -142,13 +154,19 @@ impl Router {
142154
(QoS::ExactlyOnce, QoS::ExactlyOnce) => QoS::AtLeastOnce, // We don't support QoS=2 yet
143155
};
144156

145-
let mut packet = retained_packet.clone();
146-
// Retained messages should be sent with retain flag set to true
147-
packet.retain = true;
148-
packet.qos = effective_qos;
157+
let packet = PublishPacket {
158+
topic: retained.topic.clone(),
159+
packet_id: None,
160+
payload: retained.payload.clone(),
161+
qos: effective_qos,
162+
retain: true,
163+
dup: false,
164+
};
149165
retained_to_send.push(packet);
150-
info!("Found retained message for topic {} matching filter {}, downgraded QoS from {:?} to {:?}",
151-
topic, filter.0, retained_packet.qos, effective_qos);
166+
info!(
167+
"Found retained message for topic {} matching filter {}, QoS {:?} -> {:?}",
168+
retained.topic, filter.0, retained_qos, effective_qos
169+
);
152170
}
153171
}
154172
}
@@ -214,39 +232,49 @@ impl Router {
214232

215233
// Handle retained message storage
216234
if packet.retain {
217-
let mut router = self.data.write().await;
218-
219235
if packet.payload.is_empty() {
220236
// Empty payload with retain=true means delete the retained message
221-
router.retained_messages.remove(&packet.topic);
222-
info!("Deleted retained message for topic: {}", packet.topic);
237+
if let Err(e) = self.storage.delete_retained_message(&packet.topic) {
238+
warn!(
239+
"Failed to delete retained message for {}: {}",
240+
packet.topic, e
241+
);
242+
} else {
243+
info!("Deleted retained message for topic: {}", packet.topic);
244+
}
223245
} else {
224246
// Check if we're at the limit and this is a new topic
225-
if !router.retained_messages.contains_key(&packet.topic)
226-
&& router.retained_messages.len() >= self.retained_message_limit
227-
{
247+
let existing = self.storage.load_retained_message(&packet.topic);
248+
let count = self.storage.count_retained_messages().unwrap_or(0);
249+
250+
let is_new = matches!(existing, Ok(None));
251+
if is_new && count >= self.retained_message_limit {
228252
info!(
229253
"Retained message limit reached ({}/{}), dropping message for topic: {}",
230-
router.retained_messages.len(),
231-
self.retained_message_limit,
232-
packet.topic
254+
count, self.retained_message_limit, packet.topic
233255
);
234256
} else {
235257
// Store the retained message
236-
router
237-
.retained_messages
238-
.insert(packet.topic.clone(), packet.clone());
239-
info!(
240-
"Stored retained message for topic: {} (total: {}/{})",
241-
packet.topic,
242-
router.retained_messages.len(),
243-
self.retained_message_limit
244-
);
258+
let retained = PersistedRetainedMessage {
259+
topic: packet.topic.clone(),
260+
payload: packet.payload.clone(),
261+
qos: StoredQoS::from(packet.qos),
262+
updated_at: Utc::now(),
263+
};
264+
if let Err(e) = self.storage.save_retained_message(&retained) {
265+
warn!(
266+
"Failed to save retained message for {}: {}",
267+
packet.topic, e
268+
);
269+
} else {
270+
let new_count = self.storage.count_retained_messages().unwrap_or(0);
271+
info!(
272+
"Stored retained message for topic: {} (total: {}/{})",
273+
packet.topic, new_count, self.retained_message_limit
274+
);
275+
}
245276
}
246277
}
247-
248-
// Continue to route to current subscribers
249-
drop(router);
250278
}
251279

252280
// Route to current subscribers

0 commit comments

Comments
 (0)