mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
<!-- Describe what has changed in this PR --> **What changed?** Removed all calls to `rand.Seed` with a _random_ initialization value. <!-- Tell your future self why have you made these changes --> **Why?** Less code is better. [From the docs](https://pkg.go.dev/math/rand#Seed): > If Seed is not called, the generator is seeded randomly at program startup. > Prior to Go 1.20, the generator was seeded like Seed(1) at program startup. [...] > Deprecated: **As of Go 1.20 there is no reason to call Seed with a random value**. [...] <!-- How have you verified this change? Tested locally? Added a unit test? Checked in staging env? --> **How did you test it?** Existing tests. <!-- Assuming the worst case, what can be broken when deploying this change to production? --> **Potential risks** According to the docs, this is a no-op change in terms of behaviour. <!-- Is this PR a hotfix candidate or require that a notification be sent to the broader community? (Yes/No) --> **Is hotfix candidate?** No.
267 lines
5.5 KiB
Go
267 lines
5.5 KiB
Go
// The MIT License
|
|
//
|
|
// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved.
|
|
//
|
|
// Copyright (c) 2020 Uber Technologies, Inc.
|
|
//
|
|
// 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.
|
|
|
|
package locks
|
|
|
|
import (
|
|
"math/rand"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/stretchr/testify/suite"
|
|
)
|
|
|
|
type (
|
|
conditionVariableSuite struct {
|
|
*require.Assertions
|
|
suite.Suite
|
|
|
|
lock sync.Locker
|
|
cv *ConditionVariableImpl
|
|
}
|
|
)
|
|
|
|
func TestConditionVariableSuite(t *testing.T) {
|
|
s := new(conditionVariableSuite)
|
|
suite.Run(t, s)
|
|
}
|
|
|
|
func (s *conditionVariableSuite) SetupSuite() {
|
|
s.Assertions = require.New(s.T())
|
|
}
|
|
|
|
func (s *conditionVariableSuite) TearDownSuite() {
|
|
}
|
|
|
|
func (s *conditionVariableSuite) SetupTest() {
|
|
s.lock = &sync.Mutex{}
|
|
s.cv = NewConditionVariable(s.lock)
|
|
}
|
|
|
|
func (s *conditionVariableSuite) TearDownTest() {
|
|
|
|
}
|
|
|
|
func (s *conditionVariableSuite) TestChannelSize_New() {
|
|
s.testChannelSize(s.cv.channel)
|
|
}
|
|
|
|
func (s *conditionVariableSuite) TestChannelSize_Broadcast() {
|
|
s.cv.Broadcast()
|
|
s.testChannelSize(s.cv.channel)
|
|
}
|
|
|
|
func (s *conditionVariableSuite) testChannelSize(
|
|
channel chan struct{},
|
|
) {
|
|
// assert channel size == 1
|
|
select {
|
|
case channel <- struct{}{}:
|
|
// noop
|
|
default:
|
|
s.Fail("conditional variable size should be 1")
|
|
}
|
|
|
|
select {
|
|
case channel <- struct{}{}:
|
|
s.Fail("conditional variable size should be 1")
|
|
default:
|
|
// noop
|
|
}
|
|
}
|
|
|
|
func (s *conditionVariableSuite) TestSignal() {
|
|
signalWaitGroup := sync.WaitGroup{}
|
|
signalWaitGroup.Add(1)
|
|
|
|
waitGroup := sync.WaitGroup{}
|
|
waitGroup.Add(1)
|
|
|
|
waitFn := func() {
|
|
defer waitGroup.Done()
|
|
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
|
|
signalWaitGroup.Done()
|
|
s.cv.Wait(nil)
|
|
}
|
|
go waitFn()
|
|
|
|
signalWaitGroup.Wait()
|
|
s.lock.Lock()
|
|
func() {}()
|
|
s.lock.Unlock()
|
|
s.cv.Signal()
|
|
waitGroup.Wait()
|
|
}
|
|
|
|
func (s *conditionVariableSuite) TestInterrupt() {
|
|
interruptWaitGroup := sync.WaitGroup{}
|
|
interruptWaitGroup.Add(1)
|
|
|
|
waitGroup := sync.WaitGroup{}
|
|
waitGroup.Add(1)
|
|
interruptChan := make(chan struct{})
|
|
|
|
waitFn := func() {
|
|
defer waitGroup.Done()
|
|
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
|
|
interruptWaitGroup.Done()
|
|
s.cv.Wait(interruptChan)
|
|
}
|
|
go waitFn()
|
|
|
|
interruptWaitGroup.Wait()
|
|
s.lock.Lock()
|
|
func() {}()
|
|
s.lock.Unlock()
|
|
interruptChan <- struct{}{}
|
|
waitGroup.Wait()
|
|
}
|
|
|
|
func (s *conditionVariableSuite) TestBroadcast() {
|
|
waitThreads := 256
|
|
|
|
broadcastWaitGroup := sync.WaitGroup{}
|
|
broadcastWaitGroup.Add(waitThreads)
|
|
|
|
waitGroup := sync.WaitGroup{}
|
|
waitGroup.Add(waitThreads)
|
|
|
|
waitFn := func() {
|
|
defer waitGroup.Done()
|
|
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
|
|
broadcastWaitGroup.Done()
|
|
s.cv.Wait(nil)
|
|
}
|
|
for i := 0; i < waitThreads; i++ {
|
|
go waitFn()
|
|
}
|
|
|
|
broadcastWaitGroup.Wait()
|
|
s.lock.Lock()
|
|
func() {}()
|
|
s.lock.Unlock()
|
|
s.cv.Broadcast()
|
|
waitGroup.Wait()
|
|
}
|
|
|
|
func (s *conditionVariableSuite) TestCase_ProducerConsumer() {
|
|
signalRatio := 0.8
|
|
numProducer := 256
|
|
numConsumer := 256
|
|
totalToken := numProducer * numConsumer * 10
|
|
tokenPerProducer := totalToken / numProducer
|
|
tokenPerConsumer := totalToken / numConsumer
|
|
|
|
lock := &sync.Mutex{}
|
|
tokens := 0
|
|
notifyProducerCV := NewConditionVariable(lock)
|
|
notifyConsumerCV := NewConditionVariable(lock)
|
|
|
|
waitGroup := sync.WaitGroup{}
|
|
waitGroup.Add(numProducer + numConsumer)
|
|
|
|
produceFn := func() {
|
|
defer waitGroup.Done()
|
|
remainingToken := tokenPerProducer
|
|
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
|
|
for remainingToken > 0 {
|
|
for tokens > 0 {
|
|
randSignalBroadcast(notifyConsumerCV, signalRatio)
|
|
notifyProducerCV.Wait(nil)
|
|
}
|
|
|
|
produce := rand.Intn(remainingToken + 1)
|
|
tokens += produce
|
|
remainingToken -= produce
|
|
}
|
|
randSignalBroadcast(notifyConsumerCV, signalRatio)
|
|
}
|
|
|
|
consumerFn := func() {
|
|
defer waitGroup.Done()
|
|
remainingToken := 0
|
|
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
|
|
for remainingToken < tokenPerConsumer {
|
|
for tokens == 0 {
|
|
randSignalBroadcast(notifyProducerCV, signalRatio)
|
|
notifyConsumerCV.Wait(nil)
|
|
}
|
|
|
|
consume := min(tokens, tokenPerConsumer-remainingToken)
|
|
tokens -= consume
|
|
remainingToken += consume
|
|
}
|
|
randSignalBroadcast(notifyProducerCV, signalRatio)
|
|
if tokens > 0 {
|
|
randSignalBroadcast(notifyConsumerCV, signalRatio)
|
|
}
|
|
}
|
|
|
|
for i := 0; i < numConsumer; i++ {
|
|
go consumerFn()
|
|
}
|
|
for i := 0; i < numProducer; i++ {
|
|
go produceFn()
|
|
}
|
|
|
|
waitGroup.Wait()
|
|
}
|
|
|
|
func randSignalBroadcast(
|
|
cv ConditionVariable,
|
|
signalRatio float64,
|
|
) {
|
|
if rand.Float64() <= signalRatio {
|
|
cv.Signal()
|
|
} else {
|
|
cv.Broadcast()
|
|
}
|
|
}
|
|
|
|
func min(left int, right int) int {
|
|
if left < right {
|
|
return left
|
|
} else if left > right {
|
|
return right
|
|
} else {
|
|
return left
|
|
}
|
|
}
|