2023-11-21 06:53:46 +03:00
|
|
|
// MIT License
|
|
|
|
|
|
|
|
// Copyright (c) 2023 Andy Pan
|
|
|
|
|
|
|
|
// 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 ants
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
2024-06-17 21:42:55 +03:00
|
|
|
|
|
|
|
"golang.org/x/sync/errgroup"
|
2023-11-21 06:53:46 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
// LoadBalancingStrategy represents the type of load-balancing algorithm.
|
|
|
|
type LoadBalancingStrategy int
|
|
|
|
|
|
|
|
const (
|
|
|
|
// RoundRobin distributes task to a list of pools in rotation.
|
|
|
|
RoundRobin LoadBalancingStrategy = 1 << (iota + 1)
|
|
|
|
|
|
|
|
// LeastTasks always selects the pool with the least number of pending tasks.
|
|
|
|
LeastTasks
|
|
|
|
)
|
|
|
|
|
|
|
|
// MultiPool consists of multiple pools, from which you will benefit the
|
|
|
|
// performance improvement on basis of the fine-grained locking that reduces
|
|
|
|
// the lock contention.
|
2023-11-21 08:22:02 +03:00
|
|
|
// MultiPool is a good fit for the scenario where you have a large number of
|
2023-11-21 06:53:46 +03:00
|
|
|
// tasks to submit, and you don't want the single pool to be the bottleneck.
|
|
|
|
type MultiPool struct {
|
|
|
|
pools []*Pool
|
|
|
|
index uint32
|
|
|
|
state int32
|
|
|
|
lbs LoadBalancingStrategy
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewMultiPool instantiates a MultiPool with a size of the pool list and a size
|
|
|
|
// per pool, and the load-balancing strategy.
|
|
|
|
func NewMultiPool(size, sizePerPool int, lbs LoadBalancingStrategy, options ...Option) (*MultiPool, error) {
|
2024-03-26 14:35:40 +03:00
|
|
|
if lbs != RoundRobin && lbs != LeastTasks {
|
|
|
|
return nil, ErrInvalidLoadBalancingStrategy
|
|
|
|
}
|
2023-11-21 06:53:46 +03:00
|
|
|
pools := make([]*Pool, size)
|
|
|
|
for i := 0; i < size; i++ {
|
|
|
|
pool, err := NewPool(sizePerPool, options...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
pools[i] = pool
|
|
|
|
}
|
|
|
|
return &MultiPool{pools: pools, lbs: lbs}, nil
|
|
|
|
}
|
|
|
|
|
2023-11-21 08:22:02 +03:00
|
|
|
func (mp *MultiPool) next(lbs LoadBalancingStrategy) (idx int) {
|
|
|
|
switch lbs {
|
2023-11-21 06:53:46 +03:00
|
|
|
case RoundRobin:
|
|
|
|
if idx = int((atomic.AddUint32(&mp.index, 1) - 1) % uint32(len(mp.pools))); idx == -1 {
|
|
|
|
idx = 0
|
|
|
|
}
|
|
|
|
return
|
|
|
|
case LeastTasks:
|
|
|
|
leastTasks := 1<<31 - 1
|
|
|
|
for i, pool := range mp.pools {
|
|
|
|
if n := pool.Running(); n < leastTasks {
|
|
|
|
leastTasks = n
|
|
|
|
idx = i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Submit submits a task to a pool selected by the load-balancing strategy.
|
2023-11-21 08:22:02 +03:00
|
|
|
func (mp *MultiPool) Submit(task func()) (err error) {
|
2023-11-21 06:53:46 +03:00
|
|
|
if mp.IsClosed() {
|
|
|
|
return ErrPoolClosed
|
|
|
|
}
|
2023-11-21 08:22:02 +03:00
|
|
|
if err = mp.pools[mp.next(mp.lbs)].Submit(task); err == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err == ErrPoolOverload && mp.lbs == RoundRobin {
|
|
|
|
return mp.pools[mp.next(LeastTasks)].Submit(task)
|
|
|
|
}
|
|
|
|
return
|
2023-11-21 06:53:46 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Running returns the number of the currently running workers across all pools.
|
|
|
|
func (mp *MultiPool) Running() (n int) {
|
|
|
|
for _, pool := range mp.pools {
|
|
|
|
n += pool.Running()
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// RunningByIndex returns the number of the currently running workers in the specific pool.
|
|
|
|
func (mp *MultiPool) RunningByIndex(idx int) (int, error) {
|
|
|
|
if idx < 0 || idx >= len(mp.pools) {
|
|
|
|
return -1, ErrInvalidPoolIndex
|
|
|
|
}
|
|
|
|
return mp.pools[idx].Running(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Free returns the number of available workers across all pools.
|
|
|
|
func (mp *MultiPool) Free() (n int) {
|
|
|
|
for _, pool := range mp.pools {
|
|
|
|
n += pool.Free()
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// FreeByIndex returns the number of available workers in the specific pool.
|
|
|
|
func (mp *MultiPool) FreeByIndex(idx int) (int, error) {
|
|
|
|
if idx < 0 || idx >= len(mp.pools) {
|
|
|
|
return -1, ErrInvalidPoolIndex
|
|
|
|
}
|
|
|
|
return mp.pools[idx].Free(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Waiting returns the number of the currently waiting tasks across all pools.
|
|
|
|
func (mp *MultiPool) Waiting() (n int) {
|
|
|
|
for _, pool := range mp.pools {
|
|
|
|
n += pool.Waiting()
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// WaitingByIndex returns the number of the currently waiting tasks in the specific pool.
|
|
|
|
func (mp *MultiPool) WaitingByIndex(idx int) (int, error) {
|
|
|
|
if idx < 0 || idx >= len(mp.pools) {
|
|
|
|
return -1, ErrInvalidPoolIndex
|
|
|
|
}
|
|
|
|
return mp.pools[idx].Waiting(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cap returns the capacity of this multi-pool.
|
|
|
|
func (mp *MultiPool) Cap() (n int) {
|
|
|
|
for _, pool := range mp.pools {
|
|
|
|
n += pool.Cap()
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tune resizes each pool in multi-pool.
|
|
|
|
//
|
|
|
|
// Note that this method doesn't resize the overall
|
|
|
|
// capacity of multi-pool.
|
|
|
|
func (mp *MultiPool) Tune(size int) {
|
|
|
|
for _, pool := range mp.pools {
|
|
|
|
pool.Tune(size)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsClosed indicates whether the multi-pool is closed.
|
|
|
|
func (mp *MultiPool) IsClosed() bool {
|
|
|
|
return atomic.LoadInt32(&mp.state) == CLOSED
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReleaseTimeout closes the multi-pool with a timeout,
|
|
|
|
// it waits all pools to be closed before timing out.
|
|
|
|
func (mp *MultiPool) ReleaseTimeout(timeout time.Duration) error {
|
|
|
|
if !atomic.CompareAndSwapInt32(&mp.state, OPENED, CLOSED) {
|
|
|
|
return ErrPoolClosed
|
|
|
|
}
|
|
|
|
|
2024-06-17 21:42:55 +03:00
|
|
|
errCh := make(chan error, len(mp.pools))
|
|
|
|
var wg errgroup.Group
|
2023-11-21 06:53:46 +03:00
|
|
|
for i, pool := range mp.pools {
|
2024-06-17 21:42:55 +03:00
|
|
|
func(p *Pool, idx int) {
|
|
|
|
wg.Go(func() error {
|
|
|
|
err := p.ReleaseTimeout(timeout)
|
|
|
|
if err != nil {
|
|
|
|
err = fmt.Errorf("pool %d: %v", idx, err)
|
|
|
|
}
|
|
|
|
errCh <- err
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
}(pool, i)
|
|
|
|
}
|
|
|
|
|
|
|
|
_ = wg.Wait()
|
|
|
|
|
|
|
|
var (
|
|
|
|
i int
|
|
|
|
errStr strings.Builder
|
|
|
|
)
|
|
|
|
for err := range errCh {
|
|
|
|
i++
|
|
|
|
if i == len(mp.pools) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
errStr.WriteString(err.Error())
|
|
|
|
errStr.WriteString(" | ")
|
2023-11-21 06:53:46 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if errStr.Len() == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-06-17 21:42:55 +03:00
|
|
|
return errors.New(strings.TrimSuffix(errStr.String(), " | "))
|
2023-11-21 06:53:46 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Reboot reboots a released multi-pool.
|
|
|
|
func (mp *MultiPool) Reboot() {
|
|
|
|
if atomic.CompareAndSwapInt32(&mp.state, CLOSED, OPENED) {
|
|
|
|
atomic.StoreUint32(&mp.index, 0)
|
|
|
|
for _, pool := range mp.pools {
|
|
|
|
pool.Reboot()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|