@@ -43,6 +43,11 @@ pub struct SessionConfig {
4343 pub acks_subscribe : bool ,
4444}
4545
46+ /// Bound for the per-connection event funnel — caps how many pending outbound
47+ /// updates a single connection may buffer before pumps start dropping (matches
48+ /// the hand-rolled WS server's default per-client channel capacity).
49+ const EVENT_BUFFER : usize = 256 ;
50+
4651/// One subscription update on its way back to the connection's send half.
4752struct SubEvent {
4853 sub : String ,
@@ -91,8 +96,12 @@ pub async fn run_session<C, D>(
9196 let mut session = dispatch. open ( & ctx) ;
9297
9398 // Event funnel: every per-subscription pump sends its updates here; the main
94- // loop is the sole writer to the connection.
95- let ( event_tx, mut event_rx) = mpsc:: unbounded_channel :: < SubEvent > ( ) ;
99+ // loop is the sole writer to the connection. **Bounded** so a slow client
100+ // (one whose socket is backpressured, stalling the main loop) cannot grow the
101+ // funnel without limit — the pumps drop on overflow rather than accumulate
102+ // (events carry a monotonic `seq`, so a client can detect the gap). This
103+ // restores the bounded-buffer slow-client protection the hand-rolled loops had.
104+ let ( event_tx, mut event_rx) = mpsc:: channel :: < SubEvent > ( EVENT_BUFFER ) ;
96105 // Per-connection subscription pumps; the engine future is their sole owner.
97106 let mut subs: FuturesUnordered < BoxFut < ' static , ( ) > > = FuturesUnordered :: new ( ) ;
98107 // sub-id → cancel handle (dropping/sending the oneshot cancels the pump,
@@ -248,9 +257,10 @@ async fn send_reply_err<C: EnvelopeCodec + ?Sized>(
248257async fn pump_subscription (
249258 sub_id : String ,
250259 mut stream : BoxStream < ' static , Payload > ,
251- tx : mpsc:: UnboundedSender < SubEvent > ,
260+ tx : mpsc:: Sender < SubEvent > ,
252261 mut cancel : oneshot:: Receiver < ( ) > ,
253262) {
263+ use tokio:: sync:: mpsc:: error:: TrySendError ;
254264 let mut seq: u64 = 0 ;
255265 loop {
256266 tokio:: select! {
@@ -260,8 +270,12 @@ async fn pump_subscription(
260270 next = stream. next( ) => match next {
261271 Some ( data) => {
262272 seq += 1 ;
263- if tx. send( SubEvent { sub: sub_id. clone( ) , seq, data } ) . is_err( ) {
264- break ; // funnel closed → connection gone
273+ // `try_send` keeps the pump non-blocking: a backpressured
274+ // funnel drops this update (slow-client protection) rather
275+ // than stalling the bus; only a closed funnel ends the pump.
276+ match tx. try_send( SubEvent { sub: sub_id. clone( ) , seq, data } ) {
277+ Ok ( ( ) ) | Err ( TrySendError :: Full ( _) ) => { }
278+ Err ( TrySendError :: Closed ( _) ) => break , // connection gone
265279 }
266280 }
267281 None => break , // stream exhausted
0 commit comments