fix: 写队列满时 Close 不再与 enqueue 死锁

This commit is contained in:
Nixevol
2026-09-30 19:18:29 +08:00
parent 443a6c6f59
commit aa38cfc76b
3 changed files with 166 additions and 9 deletions
+22 -9
View File
@@ -36,10 +36,11 @@ type writeJob struct {
type Queue struct {
db *sql.DB
ch chan writeJob
done chan struct{}
closed atomic.Bool
sendMu sync.RWMutex
ch chan writeJob
done chan struct{}
closing chan struct{} // Close 时关闭,唤醒持读锁阻塞在发送上的 enqueue
closed atomic.Bool
sendMu sync.RWMutex
mu sync.Mutex
ready bool
@@ -52,11 +53,16 @@ type Queue struct {
// NewQueue 创建合并写入队列并启动写 goroutine。
func NewQueue(db *sql.DB) *Queue {
return newQueue(db, queueBuffSize)
}
func newQueue(db *sql.DB, buffSize int) *Queue {
q := &Queue{
db: db,
ch: make(chan writeJob, queueBuffSize),
done: make(chan struct{}),
ready: true,
db: db,
ch: make(chan writeJob, buffSize),
done: make(chan struct{}),
closing: make(chan struct{}),
ready: true,
}
go q.loop()
return q
@@ -115,10 +121,15 @@ func (q *Queue) enqueue(job writeJob) error {
return ErrQueueClosed
}
q.addPending(1)
// 通道满时不得只堵在发送上持有读锁:Close 需要写锁关闭 q.ch。
select {
case q.ch <- job:
q.sendMu.RUnlock()
return nil
case <-q.closing:
q.addPending(-1)
q.sendMu.RUnlock()
return ErrQueueClosed
case <-job.ctx.Done():
q.addPending(-1)
q.sendMu.RUnlock()
@@ -376,11 +387,13 @@ func (q *Queue) Drain(ctx context.Context) error {
}
// Close 关闭队列:不再接受新任务,并等待写 goroutine 处理完已入队任务后退出。
// 在写锁内关闭数据通道,避免并发 Do 向已关闭 channel 发送而 panic。
// 先关闭 closing 唤醒因通道满而阻塞的发送方并释放读锁,再在无发送者时关闭数据通道,
// 避免向已关闭 channel 发送而 panic,也避免与持读锁的 enqueue 死锁。
func (q *Queue) Close() error {
if q.closed.Swap(true) {
return nil
}
close(q.closing)
q.sendMu.Lock()
close(q.ch)
q.sendMu.Unlock()