pax_global_header00006660000000000000000000000064140300447650014515gustar00rootroot0000000000000052 comment=2d3c1eaa054b6e36c7c0dfde398f2b47e4bc5094 semaphore-2.5.0/000077500000000000000000000000001403004476500135045ustar00rootroot00000000000000semaphore-2.5.0/.gitignore000066400000000000000000000004231403004476500154730ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ semaphore-2.5.0/.travis.yml000066400000000000000000000005371403004476500156220ustar00rootroot00000000000000language: go go: - 1.13.x - 1.14.x - 1.15.x - 1.16.x - 1.x - master branches: only: - master install: - go get golang.org/x/tools/cmd/cover - go get github.com/mattn/goveralls script: - go test -v -covermode=count -coverprofile=coverage.out - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKENsemaphore-2.5.0/LICENSE000066400000000000000000000020511403004476500145070ustar00rootroot00000000000000MIT License Copyright (c) 2017 marusama Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. semaphore-2.5.0/README.md000066400000000000000000000114061403004476500147650ustar00rootroot00000000000000semaphore ========= [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/avelino/awesome-go#goroutines) [![Build Status](https://travis-ci.org/marusama/semaphore.svg?branch=master)](https://travis-ci.org/marusama/semaphore) [![Go Report Card](https://goreportcard.com/badge/github.com/marusama/semaphore)](https://goreportcard.com/report/github.com/marusama/semaphore) [![Coverage Status](https://coveralls.io/repos/github/marusama/semaphore/badge.svg?branch=master)](https://coveralls.io/github/marusama/semaphore?branch=master) [![GoDoc](https://godoc.org/github.com/marusama/semaphore?status.svg)](https://godoc.org/github.com/marusama/semaphore) [![License](https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000)](LICENSE) Fast resizable golang semaphore based on CAS * allows weighted acquire/release; * supports cancellation via context; * allows change semaphore limit after creation; * faster than channel based semaphores. ### Usage Initiate ```go import "github.com/marusama/semaphore/v2" ... sem := semaphore.New(5) // new semaphore with limit = 5 ``` Acquire ```go sem.Acquire(ctx, n) // acquire n with context sem.TryAcquire(n) // try acquire n without blocking ... ctx := context.WithTimeout(context.Background(), time.Second) sem.Acquire(ctx, n) // acquire n with timeout ``` Release ```go sem.Release(n) // release n ``` Change semaphore limit ```go sem.SetLimit(new_limit) // set new semaphore limit ``` ### Some benchmarks Run on MacBook Pro (2017) with 3,1GHz Core i5 cpu and 8GB DDR3 ram, macOS High Sierra, go version go1.11.4 darwin/amd64: ```text // this semaphore: BenchmarkSemaphore_Acquire_Release_under_limit_simple-4 20000000 98.6 ns/op 96 B/op 1 allocs/op BenchmarkSemaphore_Acquire_Release_under_limit-4 1000000 1593 ns/op 960 B/op 10 allocs/op BenchmarkSemaphore_Acquire_Release_over_limit-4 100000 20760 ns/op 9600 B/op 100 allocs/op // some other implementations: // golang.org/x/sync/semaphore: BenchmarkXSyncSemaphore_Acquire_Release_under_limit_simple-4 50000000 34.9 ns/op 0 B/op 0 allocs/op BenchmarkXSyncSemaphore_Acquire_Release_under_limit-4 1000000 1103 ns/op 0 B/op 0 allocs/op BenchmarkXSyncSemaphore_Acquire_Release_over_limit-4 30000 65927 ns/op 15985 B/op 299 allocs/op // github.com/abiosoft/semaphore: BenchmarkAbiosoftSemaphore_Acquire_Release_under_limit_simple-4 10000000 208 ns/op 0 B/op 0 allocs/op BenchmarkAbiosoftSemaphore_Acquire_Release_under_limit-4 500000 3147 ns/op 0 B/op 0 allocs/op BenchmarkAbiosoftSemaphore_Acquire_Release_over_limit-4 50000 37148 ns/op 0 B/op 0 allocs/op // github.com/dropbox/godropbox BenchmarkDropboxBoundedSemaphore_Acquire_Release_under_limit_simple-4 20000000 75.9 ns/op 0 B/op 0 allocs/op BenchmarkDropboxBoundedSemaphore_Acquire_Release_under_limit-4 2000000 629 ns/op 0 B/op 0 allocs/op BenchmarkDropboxBoundedSemaphore_Acquire_Release_over_limit-4 200000 27308 ns/op 0 B/op 0 allocs/op BenchmarkDropboxUnboundedSemaphore_Acquire_Release_under_limit_simple-4 50000000 39.7 ns/op 0 B/op 0 allocs/op BenchmarkDropboxUnboundedSemaphore_Acquire_Release_under_limit-4 1000000 1170 ns/op 0 B/op 0 allocs/op BenchmarkDropboxUnboundedSemaphore_Acquire_Release_over_limit-4 100000 21013 ns/op 0 B/op 0 allocs/op // github.com/kamilsk/semaphore BenchmarkKamilskSemaphore_Acquire_Release_under_limit_simple-4 20000000 110 ns/op 16 B/op 1 allocs/op BenchmarkKamilskSemaphore_Acquire_Release_under_limit-4 1000000 1520 ns/op 160 B/op 10 allocs/op BenchmarkKamilskSemaphore_Acquire_Release_over_limit-4 50000 42693 ns/op 1600 B/op 100 allocs/op // github.com/pivotal-golang/semaphore BenchmarkPivotalGolangSemaphore_Acquire_Release_under_limit_simple-4 3000000 558 ns/op 136 B/op 2 allocs/op BenchmarkPivotalGolangSemaphore_Acquire_Release_under_limit-4 200000 9530 ns/op 1280 B/op 20 allocs/op BenchmarkPivotalGolangSemaphore_Acquire_Release_over_limit-4 10000 111264 ns/op 12801 B/op 200 allocs/op ``` You can rerun these benchmarks, just checkout `benchmarks` branch and run `go test -bench=. -benchmem ./bench/...` semaphore-2.5.0/bench/000077500000000000000000000000001403004476500145635ustar00rootroot00000000000000semaphore-2.5.0/bench/bench_0_test.go000066400000000000000000000023141403004476500174470ustar00rootroot00000000000000package bench import ( "context" "sync" "testing" "github.com/marusama/semaphore/v2" ) func BenchmarkSemaphore_Acquire_Release_under_limit_simple(b *testing.B) { sem := semaphore.New(b.N) ctx := context.Background() for i := 0; i < b.N; i++ { sem.Acquire(ctx, 1) sem.Release(1) } if sem.GetCount() != 0 { b.Error("semaphore must have count = 0") } } func BenchmarkSemaphore_Acquire_Release_under_limit(b *testing.B) { sem := semaphore.New(100) c := make(chan struct{}) wg := sync.WaitGroup{} for i := 0; i < 10; i++ { wg.Add(1) go func() { <-c for j := 0; j < b.N; j++ { sem.Acquire(nil, 1) sem.Release(1) } wg.Done() }() } b.ResetTimer() close(c) // start wg.Wait() if sem.GetCount() != 0 { b.Error("semaphore must have count = 0") } } func BenchmarkSemaphore_Acquire_Release_over_limit(b *testing.B) { sem := semaphore.New(10) c := make(chan struct{}) wg := sync.WaitGroup{} for i := 0; i < 100; i++ { wg.Add(1) go func() { <-c for j := 0; j < b.N; j++ { sem.Acquire(nil, 1) sem.Release(1) } wg.Done() }() } b.ResetTimer() close(c) // start wg.Wait() if sem.GetCount() != 0 { b.Error("semaphore must have count = 0") } } semaphore-2.5.0/go.mod000066400000000000000000000000611403004476500146070ustar00rootroot00000000000000module github.com/marusama/semaphore/v2 go 1.11 semaphore-2.5.0/semaphore.go000066400000000000000000000134621403004476500160240ustar00rootroot00000000000000// Copyright 2017 Maru Sama. All rights reserved. // Use of this source code is governed by the MIT license // that can be found in the LICENSE file. // Package semaphore provides an implementation of counting semaphore primitive with possibility to change limit // after creation. This implementation is based on Compare-and-Swap primitive that in general case works faster // than other golang channel-based semaphore implementations. package semaphore // import "github.com/marusama/semaphore/v2" import ( "context" "sync" "sync/atomic" ) // Semaphore counting resizable semaphore synchronization primitive. // Use the Semaphore to control access to a pool of resources. // There is no guaranteed order, such as FIFO or LIFO, in which blocked goroutines enter the semaphore. // A goroutine can enter the semaphore multiple times, by calling the Acquire or TryAcquire methods repeatedly. // To release some or all of these entries, the goroutine can call the Release method // that specifies the number of entries to be released. // Change Semaphore capacity to lower or higher by SetLimit. type Semaphore interface { // Acquire enters the semaphore a specified number of times, blocking only until ctx is done. // This operation can be cancelled via passed context (but it's allowed to pass ctx='nil'). // Method returns context error (ctx.Err()) if the passed context is cancelled, // but this behavior is not guaranteed and sometimes semaphore will still be acquired. Acquire(ctx context.Context, n int) error // TryAcquire acquires the semaphore without blocking. // On success, returns true. On failure, returns false and leaves the semaphore unchanged. TryAcquire(n int) bool // Release exits the semaphore a specified number of times and returns the previous count. Release(n int) int // SetLimit changes current semaphore limit in concurrent way. // It is allowed to change limit many times and it's safe to set limit higher or lower. SetLimit(limit int) // GetLimit returns current semaphore limit. GetLimit() int // GetCount returns current number of occupied entries in semaphore. GetCount() int } // semaphore impl Semaphore intf type semaphore struct { // state holds limit and count in one 64 bits unsigned integer // // state (64 bits) // +-----------------------------------------------------------------+ // limit (high 32 bits) count (low 32 bits) // +--------------------------------|--------------------------------+ // state uint64 // broadcast fields lock sync.RWMutex broadcastCh chan struct{} } // New initializes a new instance of the Semaphore, specifying the maximum number of concurrent entries. func New(limit int) Semaphore { if limit < 0 { panic("semaphore limit must not be negative") } broadcastCh := make(chan struct{}) return &semaphore{ state: uint64(limit) << 32, broadcastCh: broadcastCh, } } func (s *semaphore) Acquire(ctx context.Context, n int) error { if n <= 0 { panic("n must be positive number") } var ctxDoneCh <-chan struct{} if ctx != nil { ctxDoneCh = ctx.Done() } for { // check if context is done select { case <-ctxDoneCh: return ctx.Err() default: } // get current semaphore count and limit state := atomic.LoadUint64(&s.state) count := state & 0xFFFFFFFF limit := state >> 32 // new count newCount := count + uint64(n) if newCount <= limit { if atomic.CompareAndSwapUint64(&s.state, state, limit<<32+newCount) { // acquired return nil } // CAS failed, try again continue } else { // semaphore is full, let's wait s.lock.RLock() 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: return ctx.Err() // waiting for broadcast signal case <-broadcastCh: } } } } func (s *semaphore) TryAcquire(n int) bool { if n <= 0 { panic("n must be positive number") } for { // get current semaphore count and limit state := atomic.LoadUint64(&s.state) count := state & 0xFFFFFFFF limit := state >> 32 // new count newCount := count + uint64(n) if newCount <= limit { if atomic.CompareAndSwapUint64(&s.state, state, limit<<32+newCount) { // acquired return true } // CAS failed, try again continue } // semaphore is full return false } } func (s *semaphore) Release(n int) int { if n <= 0 { panic("n must be positive number") } for { // get current semaphore count and limit state := atomic.LoadUint64(&s.state) count := state & 0xFFFFFFFF if count < uint64(n) { panic("semaphore release without acquire") } // new count newCount := count - uint64(n) if atomic.CompareAndSwapUint64(&s.state, state, state&0xFFFFFFFF00000000+newCount) { newBroadcastCh := make(chan struct{}) s.lock.Lock() oldBroadcastCh := s.broadcastCh s.broadcastCh = newBroadcastCh s.lock.Unlock() // send broadcast signal close(oldBroadcastCh) return int(count) } } } func (s *semaphore) SetLimit(limit int) { if limit < 0 { panic("semaphore limit must not be negative") } for { state := atomic.LoadUint64(&s.state) if atomic.CompareAndSwapUint64(&s.state, state, uint64(limit)<<32+state&0xFFFFFFFF) { newBroadcastCh := make(chan struct{}) s.lock.Lock() oldBroadcastCh := s.broadcastCh s.broadcastCh = newBroadcastCh s.lock.Unlock() // send broadcast signal close(oldBroadcastCh) return } } } func (s *semaphore) GetCount() int { state := atomic.LoadUint64(&s.state) return int(state & 0xFFFFFFFF) } func (s *semaphore) GetLimit() int { state := atomic.LoadUint64(&s.state) return int(state >> 32) } semaphore-2.5.0/semaphore_test.go000066400000000000000000000366371403004476500170740ustar00rootroot00000000000000package semaphore import ( "context" "math/rand" "runtime" "sync" "testing" "time" ) func checkLimit(t *testing.T, sem Semaphore, expected int) { limit := sem.GetLimit() if limit != expected { t.Error("semaphore must have limit = ", expected, ", but has ", limit) } } func checkCount(t *testing.T, sem Semaphore, expected int) { count := sem.GetCount() if count != expected { t.Error("semaphore must have count = ", expected, ", but has ", count) } } func checkLimitAndCount(t *testing.T, sem Semaphore, expectedLimit, expectedCount int) { checkLimit(t, sem, expectedLimit) checkCount(t, sem, expectedCount) } func TestNew(t *testing.T) { tests := []func(){ func() { sem := New(1) checkLimitAndCount(t, sem, 1, 0) }, func() { sem := New(0) checkLimitAndCount(t, sem, 0, 0) }, func() { defer func() { if recover() == nil { t.Error("Panic expected") } }() _ = New(-1) }, } for _, test := range tests { test() } } func TestSemaphore_Acquire(t *testing.T) { sem := New(1) err := sem.Acquire(nil, 1) if err != nil { t.Error("Error returned:", err.Error()) } checkLimitAndCount(t, sem, 1, 1) } func TestSemaphore_Acquire_zero_panic_expected(t *testing.T) { defer func() { if recover() == nil { t.Error("Panic expected") } }() sem := New(1) // acquire zero should panic sem.Acquire(nil, 0) } func TestSemaphore_Acquire_with_ctx(t *testing.T) { sem := New(1) err := sem.Acquire(context.Background(), 1) if err != nil { t.Error("Error returned:", err.Error()) } checkLimitAndCount(t, sem, 1, 1) } func TestSemaphore_Acquire_ctx_done(t *testing.T) { sem := New(1) ctx, cancel := context.WithTimeout(context.Background(), 1*time.Hour) cancel() // make ctx.Done() err := sem.Acquire(ctx, 1) if err != context.Canceled { t.Error("Error is not context.Canceled") } checkLimitAndCount(t, sem, 1, 0) } func TestSemaphore_TryAcquire(t *testing.T) { sem := New(1) if !(sem.TryAcquire(2) == false) { t.Fail() } checkLimitAndCount(t, sem, 1, 0) if !(sem.TryAcquire(1) == true) { t.Fail() } checkLimitAndCount(t, sem, 1, 1) if !(sem.TryAcquire(1) == false) { t.Fail() } checkLimitAndCount(t, sem, 1, 1) } func TestSemaphore_TryAcquire_panic_expected(t *testing.T) { defer func() { if recover() == nil { t.Error("Panic expected") } }() sem := New(1) sem.TryAcquire(0) } func TestSemaphore_TryAcquire_contention(t *testing.T) { sem := New(5) c := make(chan struct{}) wg := sync.WaitGroup{} for i := 0; i < 10; i++ { wg.Add(1) go func() { <-c for j := 0; j < 10000; j++ { if sem.TryAcquire(1) { runtime.Gosched() sem.Release(1) } } wg.Done() }() } close(c) // start wg.Wait() checkLimitAndCount(t, sem, 5, 0) } func TestSemaphore_Release(t *testing.T) { sem := New(1) sem.Acquire(nil, 1) oldCnt := sem.Release(1) if oldCnt != 1 { t.Error("semaphore must have old count = ", 1, ", but has ", oldCnt) } checkLimitAndCount(t, sem, 1, 0) } func TestSemaphore_Release_zero_panic_expected(t *testing.T) { defer func() { if recover() == nil { t.Error("Panic expected") } }() sem := New(1) sem.Acquire(nil, 1) sem.Release(0) } func TestSemaphore_Release_with_ctx(t *testing.T) { sem := New(1) sem.Acquire(context.Background(), 1) oldCnt := sem.Release(1) if oldCnt != 1 { t.Error("semaphore must have old count = ", 1, ", but has ", oldCnt) } checkLimitAndCount(t, sem, 1, 0) } func TestSemaphore_Release_without_Acquire_panic_expected(t *testing.T) { sem := New(1) defer func() { if recover() == nil { t.Error("Panic expected") } }() sem.Release(1) } func TestSemaphore_Release_more_than_Acquired_panic_expected(t *testing.T) { sem := New(1) defer func() { if recover() == nil { t.Error("Panic expected") } }() sem.Acquire(context.Background(), 1) sem.Release(2) } func TestSemaphore_Acquire_2_times_Release_2_times(t *testing.T) { sem := New(2) checkLimitAndCount(t, sem, 2, 0) sem.Acquire(nil, 1) checkLimitAndCount(t, sem, 2, 1) sem.Acquire(nil, 1) checkLimitAndCount(t, sem, 2, 2) oldCnt := sem.Release(1) if oldCnt != 2 { t.Error("semaphore must have old count = ", 2, ", but has ", oldCnt) } checkLimitAndCount(t, sem, 2, 1) oldCnt = sem.Release(1) if oldCnt != 1 { t.Error("semaphore must have old count = ", 1, ", but has ", oldCnt) } checkLimitAndCount(t, sem, 2, 0) } func TestSemaphore_Acquire_Release_under_limit(t *testing.T) { sem := New(100) c := make(chan struct{}) wg := sync.WaitGroup{} for i := 0; i < 10; i++ { wg.Add(1) go func() { <-c err := sem.Acquire(nil, 1) if err != nil { panic(err) } sem.Release(1) wg.Done() }() } close(c) // start wg.Wait() checkLimitAndCount(t, sem, 100, 0) } func TestSemaphore_Acquire_Release_under_limit_ctx_done(t *testing.T) { sem := New(10) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() c := make(chan struct{}) wg := sync.WaitGroup{} for i := 0; i < 100; i++ { wg.Add(1) go func() { <-c for { err := sem.Acquire(ctx, 1) if err != nil { if err == context.DeadlineExceeded { break } panic(err) } sem.Release(1) } wg.Done() }() } close(c) // start wg.Wait() checkLimitAndCount(t, sem, 10, 0) } func TestSemaphore_Acquire_Release_over_limit(t *testing.T) { sem := New(1) c := make(chan struct{}) wg := sync.WaitGroup{} for i := 0; i < 100; i++ { wg.Add(1) go func() { <-c for j := 0; j < 1000; j++ { err := sem.Acquire(nil, 1) if err != nil { panic(err) } sem.Release(1) } wg.Done() }() } close(c) // start wg.Wait() checkLimitAndCount(t, sem, 1, 0) } func TestSemaphore_Acquire_Release_over_limit_ctx_done(t *testing.T) { sem := New(1) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() c := make(chan struct{}) wg := sync.WaitGroup{} for i := 0; i < 100; i++ { wg.Add(1) go func() { <-c for { err := sem.Acquire(ctx, 1) if err != nil { if err == context.DeadlineExceeded { break } panic(err) } sem.Release(1) } wg.Done() }() } close(c) // start wg.Wait() checkLimitAndCount(t, sem, 1, 0) } func TestSemaphore_SetLimit(t *testing.T) { sem := New(1) checkLimitAndCount(t, sem, 1, 0) sem.SetLimit(2) checkLimitAndCount(t, sem, 2, 0) sem.SetLimit(1) checkLimitAndCount(t, sem, 1, 0) sem.SetLimit(0) checkLimitAndCount(t, sem, 0, 0) } func TestSemaphore_SetLimit_negative_limit_panic_expected(t *testing.T) { sem := New(1) checkLimitAndCount(t, sem, 1, 0) defer func() { if recover() == nil { t.Error("Panic expected") } }() sem.SetLimit(-1) } func TestSemaphore_SetLimit_increase_limit(t *testing.T) { sem := New(1) checkLimitAndCount(t, sem, 1, 0) sem.Acquire(nil, 1) checkLimitAndCount(t, sem, 1, 1) sem.SetLimit(2) checkLimitAndCount(t, sem, 2, 1) sem.Acquire(nil, 1) checkLimitAndCount(t, sem, 2, 2) sem.Release(1) checkLimitAndCount(t, sem, 2, 1) sem.Release(1) checkLimitAndCount(t, sem, 2, 0) } func TestSemaphore_SetLimit_decrease_limit(t *testing.T) { sem := New(2) checkLimitAndCount(t, sem, 2, 0) sem.Acquire(nil, 1) checkLimitAndCount(t, sem, 2, 1) sem.Acquire(nil, 1) checkLimitAndCount(t, sem, 2, 2) sem.SetLimit(1) checkLimitAndCount(t, sem, 1, 2) sem.Release(1) checkLimitAndCount(t, sem, 1, 1) sem.Release(1) checkLimitAndCount(t, sem, 1, 0) } func TestSemaphore_New0_SetLimit1_Acquire_Release_SetLimit0(t *testing.T) { sem := New(0) checkLimitAndCount(t, sem, 0, 0) sem.SetLimit(1) checkLimitAndCount(t, sem, 1, 0) sem.Acquire(nil, 1) checkLimitAndCount(t, sem, 1, 1) oldCnt := sem.Release(1) if oldCnt != 1 { t.Error("semaphore must have old count = ", 1, ", but has ", oldCnt) } checkLimitAndCount(t, sem, 1, 0) sem.SetLimit(0) checkLimitAndCount(t, sem, 0, 0) } func TestSemaphore_SetLimit_increase_broadcast(t *testing.T) { getWGs := func(cnt int) []*sync.WaitGroup { wgs := make([]*sync.WaitGroup, cnt) for i := range wgs { wgs[i] = &sync.WaitGroup{} wgs[i].Add(1) } return wgs } sem := New(1) sem.Acquire(nil, 1) innerWGs := getWGs(2) outerWGs := getWGs(2) go func() { outerWGs[0].Wait() // here we a trying to acquire over limit checkLimitAndCount(t, sem, 1, 1) sem.Acquire(nil, 1) innerWGs[0].Done() outerWGs[1].Wait() sem.Release(1) innerWGs[1].Done() }() checkLimitAndCount(t, sem, 1, 1) outerWGs[0].Done() time.Sleep(100 * time.Millisecond) // increase limit so inner goroutine can acquire semaphore sem.SetLimit(2) checkLimitAndCount(t, sem, 2, 1) innerWGs[0].Wait() checkLimitAndCount(t, sem, 2, 2) outerWGs[1].Done() innerWGs[1].Wait() checkLimitAndCount(t, sem, 2, 1) sem.Release(1) checkLimitAndCount(t, sem, 2, 0) } func TestSemaphore_Acquire_Release_SetLimit_under_limit(t *testing.T) { sem := New(100) c := make(chan struct{}) wg := sync.WaitGroup{} for i := 0; i < 10; i++ { wg.Add(1) go func() { <-c for j := 0; j < 10000; j++ { err := sem.Acquire(nil, 1) if err != nil { panic(err) } runtime.Gosched() sem.Release(1) runtime.Gosched() } wg.Done() }() } c2 := make(chan struct{}) wg2 := sync.WaitGroup{} wg2.Add(1) go func() { <-c for { select { case <-c2: sem.SetLimit(100) wg2.Done() return default: } newLimit := rand.Intn(50) + 50 // range [50, 99] sem.SetLimit(newLimit) runtime.Gosched() } }() close(c) // start wg.Wait() close(c2) // stop 'set limit' goroutine wg2.Wait() checkLimitAndCount(t, sem, 100, 0) } func TestSemaphore_Acquire_Release_SetLimit_under_limit_ctx_done(t *testing.T) { sem := New(100) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() c := make(chan struct{}) wg := sync.WaitGroup{} for i := 0; i < 10; i++ { wg.Add(1) go func() { <-c for { err := sem.Acquire(ctx, 1) if err != nil { if err == context.DeadlineExceeded { break } panic(err) } runtime.Gosched() sem.Release(1) runtime.Gosched() } wg.Done() }() } wg.Add(1) go func() { <-c for { select { case <-ctx.Done(): sem.SetLimit(100) wg.Done() return default: } newLimit := rand.Intn(50) + 50 // range [50, 99] sem.SetLimit(newLimit) runtime.Gosched() } }() close(c) // start wg.Wait() checkLimitAndCount(t, sem, 100, 0) } func TestSemaphore_Acquire_Release_SetLimit_over_limit(t *testing.T) { sem := New(1) c := make(chan struct{}) wg := sync.WaitGroup{} for i := 0; i < 100; i++ { wg.Add(1) go func() { <-c for j := 0; j < 10000; j++ { err := sem.Acquire(nil, 1) if err != nil { panic(err) } runtime.Gosched() sem.Release(1) runtime.Gosched() } wg.Done() }() } c2 := make(chan struct{}) wg2 := sync.WaitGroup{} wg2.Add(1) go func() { <-c for { select { case <-c2: sem.SetLimit(1) wg2.Done() return default: } newLimit := rand.Intn(50) + 1 // range [1, 50] sem.SetLimit(newLimit) runtime.Gosched() } }() close(c) // start wg.Wait() close(c2) // stop 'set limit' goroutine wg2.Wait() checkLimitAndCount(t, sem, 1, 0) } func TestSemaphore_Acquire_Release_SetLimit_over_limit_ctx_done(t *testing.T) { sem := New(1) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() c := make(chan struct{}) wg := sync.WaitGroup{} for i := 0; i < 100; i++ { wg.Add(1) go func() { <-c for { err := sem.Acquire(ctx, 1) if err != nil { if err == context.DeadlineExceeded { break } panic(err) } runtime.Gosched() sem.Release(1) runtime.Gosched() } wg.Done() }() } wg.Add(1) go func() { <-c for { select { case <-ctx.Done(): sem.SetLimit(1) wg.Done() return default: } newLimit := rand.Intn(50) + 1 // range [1, 50] sem.SetLimit(newLimit) runtime.Gosched() } }() close(c) // start wg.Wait() checkLimitAndCount(t, sem, 1, 0) } func TestSemaphore_Acquire_Release_SetLimit_random_limit(t *testing.T) { sem := New(1) c := make(chan struct{}) wg := sync.WaitGroup{} for i := 0; i < 100; i++ { wg.Add(1) go func() { <-c for j := 0; j < 10000; j++ { err := sem.Acquire(nil, 1) if err != nil { panic(err) } runtime.Gosched() sem.Release(1) runtime.Gosched() } wg.Done() }() } c2 := make(chan struct{}) wg2 := sync.WaitGroup{} wg2.Add(1) go func() { <-c for { select { case <-c2: sem.SetLimit(1) wg2.Done() return default: } newLimit := rand.Intn(200) + 1 // range [1, 200] sem.SetLimit(newLimit) runtime.Gosched() } }() close(c) // start wg.Wait() close(c2) // stop 'set limit' goroutine wg2.Wait() checkLimitAndCount(t, sem, 1, 0) } func TestSemaphore_Acquire_Release_SetLimit_random_limit_ctx_done(t *testing.T) { sem := New(1) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() c := make(chan struct{}) wg := sync.WaitGroup{} for i := 0; i < 100; i++ { wg.Add(1) go func() { <-c for { err := sem.Acquire(ctx, 1) if err != nil { if err == context.DeadlineExceeded { break } panic(err) } runtime.Gosched() sem.Release(1) runtime.Gosched() } wg.Done() }() } wg.Add(1) go func() { <-c for { select { case <-ctx.Done(): sem.SetLimit(1) wg.Done() return default: } newLimit := rand.Intn(200) + 1 // range [1, 200] sem.SetLimit(newLimit) runtime.Gosched() } }() close(c) // start wg.Wait() 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") } } } func TestSemaphore_weighted_acquire_gt_release(t *testing.T) { runTest := func(done chan struct{}) { weight := 1 biggerWeight := weight * 10 limit := weight * 100 releaseSignalChan := make(chan struct{}) semp := New(limit) for i := 0; i < limit; i++ { _ = semp.Acquire(context.TODO(), weight) go func() { <-releaseSignalChan semp.Release(weight) }() } // broadcast to release go close(releaseSignalChan) // Try to acquire while releasing _ = semp.Acquire(context.TODO(), biggerWeight) close(done) } // Run several iterations of the runTest method, which hopefully will result // in one the iterations hanging. for run := 0; run < 100000; 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") } } }