Skip to content

Commit

Permalink
Merge pull request #2 from mrtracy/mtracy/broadcast_race_fix
Browse files Browse the repository at this point in the history
Fix race condition resulting in hung Acquire()
  • Loading branch information
marusama committed Oct 27, 2018
2 parents 567f172 + 458c3ab commit 8b8ee3a
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 0 deletions.
6 changes: 6 additions & 0 deletions semaphore.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ func (s *semaphore) Acquire(ctx context.Context, n int) error {
broadcastCh := s.broadcastCh
s.lock.RUnlock()

// ensure that the state is the same as when we first checked; this
// ensures that the broadcastCh will eventually be closed by a Release.
if atomic.LoadUint64(&s.state) != state {
continue
}

select {
// check if context is done
case <-ctxDoneCh:
Expand Down
40 changes: 40 additions & 0 deletions semaphore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -783,3 +783,43 @@ func TestSemaphore_Acquire_Release_SetLimit_random_limit_ctx_done(t *testing.T)

checkLimitAndCount(t, sem, 1, 0)
}

func TestSemaphore_broadcast_channel_race(t *testing.T) {
threads := 4
acquiresPerRun := 5

// runTest method creates a short-lived semaphore with contention over only
// a few attempts per thread. The condition being tested is a regression
// in which the last thread to call acquire in the group will hang forever.
runTest := func(done chan struct{}) {
sem := New(1)
wg := sync.WaitGroup{}
for i := 0; i < threads; i++ {
wg.Add(1)
go func() {
for j := 0; j < acquiresPerRun; j++ {
runtime.Gosched()
if err := sem.Acquire(context.Background(), 1); err != nil {
t.Fatal(err)
}
sem.Release(1)
}
wg.Done()
}()
}
wg.Wait()
close(done)
}

// Run several iterations of the runTest method, which hopefully will result
// in one the iterations hanging.
for run := 0; run < 1000; run++ {
done := make(chan struct{})
go runTest(done)
select {
case <-done:
case <-time.After(10 * time.Second):
t.Fatalf("single run took more than ten seconds to finish")
}
}
}

0 comments on commit 8b8ee3a

Please sign in to comment.