fix(SSE): capture Writer before goroutine, use select for channel send

Address PR review feedback:
- Capture w := c.Writer before goroutine start, use w (not c.Writer)
  inside the goroutine to avoid holding *Context reference
- Move channel send into select alongside context cancellation in all
  examples and tests, preventing goroutine leak when client disconnects
  while blocked on unbuffered send
This commit is contained in:
wjqserver 2026-03-29 16:05:09 +08:00
parent 2f94763c65
commit 559aefeb85
3 changed files with 16 additions and 17 deletions

16
sse.go
View file

@ -132,13 +132,12 @@ func (c *Context) EventStream(streamer func(w io.Writer) bool) {
// select {
// case <-c.Request.Context().Done():
// return // 客户端已断开, 退出 goroutine.
// default:
// eventChan <- touka.Event{
// Id: fmt.Sprintf("%d", i),
// Data: "hello from channel",
// }
// time.Sleep(2 * time.Second)
// case eventChan <- touka.Event{
// Id: fmt.Sprintf("%d", i),
// Data: "hello from channel",
// }:
// }
// time.Sleep(2 * time.Second)
// }
// }()
//
@ -155,7 +154,8 @@ func (c *Context) EventStreamChan(eventChan <-chan Event) {
c.Writer.Flush()
// 捕获稳定的引用, 不持有 *Context 指针, 以免 Context 被 pool 回收后出现竞态.
fl, _ := c.Writer.(http.Flusher)
w := c.Writer
fl, _ := w.(http.Flusher)
reqCtx := c.Request.Context()
goroutineExited := make(chan struct{})
@ -170,7 +170,7 @@ func (c *Context) EventStreamChan(eventChan <-chan Event) {
if !ok {
return
}
if err := event.Render(c.Writer); err != nil {
if err := event.Render(w); err != nil {
return
}
if fl != nil {