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 contains exotic synchronization primitives.
|
||||||
package syncx
|
package syncx
|
||||||
|
|
||||||
// ClosableMutex is a mutex that can also be closed.
|
import (
|
||||||
// Once closed, it can never be taken again.
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClosableMutex is a mutex that can be closed. Once closed, it cannot be locked again.
|
||||||
type ClosableMutex struct {
|
type ClosableMutex struct {
|
||||||
|
mu sync.Mutex // Protects the following fields
|
||||||
|
closed bool
|
||||||
ch chan struct{}
|
ch chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewClosableMutex creates a new closable mutex.
|
||||||
func NewClosableMutex() *ClosableMutex {
|
func NewClosableMutex() *ClosableMutex {
|
||||||
ch := make(chan struct{}, 1)
|
ch := make(chan struct{}, 1)
|
||||||
ch <- struct{}{}
|
ch <- struct{}{}
|
||||||
return &ClosableMutex{ch}
|
return &ClosableMutex{ch: ch}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TryLock attempts to lock cm.
|
// TryLock attempts to acquire the lock. Returns true if successful, false if the lock is closed or unavailable.
|
||||||
// If the mutex is closed, TryLock returns false.
|
|
||||||
func (cm *ClosableMutex) TryLock() bool {
|
func (cm *ClosableMutex) TryLock() bool {
|
||||||
_, ok := <-cm.ch
|
cm.mu.Lock()
|
||||||
return ok
|
defer cm.mu.Unlock()
|
||||||
|
if cm.closed {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustLock locks cm.
|
select {
|
||||||
// If the mutex is closed, MustLock panics.
|
case <-cm.ch:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustLock acquires the lock. Panics if the lock is already closed.
|
||||||
func (cm *ClosableMutex) MustLock() {
|
func (cm *ClosableMutex) MustLock() {
|
||||||
_, ok := <-cm.ch
|
cm.mu.Lock()
|
||||||
if !ok {
|
defer cm.mu.Unlock()
|
||||||
|
if cm.closed {
|
||||||
panic("mutex 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() {
|
func (cm *ClosableMutex) Unlock() {
|
||||||
|
cm.mu.Lock()
|
||||||
|
defer cm.mu.Unlock()
|
||||||
|
if cm.closed {
|
||||||
|
panic("Unlock after Close")
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case cm.ch <- struct{}{}:
|
case cm.ch <- struct{}{}:
|
||||||
default:
|
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() {
|
func (cm *ClosableMutex) Close() {
|
||||||
_, ok := <-cm.ch
|
cm.mu.Lock()
|
||||||
if !ok {
|
defer cm.mu.Unlock()
|
||||||
|
if cm.closed {
|
||||||
panic("Close of already-closed ClosableMutex")
|
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