Skip to content

Commit 12706b5

Browse files
authored
Merge pull request #1435 from AppFlowy-IO/stream-router-deinit
chore: remove stale broadcast channels from stream router
2 parents 8a86c74 + d4c9901 commit 12706b5

File tree

3 files changed

+91
-10
lines changed

3 files changed

+91
-10
lines changed

libs/appflowy-proto/src/client_message.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::pb;
22
use crate::pb::collab_message::Data;
33
use crate::pb::message::Payload;
4+
#[rustfmt::skip]
45
use crate::pb::{message, SyncRequest};
56
use crate::shared::{Error, ObjectId, Rid, UpdateFlags};
67
use collab::preclude::sync::AwarenessUpdate;

libs/appflowy-proto/src/server_message.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ use crate::pb;
22
use crate::pb::collab_message::Data;
33
use crate::pb::message::Payload;
44
use crate::pb::notification::{PermissionChanged, UserProfileChange};
5-
use crate::pb::{message, SyncRequest};
5+
#[rustfmt::skip]
6+
use crate::pb::{SyncRequest, message};
67
use crate::shared::{Error, ObjectId, Rid, UpdateFlags};
78
use bytes::Bytes;
89
use collab::preclude::sync::AwarenessUpdate;

libs/collab-stream/src/stream_router.rs

Lines changed: 88 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ pub struct StreamRouter {
7878
#[allow(dead_code)]
7979
workers: Vec<Worker>,
8080
metrics: Arc<CollabStreamMetrics>,
81-
channels: DashMap<StreamKey, StreamSender>,
81+
channels: Arc<DashMap<StreamKey, WeakStreamSender>>,
8282
buffer_capacity: usize,
8383
}
8484

