Skip to content

Commit

Permalink
remove demoting locks from sync package
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidVorick committed Oct 3, 2015
1 parent 9d0dc41 commit eeebfe1
Show file tree
Hide file tree
Showing 2 changed files with 7 additions and 81 deletions.
39 changes: 7 additions & 32 deletions sync/lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ type RWMutex struct {
callDepth int
maxLockTime time.Duration

outer sync.Mutex
inner sync.RWMutex
mu sync.RWMutex
}

// lockInfo contains information about when and how a lock call was made.
Expand Down Expand Up @@ -67,10 +66,9 @@ func (rwm *RWMutex) threadedDeadlockFinder() {

// Undo the deadlock and delete the entry from the map.
if info.read {
rwm.inner.RUnlock()
rwm.mu.RUnlock()
} else {
rwm.inner.Unlock()
rwm.outer.Unlock()
rwm.mu.Unlock()
}
delete(rwm.openLocks, id)
}
Expand All @@ -95,10 +93,9 @@ func (rwm *RWMutex) safeLock(read bool) int {

// Lock the mutex.
if read {
rwm.inner.RLock()
rwm.mu.RLock()
} else {
rwm.outer.Lock()
rwm.inner.Lock()
rwm.mu.Lock()
}

// Safely register that a lock has been triggered.
Expand Down Expand Up @@ -137,29 +134,13 @@ func (rwm *RWMutex) safeUnlock(read bool, id int) {

// Remove the lock and delete the entry from the map.
if read {
rwm.inner.RUnlock()
rwm.mu.RUnlock()
} else {
rwm.inner.Unlock()
rwm.outer.Unlock()
rwm.mu.Unlock()
}
delete(rwm.openLocks, id)
}

// safeDemote demotes a safelock from a Lock to a RLock.
func (rwm *RWMutex) safeDemote() {
rwm.openLocksMutex.Lock()
defer rwm.openLocksMutex.Unlock()

// Demote the lock. Ordering is important. First the inner lock is released
// and then regrabbed as a readlock. Then the outer lock is released.
// Because no safelock can grab the inner lock as a writelock until it has
// the outer lock, there is no risk of changes being made during the
// transition.
rwm.inner.Unlock()
rwm.inner.RLock()
rwm.outer.Unlock()
}

// RLock will read lock the RWMutex. The return value must be used as input
// when calling RUnlock.
func (rwm *RWMutex) RLock() int {
Expand All @@ -178,12 +159,6 @@ func (rwm *RWMutex) Lock() int {
return rwm.safeLock(false)
}

// Demote will demote the lock from a writelock to a readlock. Demote should
// only be called on a writelocked safelock.
func (rwm *RWMutex) Demote() {
rwm.safeDemote()
}

// Unlock will unlock the RWMutex. The return value of calling Lock must be
// used as input.
func (rwm *RWMutex) Unlock(id int) {
Expand Down
49 changes: 0 additions & 49 deletions sync/lock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,52 +147,3 @@ func TestLockSafety(t *testing.T) {
t.Error("test took too long to complete")
}
}

// TestDemote checks that lock demotion works correctly.
func TestDemote(t *testing.T) {
if testing.Short() {
t.SkipNow()
}

startTime := time.Now().Unix()
value := 0
safeLock := New(time.Second, 1)
_ = safeLock.Lock()

readThreads := 100
var wg sync.WaitGroup
wg.Add(readThreads)
for i := 0; i < readThreads; i++ {
go func() {
readID := safeLock.RLock()
defer safeLock.RUnlock(readID)

if value != 1 {
t.Error("reading is not happening correctly")
}

// Sleep 250 milliseconds after grabbing the readlock. Because
// there are a bunch of threads, if the readlocks are not grabbing
// the lock in parallel the test will take a long time.
time.Sleep(time.Millisecond * 250)
wg.Done()
}()
}
value = 1

// A combination of sleep and gosched to give priority to the other
// threads.
time.Sleep(time.Millisecond * 100)
runtime.Gosched()
time.Sleep(time.Millisecond * 100)
safeLock.Demote()

// Wait for all of the threads to finish sleeping.
wg.Wait()
// Check that the whole test took under 3 seconds. If the readlocks were
// efficiently being grabbed in parallel, the test should be subtantially
// less than 3 seconds.
if time.Now().Unix()-startTime > 3 {
t.Error("test took too long to complete")
}
}

0 comments on commit eeebfe1

Please sign in to comment.