1.3.1 Performance improvements

This commit is contained in:
alitto
2020-05-30 20:58:41 -03:00
parent 721045af8d
commit f8b427ec5a
5 changed files with 255 additions and 210 deletions
+3 -3
View File
@@ -43,9 +43,9 @@ var defaultPoolConfig = poolConfig{
} }
var pondSubjects = []subject{ var pondSubjects = []subject{
{"Pond-Eager", pondPool, poolConfig{maxWorkers: defaultPoolConfig.maxWorkers, maxCapacity: 1000000, strategy: pond.Eager}}, {"Pond-Eager", pondPool, poolConfig{maxWorkers: defaultPoolConfig.maxWorkers, maxCapacity: 1000000, strategy: pond.Eager()}},
{"Pond-Balanced", pondPool, poolConfig{maxWorkers: defaultPoolConfig.maxWorkers, maxCapacity: 1000000, strategy: pond.Balanced}}, {"Pond-Balanced", pondPool, poolConfig{maxWorkers: defaultPoolConfig.maxWorkers, maxCapacity: 1000000, strategy: pond.Balanced()}},
{"Pond-Lazy", pondPool, poolConfig{maxWorkers: defaultPoolConfig.maxWorkers, maxCapacity: 1000000, strategy: pond.Lazy}}, {"Pond-Lazy", pondPool, poolConfig{maxWorkers: defaultPoolConfig.maxWorkers, maxCapacity: 1000000, strategy: pond.Lazy()}},
} }
var otherSubjects = []subject{ var otherSubjects = []subject{
+208 -191
View File
@@ -70,11 +70,12 @@ type WorkerPool struct {
idleWorkerCount int32 idleWorkerCount int32
completedTaskCount uint64 completedTaskCount uint64
// Private properties // Private properties
tasks chan func() tasks chan func()
dispatchedTasks chan func() dispatchedTasks chan func()
purgerQuit chan struct{} stopOnce sync.Once
stopOnce sync.Once waitGroup sync.WaitGroup
waitGroup sync.WaitGroup lastResizeTime time.Time
lastResizeCompletedTasks uint64
// Debug information // Debug information
debug bool debug bool
maxWorkerCount int maxWorkerCount int
@@ -91,8 +92,9 @@ func New(maxWorkers, maxCapacity int, options ...Option) *WorkerPool {
maxWorkers: maxWorkers, maxWorkers: maxWorkers,
maxCapacity: maxCapacity, maxCapacity: maxCapacity,
idleTimeout: defaultIdleTimeout, idleTimeout: defaultIdleTimeout,
strategy: Balanced, strategy: Balanced(),
panicHandler: defaultPanicHandler, panicHandler: defaultPanicHandler,
debug: false,
} }
// Apply all options // Apply all options
@@ -117,7 +119,6 @@ func New(maxWorkers, maxCapacity int, options ...Option) *WorkerPool {
// Create internal channels // Create internal channels
pool.tasks = make(chan func(), pool.maxCapacity) pool.tasks = make(chan func(), pool.maxCapacity)
pool.dispatchedTasks = make(chan func(), pool.maxWorkers) pool.dispatchedTasks = make(chan func(), pool.maxWorkers)
pool.purgerQuit = make(chan struct{})
// Start dispatcher goroutine // Start dispatcher goroutine
pool.waitGroup.Add(1) pool.waitGroup.Add(1)
@@ -127,14 +128,6 @@ func New(maxWorkers, maxCapacity int, options ...Option) *WorkerPool {
pool.dispatch() pool.dispatch()
}() }()
// Start purger goroutine
pool.waitGroup.Add(1)
go func() {
defer pool.waitGroup.Done()
pool.purge()
}()
// Start minWorkers workers // Start minWorkers workers
if pool.minWorkers > 0 { if pool.minWorkers > 0 {
pool.startWorkers(pool.minWorkers, nil) pool.startWorkers(pool.minWorkers, nil)
@@ -203,8 +196,8 @@ func (p *WorkerPool) SubmitBefore(task func(), deadline time.Duration) {
// Stop causes this pool to stop accepting tasks, without waiting for goroutines to exit // Stop causes this pool to stop accepting tasks, without waiting for goroutines to exit
func (p *WorkerPool) Stop() { func (p *WorkerPool) Stop() {
p.stopOnce.Do(func() { p.stopOnce.Do(func() {
// Send signal to stop the purger // Close the tasks channel to prevent receiving new tasks
close(p.purgerQuit) close(p.tasks)
}) })
} }
@@ -219,157 +212,181 @@ func (p *WorkerPool) StopAndWait() {
// dispatch represents the work done by the dispatcher goroutine // dispatch represents the work done by the dispatcher goroutine
func (p *WorkerPool) dispatch() { func (p *WorkerPool) dispatch() {
batch := make([]func(), 0) // Declare vars
batchSize := int(math.Max(float64(p.minWorkers), 1000)) var (
var lastCompletedTasks uint64 = 0 maxBatchSize = 1000
var lastCycle time.Time = time.Now() batch = make([]func(), maxBatchSize)
batchSize = int(math.Max(float64(p.minWorkers), 100))
idleWorkers = 0
dispatchedToIdleWorkers = 0
dispatchedToNewWorkers = 0
dispatchedBlocking = 0
nextTask func() = nil
)
for task := range p.tasks { idleTimer := time.NewTimer(p.idleTimeout)
defer idleTimer.Stop()
idleCount := p.Idle() // Start dispatching cycle
dispatchedImmediately := 0 DispatchCycle:
for {
// Reset idle timer
idleTimer.Reset(p.idleTimeout)
// Dispatch up to idleCount tasks without blocking select {
nextTask := task // Receive a task
ImmediateDispatch: case task, ok := <-p.tasks:
for i := 0; i < idleCount; i++ { if !ok {
// Received the signal to exit
// Attempt to dispatch break DispatchCycle
select {
case p.dispatchedTasks <- nextTask:
dispatchedImmediately++
default:
break ImmediateDispatch
} }
// Attempt to receive another task idleWorkers = p.Idle()
select {
case t, ok := <-p.tasks: // Dispatch tasks to idle workers
if !ok { nextTask, dispatchedToIdleWorkers = p.dispatchToIdleWorkers(task, idleWorkers)
// Nothing to dispatch if nextTask == nil {
nextTask = nil continue DispatchCycle
break ImmediateDispatch
}
nextTask = t
default:
nextTask = nil
break ImmediateDispatch
} }
}
if nextTask == nil {
continue
}
// Start batching tasks // Read up to batchSize tasks without blocking
batch = append(batch, nextTask) p.receiveBatch(nextTask, &batch, batchSize)
// Read up to batchSize tasks without blocking // Resize the pool
BulkReceive: dispatchedToNewWorkers = p.resizePool(batch, dispatchedToIdleWorkers)
for i := 0; i < batchSize-1; i++ {
select {
case t, ok := <-p.tasks:
if !ok {
break BulkReceive
}
if t != nil {
batch = append(batch, t)
}
default:
break BulkReceive
}
}
// Resize the pool dispatchedBlocking = 0
now := time.Now() if len(batch) > dispatchedToNewWorkers {
delta := now.Sub(lastCycle) for _, task := range batch[dispatchedToNewWorkers:] {
workload := len(batch) // Attempt to dispatch the task without blocking
runningCount := p.Running() select {
lastCycle = now case p.dispatchedTasks <- task:
currentCompletedTasks := atomic.LoadUint64(&p.completedTaskCount) default:
completedTasks := int(currentCompletedTasks - lastCompletedTasks) // Block until a worker accepts this task
if completedTasks < 0 { p.dispatchedTasks <- task
completedTasks = 0 dispatchedBlocking++
} }
lastCompletedTasks = currentCompletedTasks
targetDelta := p.calculatePoolSizeDelta(runningCount, idleCount, workload+dispatchedImmediately, completedTasks, delta)
// Start up to targetDelta workers
dispatched := 0
if targetDelta > 0 {
p.startWorkers(targetDelta, batch)
dispatched = workload
if targetDelta < workload {
dispatched = targetDelta
}
} else if targetDelta < 0 {
// Kill targetDelta workers
for i := 0; i < -targetDelta; i++ {
p.dispatchedTasks <- nil
}
}
dispatchedBlocking := 0
if workload > dispatched {
for _, task := range batch[dispatched:] {
// Attempt to dispatch the task without blocking
select {
case p.dispatchedTasks <- task:
default:
// Block until a worker accepts this task
p.dispatchedTasks <- task
dispatchedBlocking++
} }
} }
}
// Adjust batch size // Adjust batch size
if dispatchedBlocking > 0 { if dispatchedBlocking > 0 {
if batchSize > 1 { if batchSize > 1 {
batchSize = 1 batchSize = 1
} }
} else { } else {
maxBatchSize := runningCount + targetDelta batchSize = batchSize * 2
batchSize = batchSize * 2 if batchSize > maxBatchSize {
if batchSize > maxBatchSize { batchSize = maxBatchSize
batchSize = maxBatchSize }
} }
// Timed out waiting for any activity to happen, attempt to resize the pool
case <-idleTimer.C:
p.resizePool(batch[:0], 0)
} }
// Clear batch slice
batch = nil
} }
// Send signal to stop all workers // Send signal to stop all workers
close(p.dispatchedTasks) close(p.dispatchedTasks)
if p.debug {
fmt.Printf("Max workers: %d", p.maxWorkerCount)
}
} }
// purge represents the work done by the purger goroutine func (p *WorkerPool) dispatchToIdleWorkers(task func(), limit int) (nextTask func(), dispatched int) {
func (p *WorkerPool) purge() {
ticker := time.NewTicker(p.idleTimeout)
defer ticker.Stop()
for { // Dispatch up to limit tasks without blocking
nextTask = task
for i := 0; i < limit; i++ {
// Attempt to dispatch without blocking
select { select {
// Timed out waiting for any activity to happen, attempt to resize the pool case p.dispatchedTasks <- nextTask:
case <-ticker.C: nextTask = nil
if p.Idle() > 0 { dispatched++
select { default:
case p.tasks <- nil: // Could not dispatch, return the task
default: return
// If tasks channel is full, there's no need to resize the pool }
}
// Attempt to receive another task
select {
case t, ok := <-p.tasks:
if !ok {
// Nothing else to dispatch
nextTask = nil
return
} }
nextTask = t
// Received the signal to exit default:
case <-p.purgerQuit: nextTask = nil
// Close the tasks channel to prevent receiving new tasks
close(p.tasks)
return return
} }
} }
return
}
func (p *WorkerPool) receiveBatch(task func(), batch *[]func(), batchSize int) {
// Reset batch slice
*batch = (*batch)[:0]
*batch = append(*batch, task)
// Read up to batchSize tasks without blocking
for i := 0; i < batchSize-1; i++ {
select {
case t, ok := <-p.tasks:
if !ok {
return
}
if t != nil {
*batch = append(*batch, t)
}
default:
return
}
}
}
func (p *WorkerPool) resizePool(batch []func(), dispatchedToIdleWorkers int) int {
// Time to resize the pool
now := time.Now()
workload := len(batch)
currentCompletedTasks := atomic.LoadUint64(&p.completedTaskCount)
completedTasksDelta := int(currentCompletedTasks - p.lastResizeCompletedTasks)
if completedTasksDelta < 0 {
completedTasksDelta = 0
}
duration := 0 * time.Millisecond
if !p.lastResizeTime.IsZero() {
duration = now.Sub(p.lastResizeTime)
}
poolSizeDelta := p.calculatePoolSizeDelta(p.Running(), p.Idle(),
workload+dispatchedToIdleWorkers, completedTasksDelta, duration)
// Capture values for next resize cycle
p.lastResizeTime = now
p.lastResizeCompletedTasks = currentCompletedTasks
// Start up to poolSizeDelta workers
dispatched := 0
if poolSizeDelta > 0 {
p.startWorkers(poolSizeDelta, batch)
dispatched = workload
if poolSizeDelta < workload {
dispatched = poolSizeDelta
}
} else if poolSizeDelta < 0 {
// Kill poolSizeDelta workers
for i := 0; i < -poolSizeDelta; i++ {
p.dispatchedTasks <- nil
}
}
return dispatched
} }
// calculatePoolSizeDelta calculates what's the delta to reach the ideal pool size based on the current size and workload // calculatePoolSizeDelta calculates what's the delta to reach the ideal pool size based on the current size and workload
@@ -420,23 +437,25 @@ func (p *WorkerPool) startWorkers(count int, firstTasks []func()) {
p.waitGroup.Add(count) p.waitGroup.Add(count)
// Launch workers // Launch workers
var firstTask func()
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
var firstTask func() = nil firstTask = nil
if i < len(firstTasks) { if i < len(firstTasks) {
firstTask = firstTasks[i] firstTask = firstTasks[i]
} }
worker(firstTask, p.dispatchedTasks, &p.idleWorkerCount, &p.completedTaskCount, func() { go worker(firstTask, p.dispatchedTasks, &p.idleWorkerCount, &p.completedTaskCount, p.decrementWorkers, p.panicHandler)
// Decrement worker count
atomic.AddInt32(&p.workerCount, -1)
// Decrement waiting group semaphore
p.waitGroup.Done()
}, p.panicHandler)
} }
} }
func (p *WorkerPool) decrementWorkers() {
// Decrement worker count
atomic.AddInt32(&p.workerCount, -1)
// Decrement waiting group semaphore
p.waitGroup.Done()
}
// Group creates a new task group // Group creates a new task group
func (p *WorkerPool) Group() *TaskGroup { func (p *WorkerPool) Group() *TaskGroup {
return &TaskGroup{ return &TaskGroup{
@@ -447,53 +466,51 @@ func (p *WorkerPool) Group() *TaskGroup {
// worker launches a worker goroutine // worker launches a worker goroutine
func worker(firstTask func(), tasks chan func(), idleWorkerCount *int32, completedTaskCount *uint64, exitHandler func(), panicHandler func(interface{})) { func worker(firstTask func(), tasks chan func(), idleWorkerCount *int32, completedTaskCount *uint64, exitHandler func(), panicHandler func(interface{})) {
go func() { defer func() {
defer func() { if panic := recover(); panic != nil {
if panic := recover(); panic != nil { // Handle panic
// Handle panic panicHandler(panic)
panicHandler(panic)
// Restart goroutine // Restart goroutine
worker(nil, tasks, idleWorkerCount, completedTaskCount, exitHandler, panicHandler) go worker(nil, tasks, idleWorkerCount, completedTaskCount, exitHandler, panicHandler)
} else { } else {
// Handle exit // Handle exit
exitHandler() exitHandler()
} }
}() }()
// We have received a task, execute it
func() {
// Increment idle count
defer atomic.AddInt32(idleWorkerCount, 1)
if firstTask != nil {
// Increment completed task count
defer atomic.AddUint64(completedTaskCount, 1)
firstTask()
}
}()
for task := range tasks {
if task == nil {
// We have received a signal to quit
return
}
// Decrement idle count
atomic.AddInt32(idleWorkerCount, -1)
// We have received a task, execute it // We have received a task, execute it
func() { func() {
// Increment idle count // Increment idle count
defer atomic.AddInt32(idleWorkerCount, 1) defer atomic.AddInt32(idleWorkerCount, 1)
if firstTask != nil {
// Increment completed task count
defer atomic.AddUint64(completedTaskCount, 1)
firstTask() // Increment completed task count
} defer atomic.AddUint64(completedTaskCount, 1)
task()
}() }()
}
for task := range tasks {
if task == nil {
// We have received a signal to quit
return
}
// Decrement idle count
atomic.AddInt32(idleWorkerCount, -1)
// We have received a task, execute it
func() {
// Increment idle count
defer atomic.AddInt32(idleWorkerCount, 1)
// Increment completed task count
defer atomic.AddUint64(completedTaskCount, 1)
task()
}()
}
}()
} }
// TaskGroup represents a group of related tasks // TaskGroup represents a group of related tasks
+3 -3
View File
@@ -238,7 +238,7 @@ func TestSubmitWithPanic(t *testing.T) {
func TestPoolWithCustomIdleTimeout(t *testing.T) { func TestPoolWithCustomIdleTimeout(t *testing.T) {
pool := pond.New(1, 5, pond.IdleTimeout(2*time.Millisecond)) pool := pond.New(1, 5, pond.IdleTimeout(1*time.Millisecond))
// Submit a task // Submit a task
started := make(chan bool) started := make(chan bool)
@@ -258,8 +258,8 @@ func TestPoolWithCustomIdleTimeout(t *testing.T) {
// Let the task complete // Let the task complete
completed <- true completed <- true
// Wait for idle timeout + 1ms // Wait for some time
time.Sleep(3 * time.Millisecond) time.Sleep(10 * time.Millisecond)
// Worker should have been killed // Worker should have been killed
assertEqual(t, 0, pool.Running()) assertEqual(t, 0, pool.Running())
+38 -10
View File
@@ -12,14 +12,14 @@ var (
// which can reduce throughput under certain conditions. // which can reduce throughput under certain conditions.
// This strategy is meant for worker pools that will operate at a small percentage of their capacity // This strategy is meant for worker pools that will operate at a small percentage of their capacity
// most of the time and may occasionally receive bursts of tasks. // most of the time and may occasionally receive bursts of tasks.
Eager = DynamicResizer(1, 0.01) Eager = func() ResizingStrategy { return DynamicResizer(1, 0.01) }
// Balanced tries to find a balance between responsiveness and throughput. // Balanced tries to find a balance between responsiveness and throughput.
// It's the default strategy and it's suitable for general purpose worker pools or those // It's the default strategy and it's suitable for general purpose worker pools or those
// that will operate close to 50% of their capacity most of the time. // that will operate close to 50% of their capacity most of the time.
Balanced = DynamicResizer(3, 0.01) Balanced = func() ResizingStrategy { return DynamicResizer(3, 0.01) }
// Lazy maximizes throughput at the expense of responsiveness. // Lazy maximizes throughput at the expense of responsiveness.
// This strategy is meant for worker pools that will operate close to their max. capacity most of the time. // This strategy is meant for worker pools that will operate close to their max. capacity most of the time.
Lazy = DynamicResizer(5, 0.01) Lazy = func() ResizingStrategy { return DynamicResizer(5, 0.01) }
) )
// dynamicResizer implements a configurable dynamic resizing strategy // dynamicResizer implements a configurable dynamic resizing strategy
@@ -29,6 +29,7 @@ type dynamicResizer struct {
incomingTasks *ring.Ring incomingTasks *ring.Ring
completedTasks *ring.Ring completedTasks *ring.Ring
duration *ring.Ring duration *ring.Ring
busyWorkers *ring.Ring
} }
// DynamicResizer creates a dynamic resizing strategy that gradually increases or decreases // DynamicResizer creates a dynamic resizing strategy that gradually increases or decreases
@@ -58,15 +59,18 @@ func (r *dynamicResizer) reset() {
r.incomingTasks = ring.New(r.windowSize) r.incomingTasks = ring.New(r.windowSize)
r.completedTasks = ring.New(r.windowSize) r.completedTasks = ring.New(r.windowSize)
r.duration = ring.New(r.windowSize) r.duration = ring.New(r.windowSize)
r.busyWorkers = ring.New(r.windowSize)
// Initialize with 0s // Initialize with 0s
for i := 0; i < r.windowSize; i++ { for i := 0; i < r.windowSize; i++ {
r.incomingTasks.Value = 0 r.incomingTasks.Value = 0
r.completedTasks.Value = 0 r.completedTasks.Value = 0
r.duration.Value = 0 * time.Second r.duration.Value = 0 * time.Second
r.busyWorkers.Value = 0
r.incomingTasks = r.incomingTasks.Next() r.incomingTasks = r.incomingTasks.Next()
r.completedTasks = r.completedTasks.Next() r.completedTasks = r.completedTasks.Next()
r.duration = r.duration.Next() r.duration = r.duration.Next()
r.busyWorkers = r.busyWorkers.Next()
} }
} }
@@ -94,18 +98,28 @@ func (r *dynamicResizer) totalDuration() time.Duration {
return valueSum return valueSum
} }
func (r *dynamicResizer) push(incomingTasks int, completedTasks int, duration time.Duration) { func (r *dynamicResizer) avgBusyWorkers() float64 {
var valueSum int = 0
r.busyWorkers.Do(func(value interface{}) {
valueSum += value.(int)
})
return float64(valueSum) / float64(r.windowSize)
}
func (r *dynamicResizer) push(incomingTasks, completedTasks, busyWorkers int, duration time.Duration) {
r.incomingTasks.Value = incomingTasks r.incomingTasks.Value = incomingTasks
r.completedTasks.Value = completedTasks r.completedTasks.Value = completedTasks
r.duration.Value = duration r.duration.Value = duration
r.busyWorkers.Value = busyWorkers
r.incomingTasks = r.incomingTasks.Next() r.incomingTasks = r.incomingTasks.Next()
r.completedTasks = r.completedTasks.Next() r.completedTasks = r.completedTasks.Next()
r.duration = r.duration.Next() r.duration = r.duration.Next()
r.busyWorkers = r.busyWorkers.Next()
} }
func (r *dynamicResizer) Resize(runningWorkers, idleWorkers, minWorkers, maxWorkers, incomingTasks, completedTasks int, duration time.Duration) int { func (r *dynamicResizer) Resize(runningWorkers, idleWorkers, minWorkers, maxWorkers, incomingTasks, completedTasks int, duration time.Duration) int {
r.push(incomingTasks, completedTasks, duration) r.push(incomingTasks, completedTasks, runningWorkers-idleWorkers, duration)
windowIncomingTasks := r.totalIncomingTasks() windowIncomingTasks := r.totalIncomingTasks()
windowCompletedTasks := r.totalCompletedTasks() windowCompletedTasks := r.totalCompletedTasks()
@@ -116,10 +130,24 @@ func (r *dynamicResizer) Resize(runningWorkers, idleWorkers, minWorkers, maxWork
if runningWorkers == 0 || windowCompletedTasks == 0 { if runningWorkers == 0 || windowCompletedTasks == 0 {
// No workers yet, create as many workers ar.incomingTasks-idleWorkers // No workers yet, create as many workers ar.incomingTasks-idleWorkers
delta := incomingTasks - idleWorkers delta := incomingTasks - idleWorkers
if delta < 0 {
delta = 0
}
return r.fitDelta(delta, runningWorkers, minWorkers, maxWorkers) return r.fitDelta(delta, runningWorkers, minWorkers, maxWorkers)
} }
deltaRate := windowInputRate - windowOutputRate // Calculate max throughput
avgBusyWorkers := r.avgBusyWorkers()
if avgBusyWorkers < 1 {
avgBusyWorkers = 1
}
windowWorkerRate := windowOutputRate / avgBusyWorkers
if windowWorkerRate < 1 {
windowWorkerRate = 1
}
maxOutputRate := windowWorkerRate * float64(runningWorkers)
deltaRate := windowInputRate - maxOutputRate
// No changes, do not resize // No changes, do not resize
if deltaRate == 0 { if deltaRate == 0 {
@@ -127,7 +155,6 @@ func (r *dynamicResizer) Resize(runningWorkers, idleWorkers, minWorkers, maxWork
} }
// If delta % is below the defined tolerance, do not resize // If delta % is below the defined tolerance, do not resize
if r.tolerance > 0 { if r.tolerance > 0 {
deltaPercentage := math.Abs(deltaRate / windowInputRate) deltaPercentage := math.Abs(deltaRate / windowInputRate)
if deltaPercentage < r.tolerance { if deltaPercentage < r.tolerance {
@@ -136,10 +163,11 @@ func (r *dynamicResizer) Resize(runningWorkers, idleWorkers, minWorkers, maxWork
} }
if deltaRate > 0 { if deltaRate > 0 {
// Need to grow the pool
workerRate := windowOutputRate / float64(runningWorkers)
ratio := windowSecs / float64(r.windowSize) ratio := windowSecs / float64(r.windowSize)
delta := int(ratio*(deltaRate/workerRate)) - idleWorkers delta := int(ratio * (deltaRate / windowWorkerRate))
if delta < 0 {
delta = 0
}
if deltaRate > 0 && delta < 1 { if deltaRate > 0 && delta < 1 {
delta = 1 delta = 1
} }
+3 -3
View File
@@ -13,19 +13,19 @@ func TestResize(t *testing.T) {
assertEqual(t, 10, resizer.Resize(0, 0, 1, 100, 10, 0, 1*time.Second)) assertEqual(t, 10, resizer.Resize(0, 0, 1, 100, 10, 0, 1*time.Second))
// Now the input rate grows but below the tolerance (10%) // Now the input rate grows but below the tolerance (10%)
assertEqual(t, 0, resizer.Resize(10, 10, 1, 100, 1, 10, 1*time.Second)) assertEqual(t, -1, resizer.Resize(10, 10, 1, 100, 1, 10, 1*time.Second))
// Now the input rate grows more // Now the input rate grows more
assertEqual(t, 90, resizer.Resize(10, 10, 1, 100, 100000, 11, 1*time.Second)) assertEqual(t, 90, resizer.Resize(10, 10, 1, 100, 100000, 11, 1*time.Second))
// Now there's no new tasks for 3 cycles // Now there's no new tasks for 3 cycles
assertEqual(t, 0, resizer.Resize(10, 10, 1, 100, 0, 100011, 1*time.Second)) assertEqual(t, -1, resizer.Resize(10, 10, 1, 100, 0, 100011, 1*time.Second))
assertEqual(t, -1, resizer.Resize(10, 10, 1, 100, 0, 100011, 1*time.Second)) assertEqual(t, -1, resizer.Resize(10, 10, 1, 100, 0, 100011, 1*time.Second))
assertEqual(t, 0, resizer.Resize(1, 1, 1, 100, 0, 100011, 10*time.Second)) assertEqual(t, 0, resizer.Resize(1, 1, 1, 100, 0, 100011, 10*time.Second))
} }
func TestEagerPool(t *testing.T) { func TestEagerPool(t *testing.T) {
pool := New(100, 1000, Strategy(Eager)) pool := New(100, 1000, Strategy(Eager()))
pool.debug = true pool.debug = true
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {