mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 17:33:47 +00:00
fix syncx dead lock
This commit is contained in:
parent
e26dd774a9
commit
288d85f411
2 changed files with 127 additions and 20 deletions
|
|
@ -17,36 +17,62 @@
|
|||
// Package syncx contains exotic synchronization primitives.
|
||||
package syncx
|
||||
|
||||
// ClosableMutex is a mutex that can also be closed.
|
||||
// Once closed, it can never be taken again.
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ClosableMutex is a mutex that can be closed. Once closed, it cannot be locked again.
|
||||
type ClosableMutex struct {
|
||||
mu sync.Mutex // Protects the following fields
|
||||
closed bool
|
||||
ch chan struct{}
|
||||
}
|
||||
|
||||
// NewClosableMutex creates a new closable mutex.
|
||||
func NewClosableMutex() *ClosableMutex {
|
||||
ch := make(chan struct{}, 1)
|
||||
ch <- struct{}{}
|
||||
return &ClosableMutex{ch}
|
||||
return &ClosableMutex{ch: ch}
|
||||
}
|
||||
|
||||
// TryLock attempts to lock cm.
|
||||
// If the mutex is closed, TryLock returns false.
|
||||
// TryLock attempts to acquire the lock. Returns true if successful, false if the lock is closed or unavailable.
|
||||
func (cm *ClosableMutex) TryLock() bool {
|
||||
_, ok := <-cm.ch
|
||||
return ok
|
||||
cm.mu.Lock()
|
||||
defer cm.mu.Unlock()
|
||||
if cm.closed {
|
||||
return false
|
||||
}
|
||||
|
||||
// MustLock locks cm.
|
||||
// If the mutex is closed, MustLock panics.
|
||||
select {
|
||||
case <-cm.ch:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// MustLock acquires the lock. Panics if the lock is already closed.
|
||||
func (cm *ClosableMutex) MustLock() {
|
||||
_, ok := <-cm.ch
|
||||
if !ok {
|
||||
cm.mu.Lock()
|
||||
defer cm.mu.Unlock()
|
||||
if cm.closed {
|
||||
panic("mutex closed")
|
||||
}
|
||||
select {
|
||||
case <-cm.ch:
|
||||
return
|
||||
default:
|
||||
panic("mutex is already locked")
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock unlocks cm.
|
||||
// Unlock releases the lock. Panics if the lock is already closed or if called without holding the lock.
|
||||
func (cm *ClosableMutex) Unlock() {
|
||||
cm.mu.Lock()
|
||||
defer cm.mu.Unlock()
|
||||
if cm.closed {
|
||||
panic("Unlock after Close")
|
||||
}
|
||||
select {
|
||||
case cm.ch <- struct{}{}:
|
||||
default:
|
||||
|
|
@ -54,11 +80,13 @@ func (cm *ClosableMutex) Unlock() {
|
|||
}
|
||||
}
|
||||
|
||||
// Close locks the mutex, then closes it.
|
||||
// Close closes the mutex, preventing further lock operations. Panics if called on an already-closed mutex.
|
||||
func (cm *ClosableMutex) Close() {
|
||||
_, ok := <-cm.ch
|
||||
if !ok {
|
||||
cm.mu.Lock()
|
||||
defer cm.mu.Unlock()
|
||||
if cm.closed {
|
||||
panic("Close of already-closed ClosableMutex")
|
||||
}
|
||||
close(cm.ch)
|
||||
cm.closed = true
|
||||
close(cm.ch) // Closing the channel will cause subsequent send operations to panic
|
||||
}
|
||||
|
|
|
|||
79
internal/syncx/mutex_test.go
Normal file
79
internal/syncx/mutex_test.go
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
package syncx
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestClosableMutex_TryLock(t *testing.T) {
|
||||
cm := NewClosableMutex()
|
||||
if !cm.TryLock() {
|
||||
t.Fatal("expected TryLock to succeed")
|
||||
}
|
||||
if cm.TryLock() {
|
||||
t.Fatal("expected TryLock to fail when already locked")
|
||||
}
|
||||
cm.Unlock()
|
||||
if !cm.TryLock() {
|
||||
t.Fatal("expected TryLock to succeed after unlock")
|
||||
}
|
||||
cm.Close()
|
||||
if cm.TryLock() {
|
||||
t.Fatal("expected TryLock to fail after close")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClosableMutex_MustLock(t *testing.T) {
|
||||
cm := NewClosableMutex()
|
||||
cm.MustLock()
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Fatal("expected MustLock to panic when already locked")
|
||||
}
|
||||
}()
|
||||
cm.MustLock()
|
||||
}
|
||||
|
||||
func TestClosableMutex_Unlock(t *testing.T) {
|
||||
cm := NewClosableMutex()
|
||||
cm.MustLock()
|
||||
cm.Unlock()
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Fatal("expected Unlock to panic when already unlocked")
|
||||
}
|
||||
}()
|
||||
cm.Unlock()
|
||||
}
|
||||
|
||||
func TestClosableMutex_Close(t *testing.T) {
|
||||
cm := NewClosableMutex()
|
||||
cm.MustLock()
|
||||
cm.Close()
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Fatal("expected Close to panic when already closed")
|
||||
}
|
||||
}()
|
||||
cm.Close()
|
||||
}
|
||||
|
||||
func TestClosableMutex_Concurrent(t *testing.T) {
|
||||
cm := NewClosableMutex()
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
cm.MustLock()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
cm.Unlock()
|
||||
close(done)
|
||||
}()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if cm.TryLock() {
|
||||
t.Fatal("expected TryLock to fail when locked by another goroutine")
|
||||
}
|
||||
<-done
|
||||
if !cm.TryLock() {
|
||||
t.Fatal("expected TryLock to succeed after other goroutine unlocks")
|
||||
}
|
||||
cm.Unlock()
|
||||
}
|
||||
Loading…
Reference in a new issue