ProcessRx enqueues f to the per-session rxLoop goroutine. The fast path is non-blocking. If rxInbox is saturated (slow SOCKS consumer or large burst), we wait up to rxInboxBlockTimeout to absorb the transient backpressure before declaring the session dead and killing it. Blocking briefly is far pref
(f *frame.Frame)
| 463 | // stall — the original kill-on-overflow behavior caused mid-stream session |
| 464 | // drops under multi-user fan-out and brief GC pauses. |
| 465 | func (s *Session) ProcessRx(f *frame.Frame) { |
| 466 | s.mu.Lock() |
| 467 | if s.rxClosed { |
| 468 | s.mu.Unlock() |
| 469 | return |
| 470 | } |
| 471 | s.mu.Unlock() |
| 472 | // Fast path: enqueue without blocking when there is room. |
| 473 | select { |
| 474 | case s.rxInbox <- f: |
| 475 | return |
| 476 | case <-s.rxDone: |
| 477 | return |
| 478 | default: |
| 479 | } |
| 480 | // Slow path: rxInbox is full. Block briefly so transient consumer pauses |
| 481 | // (GC, syscall, page fault) don't tear down the session. Only kill on a |
| 482 | // genuine deadlock that exceeds rxInboxBlockTimeout. |
| 483 | t := time.NewTimer(rxInboxBlockTimeout) |
| 484 | defer t.Stop() |
| 485 | select { |
| 486 | case s.rxInbox <- f: |
| 487 | case <-s.rxDone: |
| 488 | case <-t.C: |
| 489 | s.Stop() |
| 490 | } |
| 491 | } |
| 492 | |
| 493 | // deliverRx performs in-order reassembly and delivers payloads to RxChan. |
| 494 | // Called exclusively by rxLoop. Returns true when a FIN frame is processed |