@@ -94,6 +94,7 @@ impl StreamRouter {
9494
) -> Result<Self, RedisError> {
9595
let alive = Arc::new(AtomicBool::new(true));
9696
let (tx, rx) = loole::unbounded();
97+
let channels = Arc::new(DashMap::new());
9798
let mut workers = Vec::with_capacity(options.worker_count);
9899
for worker_id in 0..options.worker_count {
99100
let conn = client.get_connection()?;
@@ -105,6 +106,7 @@ impl StreamRouter {
105106
alive.clone(),
106107
&options,
107108
metrics.clone(),
109+
channels.clone(),
108110
);
109111
workers.push(worker);
110112
}
@@ -114,7 +116,7 @@ impl StreamRouter {
114116
workers,
115117
alive,
116118
metrics,
117-
channels: DashMap::new(),
119+
channels,
118120
buffer_capacity: options.xread_count.unwrap_or(10_000),
119121
})
120122
}
@@ -128,16 +130,28 @@ impl StreamRouter {
128130
Entry::Vacant(e) => {
129131
tracing::trace!("creating new stream channel for {}", e.key());
130132
let (tx, rx) = tokio::sync::broadcast::channel(self.buffer_capacity);
133+
e.insert(tx.downgrade());
131134
let last_id = last_id.unwrap_or_else(|| "0".to_string());
132-
let h = StreamHandle::new(stream_key.clone(), last_id, tx.clone());
135+
let h = StreamHandle::new(stream_key.clone(), last_id, tx);
133136
self.buf.send(h).unwrap();
134137
self.metrics.reads_enqueued.inc();
135-
e.insert(tx);
136138
rx
137139
},
138-
Entry::Occupied(e) => {
139-
tracing::trace!("reusing existing stream channel for {}", e.key());
140-
e.get().subscribe()
140+
Entry::Occupied(mut e) => {
141+
let sender = e.get();
142+
if let Some(sender) = sender.upgrade() {
143+
tracing::trace!("reusing existing stream channel for {}", e.key());
144+
sender.subscribe()
145+
} else {
146+
tracing::trace!("creating new stream channel for {}", e.key());
147+
let (tx, rx) = tokio::sync::broadcast::channel(self.buffer_capacity);
148+
e.insert(tx.downgrade());
149+
let last_id = last_id.unwrap_or_else(|| "0".to_string());
150+
let h = StreamHandle::new(stream_key.clone(), last_id, tx);
151+
self.buf.send(h).unwrap();
152+
self.metrics.reads_enqueued.inc();
153+
rx
154+
}
141155
},
142156
};
143157
StreamReader::new(rx)
@@ -188,6 +202,7 @@ struct Worker {
188202
}
189203

190204
impl Worker {
205+
#[allow(clippy::too_many_arguments)]
191206
fn new(
192207
worker_id: usize,
193208
conn: Connection,
@@ -196,6 +211,7 @@ impl Worker {
196211
alive: Arc<AtomicBool>,
197212
options: &StreamRouterOptions,
198213
metrics: Arc<CollabStreamMetrics>,
214+
channels: Arc<DashMap<StreamKey, WeakStreamSender>>,
199215
) -> Self {
200216
let mut xread_options = StreamReadOptions::default();
201217
if let Some(block_millis) = options.xread_block_millis {
@@ -206,13 +222,16 @@ impl Worker {
206222
}
207223
let count = options.xread_streams;
208224
let handle = std::thread::spawn(move || {
209-
if let Err(err) = Self::process_streams(conn, tx, rx, alive, xread_options, count, metrics) {
225+
if let Err(err) =
226+
Self::process_streams(conn, tx, rx, alive, xread_options, count, metrics, channels)
227+
{
210228
tracing::error!("worker {} failed: {}", worker_id, err);
211229
}
212230
});
213231
Self { _handle: handle }
214232
}
215233

234+
#[allow(clippy::too_many_arguments)]
216235
fn process_streams(
217236
mut conn: Connection,
218237
tx: Sender<StreamHandle>,
@@ -221,6 +240,7 @@ impl Worker {
221240
options: StreamReadOptions,
222241
count: usize,
223242
metrics: Arc<CollabStreamMetrics>,
243+
channels: Arc<DashMap<StreamKey, WeakStreamSender>>,
224244
) -> RedisResult<()> {
225245
let mut stream_keys = Vec::with_capacity(count);
226246
let mut message_ids = Vec::with_capacity(count);
@@ -259,6 +279,7 @@ impl Worker {
259279
}
260280

261281
if remove_sender {
282+
channels.remove(&stream.key);
262283
senders.remove(stream.key.as_str());
263284
}
264285
}
@@ -270,7 +291,13 @@ impl Worker {
270291
key_count
271292
);
272293
}
273-
let scheduled = Self::schedule_back(&tx, &mut stream_keys, &mut message_ids, &mut senders);
294+
let scheduled = Self::schedule_back(
295+
&tx,
296+
&mut stream_keys,
297+
&mut message_ids,
298+
&mut senders,
299+
&channels,
300+
);
274301
metrics.reads_enqueued.inc_by(scheduled as u64);
275302
}
276303
Ok(())
@@ -281,6 +308,7 @@ impl Worker {
281308
keys: &mut Vec<StreamKey>,
282309
ids: &mut Vec<String>,
283310
senders: &mut HashMap<&str, (StreamSender, usize)>,
311+
channels: &DashMap<StreamKey, WeakStreamSender>,
284312
) -> usize {
285313
let keys = keys.drain(..);
286314
let mut ids = ids.drain(..);
@@ -289,6 +317,8 @@ impl Worker {
289317
if let Some(last_id) = ids.next() {
290318
if let Some((sender, _)) = senders.remove(key.as_str()) {
291319
if sender.receiver_count() == 0 {
320+
channels.remove(key.as_str());
321+
tracing::trace!("no subscribers for {}, removing channel", key);
292322
continue; // sender is already closed
293323
}
294324
let h = StreamHandle::new(key, last_id, sender);
@@ -345,6 +375,7 @@ impl Worker {
345375
}
346376

347377
pub type RedisMap = HashMap<String, Value>;
378+
type WeakStreamSender = tokio::sync::broadcast::WeakSender<Arc<(String, RedisMap)>>;
348379
type StreamSender = tokio::sync::broadcast::Sender<Arc<(String, RedisMap)>>;
349380
type StreamReceiver = tokio::sync::broadcast::Receiver<Arc<(String, RedisMap)>>;
350381

@@ -372,7 +403,9 @@ mod test {
372403
use rand::random;
373404
use redis::{Client, Commands, FromRedisValue};
374405
use std::sync::Arc;
406+
use std::time::Duration;
375407
use tokio::task::JoinSet;
408+
use tokio::time::timeout;
376409

377410
struct TestMessage {
378411
id: String,
@@ -524,6 +557,52 @@ mod test {
524557
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
525558
assert_eq!(metrics.reads_enqueued.get(), enqueued, "unchanged enqueues");
526559
assert_eq!(metrics.reads_dequeued.get(), dequeued, "unchanged dequeues");
560+
561+
assert!(router.channels.get(&key).is_none());
562+
}
563+
564+
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
565+
async fn observe_unobserve_observe_again() {
566+
const ROUTES_COUNT: usize = 1;
567+
const MSG_PER_ROUTE: usize = 10;
568+
569+
let mut client = Client::open("redis://127.0.0.1/").unwrap();
570+
let mut keys = init_streams(&mut client, ROUTES_COUNT, MSG_PER_ROUTE);
571+
let metrics = Arc::new(CollabStreamMetrics::default());
572+
573+
let router = StreamRouter::with_options(
574+
&client,
575+
metrics.clone(),
576+
StreamRouterOptions {
577+
worker_count: 2,
578+
xread_streams: 100,
579+
xread_block_millis: Some(50),
580+
xread_count: Some(MSG_PER_ROUTE / 2),
581+
},
582+
)
583+
.unwrap();
584+
585+
let key = keys.pop().unwrap();
586+
let mut observer = router.observe(key.clone(), None);
587+
// read half of the messages
588+
for i in 0..MSG_PER_ROUTE {
589+
let msg: TestMessage = observer.next().await.unwrap().unwrap();
590+
assert_eq!(msg.data, format!("{}-{}", key, i));
591+
}
592+
drop(observer);
593+
594+
// try to overflow the tokio broadcast buffer by producing more messages
595+
for i in 0..MSG_PER_ROUTE {
596+
let data = format!("{}-{}", key, i);
597+
let _: String = client.xadd(&key, "*", &[("data", data)]).unwrap();
598+
}
599+
600+
let mut observer = router.observe(key.clone(), None);
601+
let t = Duration::from_millis(100);
602+
for i in 0..MSG_PER_ROUTE {
603+
let msg: TestMessage = timeout(t, observer.next()).await.unwrap().unwrap().unwrap();
604+
assert_eq!(msg.data, format!("{}-{}", key, i));
605+
}
527606
}
528607

529608
fn init_streams(client: &mut Client, stream_count: usize, msgs_per_stream: usize) -> Vec<String> {

0 commit comments

Comments
 (0)