fix: 写队列满时 Close 不再与 enqueue 死锁
This commit is contained in:
+22
-9
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user