旧实现 OnClose 先等待 ready,但 Inject 只有业务方法返回后才发生:业务同步 OnClose → 等 Inject;框架等业务返回 → 永久死锁,流首帧永不输出。改为仅持锁注册回调,不等待 channel 注入;已关闭场景锁外立即回调,避免重入自锁。新增两条回归测试。
47 lines
1.2 KiB
Go
47 lines
1.2 KiB
Go
package fun
|
|
|
|
import (
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// OnClose 必须允许业务方法在 return *Stream 之前同步注册;旧实现等待 Inject,
|
|
// 而 Inject 只有 invoke 在业务方法返回后才调用,形成精确死锁:
|
|
//
|
|
// business OnClose -> wait ready -> invoke wait business return -> never Inject.
|
|
func TestStreamOnCloseBeforeInjectDoesNotBlock(t *testing.T) {
|
|
st := &Stream{}
|
|
registered := make(chan struct{})
|
|
var called atomic.Int32
|
|
go func() {
|
|
st.OnClose(func() { called.Add(1) })
|
|
close(registered)
|
|
}()
|
|
select {
|
|
case <-registered:
|
|
// expected: registration is independent from channel injection
|
|
case <-time.After(time.Second):
|
|
t.Fatal("OnClose blocked before Inject (business method would deadlock)")
|
|
}
|
|
|
|
ch := make(chan any)
|
|
done := make(chan struct{})
|
|
st.Inject(ch, done)
|
|
st.Close()
|
|
if got := called.Load(); got != 1 {
|
|
t.Fatalf("callback called %d times, want 1", got)
|
|
}
|
|
}
|
|
|
|
func TestStreamOnCloseAfterAlreadyClosedRunsImmediately(t *testing.T) {
|
|
st := &Stream{}
|
|
st.Inject(make(chan any), make(chan struct{}))
|
|
st.Close()
|
|
var called atomic.Int32
|
|
st.OnClose(func() { called.Add(1) })
|
|
if got := called.Load(); got != 1 {
|
|
t.Fatalf("callback called %d times, want 1", got)
|
|
}
|
|
}
|