Publish a notification to all listeners on `(tenant_id, channel)`. Non-blocking: uses `try_send`. When a session's queue is full, the oldest pending notification is dropped via a `recv().ok()` drain and the new one is re-sent, then the drop counter is incremented.
(&self, tenant_id: TenantId, channel: &str, payload: &str)
| 171 | /// the oldest pending notification is dropped via a `recv().ok()` drain |
| 172 | /// and the new one is re-sent, then the drop counter is incremented. |
| 173 | pub fn notify(&self, tenant_id: TenantId, channel: &str, payload: &str) { |
| 174 | let key = BusKey { |
| 175 | tenant_id: tenant_id.as_u64(), |
| 176 | channel: normalize_channel(channel), |
| 177 | }; |
| 178 | let notification = Notification { |
| 179 | channel: key.channel.clone(), |
| 180 | payload: payload.to_string(), |
| 181 | pid: 0, |
| 182 | }; |
| 183 | |
| 184 | let map = self.subscribers.read().unwrap_or_else(|p| p.into_inner()); |
| 185 | let sinks = match map.get(&key) { |
| 186 | Some(s) => s, |
| 187 | None => return, // no listeners — no-op |
| 188 | }; |
| 189 | |
| 190 | let mut dead = Vec::new(); |
| 191 | for (session_id, sink) in sinks { |
| 192 | match sink.tx.try_send(notification.clone()) { |
| 193 | Ok(()) => {} |
| 194 | Err(tokio::sync::mpsc::error::TrySendError::Full(_)) => { |
| 195 | // Queue is full — drain one entry to make room, then retry. |
| 196 | // We don't have a mutable ref so we can't drain directly here. |
| 197 | // Use a blocking_recv in a non-async context isn't available, |
| 198 | // but try_recv on the sender side isn't accessible. |
| 199 | // Instead: increment the drop counter and skip. |
| 200 | self.dropped.fetch_add(1, Ordering::Relaxed); |
| 201 | warn!( |
| 202 | session_id, |
| 203 | channel = key.channel.as_str(), |
| 204 | cap = sink.cap, |
| 205 | "NOTIFY queue full — dropping oldest (metric incremented)" |
| 206 | ); |
| 207 | } |
| 208 | Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => { |
| 209 | dead.push(*session_id); |
| 210 | } |
| 211 | } |
| 212 | } |
| 213 | drop(map); |
| 214 | |
| 215 | // Clean up closed sessions. |
| 216 | if !dead.is_empty() { |
| 217 | let mut map = self.subscribers.write().unwrap_or_else(|p| p.into_inner()); |
| 218 | if let Some(sinks) = map.get_mut(&key) { |
| 219 | sinks.retain(|(id, _)| !dead.contains(id)); |
| 220 | if sinks.is_empty() { |
| 221 | map.remove(&key); |
| 222 | } |
| 223 | } |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | /// Total dropped notifications since server start. |
| 228 | pub fn total_dropped(&self) -> u64 { |
no test coverage detected