aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/sync/mutex.go
diff options
context:
space:
mode:
authorNia Waldvogel <[email protected]>2021-12-18 14:55:07 -0500
committerNia <[email protected]>2021-12-20 13:35:57 -0500
commit5e719b0d3d2768e9155d9ec1c31663b0a38b1604 (patch)
tree9d3e970d2f1cf827c9cc1ddf284a5320298d2671 /src/sync/mutex.go
parent9eb13884de31ba80804c4a7732b1fcef4af36af7 (diff)
downloadtinygo-5e719b0d3d2768e9155d9ec1c31663b0a38b1604.tar.gz
tinygo-5e719b0d3d2768e9155d9ec1c31663b0a38b1604.zip
sync: fix concurrent read-lock on write-locked RWMutex
This bug can be triggered by the following series of events: A acquires a write lock B starts waiting for a read lock C starts waiting for a read lock A releases the write lock After this, both B and C are supposed to be resumed as a read-lock is available. However, with the previous implementation, only C would be resumed immediately. Other goroutines could immediately acquire the read lock, but B would not be resumed until C released the read lock.
Diffstat (limited to 'src/sync/mutex.go')
-rw-r--r--src/sync/mutex.go122
1 files changed, 109 insertions, 13 deletions
diff --git a/src/sync/mutex.go b/src/sync/mutex.go
index 7284300fd..0f272ad7c 100644
--- a/src/sync/mutex.go
+++ b/src/sync/mutex.go
@@ -5,8 +5,7 @@ import (
_ "unsafe"
)
-// These mutexes assume there is only one thread of operation: no goroutines,
-// interrupts or anything else.
+// These mutexes assume there is only one thread of operation and cannot be accessed safely from interrupts.
type Mutex struct {
locked bool
@@ -41,35 +40,132 @@ func (m *Mutex) Unlock() {
}
type RWMutex struct {
- m Mutex
- readers uint32
+ // waitingWriters are all of the tasks waiting for write locks.
+ waitingWriters task.Stack
+
+ // waitingReaders are all of the tasks waiting for a read lock.
+ waitingReaders task.Stack
+
+ // state is the current state of the RWMutex.
+ // Iff the mutex is completely unlocked, it contains rwMutexStateUnlocked (aka 0).
+ // Iff the mutex is write-locked, it contains rwMutexStateWLocked.
+ // While the mutex is read-locked, it contains the current number of readers.
+ state uint32
}
+const (
+ rwMutexStateUnlocked = uint32(0)
+ rwMutexStateWLocked = ^uint32(0)
+ rwMutexMaxReaders = rwMutexStateWLocked - 1
+)
+
func (rw *RWMutex) Lock() {
- rw.m.Lock()
+ if rw.state == 0 {
+ // The mutex is completely unlocked.
+ // Lock without waiting.
+ rw.state = rwMutexStateWLocked
+ return
+ }
+
+ // Wait for the lock to be released.
+ rw.waitingWriters.Push(task.Current())
+ task.Pause()
}
func (rw *RWMutex) Unlock() {
- rw.m.Unlock()
+ switch rw.state {
+ case rwMutexStateWLocked:
+ // This is correct.
+
+ case rwMutexStateUnlocked:
+ // The mutex is already unlocked.
+ panic("sync: unlock of unlocked RWMutex")
+
+ default:
+ // The mutex is read-locked instead of write-locked.
+ panic("sync: write-unlock of read-locked RWMutex")
+ }
+
+ switch {
+ case rw.maybeUnblockReaders():
+ // Switched over to read mode.
+
+ case rw.maybeUnblockWriter():
+ // Transferred to another writer.
+
+ default:
+ // Nothing is waiting for the lock.
+ rw.state = rwMutexStateUnlocked
+ }
}
func (rw *RWMutex) RLock() {
- if rw.readers == 0 {
- rw.m.Lock()
+ if rw.state == rwMutexStateWLocked {
+ // Wait for the write lock to be released.
+ rw.waitingReaders.Push(task.Current())
+ task.Pause()
+ return
}
- rw.readers++
+
+ if rw.state == rwMutexMaxReaders {
+ panic("sync: too many readers on RWMutex")
+ }
+
+ // Increase the reader count.
+ rw.state++
}
func (rw *RWMutex) RUnlock() {
- if rw.readers == 0 {
+ switch rw.state {
+ case rwMutexStateUnlocked:
+ // The mutex is already unlocked.
panic("sync: unlock of unlocked RWMutex")
+
+ case rwMutexStateWLocked:
+ // The mutex is write-locked instead of read-locked.
+ panic("sync: read-unlock of write-locked RWMutex")
}
- rw.readers--
- if rw.readers == 0 {
- rw.m.Unlock()
+
+ rw.state--
+
+ if rw.state == rwMutexStateUnlocked {
+ // This was the last reader.
+ // Try to unblock a writer.
+ rw.maybeUnblockWriter()
}
}
+func (rw *RWMutex) maybeUnblockReaders() bool {
+ var n uint32
+ for {
+ t := rw.waitingReaders.Pop()
+ if t == nil {
+ break
+ }
+
+ n++
+ scheduleTask(t)
+ }
+ if n == 0 {
+ return false
+ }
+
+ rw.state = n
+ return true
+}
+
+func (rw *RWMutex) maybeUnblockWriter() bool {
+ t := rw.waitingWriters.Pop()
+ if t == nil {
+ return false
+ }
+
+ rw.state = rwMutexStateWLocked
+ scheduleTask(t)
+
+ return true
+}
+
type Locker interface {
Lock()
Unlock()