semaphore: add a weighted semaphore implementation
This package provides a weighted semaphore that is context-aware. The code is derived from a similar package inside Google. Change-Id: Id1dad96d79e8ccfd289e4299e8265aa5bdad3a5b Reviewed-on: https://go-review.googlesource.com/38298 Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: Damien Neil <dneil@google.com> Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
a60ad46e0e
commit
5a06fca2c3
3 changed files with 429 additions and 0 deletions
168
semaphore/semaphore_test.go
Normal file
168
semaphore/semaphore_test.go
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package semaphore
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
const maxSleep = 1 * time.Millisecond
|
||||
|
||||
func HammerWeighted(sem *Weighted, n int64, loops int) {
|
||||
for i := 0; i < loops; i++ {
|
||||
sem.Acquire(context.Background(), n)
|
||||
time.Sleep(time.Duration(rand.Int63n(int64(maxSleep/time.Nanosecond))) * time.Nanosecond)
|
||||
sem.Release(n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWeighted(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
n := runtime.GOMAXPROCS(0)
|
||||
sem := NewWeighted(int64(n))
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(n)
|
||||
for i := 0; i < n; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
HammerWeighted(sem, int64(i), 1000)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestWeightedPanic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
if recover() == nil {
|
||||
t.Fatal("release of an unacquired weighted semaphore did not panic")
|
||||
}
|
||||
}()
|
||||
w := NewWeighted(1)
|
||||
w.Release(1)
|
||||
}
|
||||
|
||||
func TestWeightedTryAcquire(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
sem := NewWeighted(2)
|
||||
tries := []bool{}
|
||||
sem.Acquire(ctx, 1)
|
||||
tries = append(tries, sem.TryAcquire(1))
|
||||
tries = append(tries, sem.TryAcquire(1))
|
||||
|
||||
sem.Release(2)
|
||||
|
||||
tries = append(tries, sem.TryAcquire(1))
|
||||
sem.Acquire(ctx, 1)
|
||||
tries = append(tries, sem.TryAcquire(1))
|
||||
|
||||
want := []bool{true, false, true, false}
|
||||
for i := range tries {
|
||||
if tries[i] != want[i] {
|
||||
t.Errorf("tries[%d]: got %t, want %t", i, tries[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWeightedAcquire(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
sem := NewWeighted(2)
|
||||
tryAcquire := func(n int64) bool {
|
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Millisecond)
|
||||
defer cancel()
|
||||
return sem.Acquire(ctx, n) == nil
|
||||
}
|
||||
|
||||
tries := []bool{}
|
||||
sem.Acquire(ctx, 1)
|
||||
tries = append(tries, tryAcquire(1))
|
||||
tries = append(tries, tryAcquire(1))
|
||||
|
||||
sem.Release(2)
|
||||
|
||||
tries = append(tries, tryAcquire(1))
|
||||
sem.Acquire(ctx, 1)
|
||||
tries = append(tries, tryAcquire(1))
|
||||
|
||||
want := []bool{true, false, true, false}
|
||||
for i := range tries {
|
||||
if tries[i] != want[i] {
|
||||
t.Errorf("tries[%d]: got %t, want %t", i, tries[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWeightedDoesntBlockIfTooBig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const n = 2
|
||||
sem := NewWeighted(n)
|
||||
{
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go sem.Acquire(ctx, n+1)
|
||||
}
|
||||
|
||||
g, ctx := errgroup.WithContext(context.Background())
|
||||
for i := n * 3; i > 0; i-- {
|
||||
g.Go(func() error {
|
||||
err := sem.Acquire(ctx, 1)
|
||||
if err == nil {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
sem.Release(1)
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
if err := g.Wait(); err != nil {
|
||||
t.Errorf("NewWeighted(%v) failed to AcquireCtx(_, 1) with AcquireCtx(_, %v) pending", n, n+1)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLargeAcquireDoesntStarve times out if a large call to Acquire starves.
|
||||
// Merely returning from the test function indicates success.
|
||||
func TestLargeAcquireDoesntStarve(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
n := int64(runtime.GOMAXPROCS(0))
|
||||
sem := NewWeighted(n)
|
||||
running := true
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(int(n))
|
||||
for i := n; i > 0; i-- {
|
||||
sem.Acquire(ctx, 1)
|
||||
go func() {
|
||||
defer func() {
|
||||
sem.Release(1)
|
||||
wg.Done()
|
||||
}()
|
||||
for running {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
sem.Release(1)
|
||||
sem.Acquire(ctx, 1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
sem.Acquire(ctx, n)
|
||||
running = false
|
||||
sem.Release(n)
|
||||
wg.Wait()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue