v8.run/go/exp@v0.0.26-0.20230226010534-afcdbd3f782d/pool2/gopool1/gopool1.go (about)

     1  package gopool1
     2  
     3  import (
     4  	"errors"
     5  	"sync"
     6  	"time"
     7  )
     8  
     9  type timedworker[T any] struct {
    10  	ch chan T
    11  	tt int64
    12  }
    13  
    14  type GoPool1[T any] struct {
    15  	maxWorkers  int64
    16  	handler     func(T)
    17  	stop        chan struct{}
    18  	idleTimeout time.Duration
    19  	gcPeriod    time.Duration
    20  	_           [10]uint64
    21  
    22  	mu      sync.Mutex
    23  	workers int64
    24  	pool    sync.Pool
    25  	dobby   []*timedworker[T]
    26  }
    27  
    28  func tworker[T any](pool *GoPool1[T], w *timedworker[T]) {
    29  	for v := range w.ch {
    30  		pool.handler(v)
    31  		if pool.free(w) {
    32  			return
    33  		}
    34  	}
    35  
    36  	pool.mu.Lock()
    37  	pool.workers--
    38  	pool.mu.Unlock()
    39  }
    40  
    41  func (pool *GoPool1[T]) start(preheat int) {
    42  	pool.pool.New = func() interface{} {
    43  		return &timedworker[T]{
    44  			ch: make(chan T, 1),
    45  		}
    46  	}
    47  	pool.stop = make(chan struct{})
    48  
    49  	if preheat > 0 {
    50  		pool.workers = int64(preheat)
    51  		for i := 0; i < preheat; i++ {
    52  			w := pool.pool.Get().(*timedworker[T])
    53  			w.tt = time.Now().UnixNano()
    54  			pool.dobby = append(pool.dobby, w)
    55  			go tworker(pool, w)
    56  		}
    57  	}
    58  
    59  	go func() {
    60  		ticker := time.NewTicker(pool.gcPeriod)
    61  		defer ticker.Stop()
    62  
    63  		var wbuffer []*timedworker[T]
    64  
    65  		for {
    66  			select {
    67  			case <-ticker.C:
    68  				pool.mu.Lock()
    69  				if pool.maxWorkers == -1 {
    70  					// Stop All Workers
    71  					for _, w := range pool.dobby {
    72  						close(w.ch)
    73  					}
    74  					pool.mu.Unlock()
    75  					return
    76  				}
    77  
    78  				// Stop Idle Workers
    79  				now := time.Now().Add(-pool.idleTimeout).UnixNano()
    80  				dc := len(pool.dobby)
    81  				if dc > 0 {
    82  					l, r := 0, dc
    83  					m := (l + r) >> 1
    84  					for l < r {
    85  						if pool.dobby[m].tt < now {
    86  							l = m + 1
    87  						} else {
    88  							r = m
    89  						}
    90  						m = (l + r) >> 1
    91  					}
    92  
    93  					wbuffer = append(wbuffer, pool.dobby[:l]...)
    94  					copy(pool.dobby, pool.dobby[l:])
    95  					pool.dobby = pool.dobby[:dc-l]
    96  				}
    97  				pool.mu.Unlock()
    98  
    99  				// Stop Old Workers
   100  				for i := range wbuffer {
   101  					close(wbuffer[i].ch)
   102  					wbuffer[i].tt = 0
   103  					wbuffer[i].ch = nil
   104  					pool.pool.Put(wbuffer[i])
   105  					wbuffer[i] = nil
   106  				}
   107  				wbuffer = wbuffer[:0]
   108  
   109  			case <-pool.stop:
   110  				pool.mu.Lock()
   111  				pool.maxWorkers = -1
   112  				for i := range pool.dobby {
   113  					close(pool.dobby[i].ch)
   114  					// Drop All Workers
   115  					// pool.dobby[i].tt = 0
   116  					// pool.dobby[i].ch = nil
   117  					// pool.pool.Put(pool.dobby[i])
   118  					// pool.dobby[i] = nil
   119  				}
   120  				pool.mu.Unlock()
   121  				return
   122  			}
   123  		}
   124  	}()
   125  }
   126  
   127  func (pool *GoPool1[T]) free(w *timedworker[T]) (stop bool) {
   128  	w.tt = time.Now().UnixNano()
   129  	pool.mu.Lock()
   130  	if pool.maxWorkers == -1 {
   131  		pool.workers--
   132  		pool.mu.Unlock()
   133  		return true
   134  	}
   135  	pool.dobby = append(pool.dobby, w)
   136  	pool.mu.Unlock()
   137  	return false
   138  }
   139  
   140  func (pool *GoPool1[T]) Run(v T) (ok bool) {
   141  	var w *timedworker[T]
   142  	pool.mu.Lock()
   143  	// Check If Pool Is Draining
   144  	if pool.maxWorkers == -1 {
   145  		pool.mu.Unlock()
   146  		return false
   147  	}
   148  
   149  	// Check For Dobby
   150  	if len(pool.dobby) > 0 {
   151  		w = pool.dobby[len(pool.dobby)-1]
   152  		pool.dobby = pool.dobby[:len(pool.dobby)-1]
   153  	} else {
   154  		// No Dobby, Create Worker
   155  		if pool.workers < pool.maxWorkers {
   156  			pool.workers++
   157  			w = pool.pool.Get().(*timedworker[T])
   158  			w.ch = make(chan T, 1)
   159  			go tworker(pool, w)
   160  		} else {
   161  			pool.mu.Unlock()
   162  			return false
   163  		}
   164  	}
   165  	pool.mu.Unlock()
   166  
   167  	w.ch <- v
   168  	return true
   169  }
   170  
   171  var (
   172  	ErrInvalidMaxWorkers  = errors.New("invalid max workers, must be greater than 0")
   173  	ErrInvalidHandler     = errors.New("invalid handler, must not be nil")
   174  	ErrInvalidIdleTimeout = errors.New("invalid idle timeout, must be greater than 0s")
   175  	ErrInvalidGCPeriod    = errors.New("invalid gc period, must be greater than 0s")
   176  )
   177  
   178  func New[T any](maxWorkers int64, handler func(T), idleTimeout, gcPeriod time.Duration, preheat int) (*GoPool1[T], error) {
   179  	if maxWorkers <= 0 {
   180  		return nil, ErrInvalidMaxWorkers
   181  	}
   182  	if handler == nil {
   183  		return nil, ErrInvalidHandler
   184  	}
   185  	if idleTimeout <= 0 {
   186  		return nil, ErrInvalidIdleTimeout
   187  	}
   188  	if gcPeriod <= 0 {
   189  		return nil, ErrInvalidGCPeriod
   190  	}
   191  
   192  	pool := &GoPool1[T]{
   193  		maxWorkers:  maxWorkers,
   194  		handler:     handler,
   195  		idleTimeout: idleTimeout,
   196  		gcPeriod:    gcPeriod,
   197  	}
   198  
   199  	pool.start(preheat)
   200  	return pool, nil
   201  }
   202  
   203  func (pool *GoPool1[T]) Stop() {
   204  	if pool.stop == nil {
   205  		return
   206  	}
   207  	pool.stop <- struct{}{}
   208  	pool.stop = nil
   209  }
   210  
   211  func (pool *GoPool1[T]) Workers() int64 {
   212  	pool.mu.Lock()
   213  	w := pool.workers
   214  	pool.mu.Unlock()
   215  	return w
   216  }