github.com/opentelekomcloud/gophertelekomcloud@v0.9.3/openstack/obs/pool.go (about)

     1  package obs
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"runtime"
     7  	"sync"
     8  	"sync/atomic"
     9  	"time"
    10  )
    11  
    12  // Future defines interface with function: Get
    13  type Future interface {
    14  	Get() interface{}
    15  }
    16  
    17  // FutureResult for task result
    18  type FutureResult struct {
    19  	result     interface{}
    20  	resultChan chan interface{}
    21  	lock       sync.Mutex
    22  }
    23  
    24  type panicResult struct {
    25  	presult interface{}
    26  }
    27  
    28  func (f *FutureResult) checkPanic() interface{} {
    29  	if r, ok := f.result.(panicResult); ok {
    30  		panic(r.presult)
    31  	}
    32  	return f.result
    33  }
    34  
    35  // Get gets the task result
    36  func (f *FutureResult) Get() interface{} {
    37  	if f.resultChan == nil {
    38  		return f.checkPanic()
    39  	}
    40  	f.lock.Lock()
    41  	defer f.lock.Unlock()
    42  	if f.resultChan == nil {
    43  		return f.checkPanic()
    44  	}
    45  
    46  	f.result = <-f.resultChan
    47  	close(f.resultChan)
    48  	f.resultChan = nil
    49  	return f.checkPanic()
    50  }
    51  
    52  // Task defines interface with function: Run
    53  type Task interface {
    54  	Run() interface{}
    55  }
    56  
    57  type funcWrapper struct {
    58  	f func() interface{}
    59  }
    60  
    61  func (fw *funcWrapper) Run() interface{} {
    62  	if fw.f != nil {
    63  		return fw.f()
    64  	}
    65  	return nil
    66  }
    67  
    68  type taskWrapper struct {
    69  	t Task
    70  	f *FutureResult
    71  }
    72  
    73  func (tw *taskWrapper) Run() interface{} {
    74  	if tw.t != nil {
    75  		return tw.t.Run()
    76  	}
    77  	return nil
    78  }
    79  
    80  type signalTask struct {
    81  	id string
    82  }
    83  
    84  func (signalTask) Run() interface{} {
    85  	return nil
    86  }
    87  
    88  type worker struct {
    89  	name      string
    90  	taskQueue chan Task
    91  	wg        *sync.WaitGroup
    92  	pool      *RoutinePool
    93  }
    94  
    95  func runTask(t Task) {
    96  	if tw, ok := t.(*taskWrapper); ok {
    97  		defer func() {
    98  			if r := recover(); r != nil {
    99  				tw.f.resultChan <- panicResult{
   100  					presult: r,
   101  				}
   102  			}
   103  		}()
   104  		ret := t.Run()
   105  		tw.f.resultChan <- ret
   106  	} else {
   107  		t.Run()
   108  	}
   109  }
   110  
   111  func (*worker) runTask(t Task) {
   112  	runTask(t)
   113  }
   114  
   115  func (w *worker) start() {
   116  	go func() {
   117  		defer func() {
   118  			if w.wg != nil {
   119  				w.wg.Done()
   120  			}
   121  		}()
   122  		for {
   123  			task, ok := <-w.taskQueue
   124  			if !ok {
   125  				break
   126  			}
   127  			w.pool.AddCurrentWorkingCnt(1)
   128  			w.runTask(task)
   129  			w.pool.AddCurrentWorkingCnt(-1)
   130  			if w.pool.autoTuneWorker(w) {
   131  				break
   132  			}
   133  		}
   134  	}()
   135  }
   136  
   137  func (w *worker) release() {
   138  	w.taskQueue = nil
   139  	w.wg = nil
   140  	w.pool = nil
   141  }
   142  
   143  // Pool defines coroutine pool interface
   144  type Pool interface {
   145  	ShutDown()
   146  	Submit(t Task) (Future, error)
   147  	SubmitFunc(f func() interface{}) (Future, error)
   148  	Execute(t Task)
   149  	ExecuteFunc(f func() interface{})
   150  	GetMaxWorkerCnt() int64
   151  	AddMaxWorkerCnt(value int64) int64
   152  	GetCurrentWorkingCnt() int64
   153  	AddCurrentWorkingCnt(value int64) int64
   154  	GetWorkerCnt() int64
   155  	AddWorkerCnt(value int64) int64
   156  	EnableAutoTune()
   157  }
   158  
   159  type basicPool struct {
   160  	maxWorkerCnt      int64
   161  	workerCnt         int64
   162  	currentWorkingCnt int64
   163  	isShutDown        int32 // nolint: structcheck
   164  }
   165  
   166  // ErrTaskInvalid will be returned if the task is nil
   167  var ErrTaskInvalid = errors.New("Task is nil")
   168  
   169  func (pool *basicPool) GetCurrentWorkingCnt() int64 {
   170  	return atomic.LoadInt64(&pool.currentWorkingCnt)
   171  }
   172  
   173  func (pool *basicPool) AddCurrentWorkingCnt(value int64) int64 {
   174  	return atomic.AddInt64(&pool.currentWorkingCnt, value)
   175  }
   176  
   177  func (pool *basicPool) GetWorkerCnt() int64 {
   178  	return atomic.LoadInt64(&pool.workerCnt)
   179  }
   180  
   181  func (pool *basicPool) AddWorkerCnt(value int64) int64 {
   182  	return atomic.AddInt64(&pool.workerCnt, value)
   183  }
   184  
   185  func (pool *basicPool) GetMaxWorkerCnt() int64 {
   186  	return atomic.LoadInt64(&pool.maxWorkerCnt)
   187  }
   188  
   189  func (pool *basicPool) AddMaxWorkerCnt(value int64) int64 {
   190  	return atomic.AddInt64(&pool.maxWorkerCnt, value)
   191  }
   192  
   193  func (pool *basicPool) CompareAndSwapCurrentWorkingCnt(oldValue, newValue int64) bool {
   194  	return atomic.CompareAndSwapInt64(&pool.currentWorkingCnt, oldValue, newValue)
   195  }
   196  
   197  func (pool *basicPool) EnableAutoTune() {
   198  
   199  }
   200  
   201  // RoutinePool defines the coroutine pool struct
   202  type RoutinePool struct {
   203  	basicPool
   204  	taskQueue     chan Task
   205  	dispatchQueue chan Task
   206  	workers       map[string]*worker
   207  	cacheCnt      int
   208  	wg            *sync.WaitGroup
   209  	lock          *sync.Mutex
   210  	shutDownWg    *sync.WaitGroup
   211  	autoTune      int32
   212  }
   213  
   214  // ErrSubmitTimeout will be returned if submit task timeout when calling SubmitWithTimeout function
   215  var ErrSubmitTimeout = errors.New("Submit task timeout")
   216  
   217  // ErrPoolShutDown will be returned if RoutinePool is shutdown
   218  var ErrPoolShutDown = errors.New("RoutinePool is shutdown")
   219  
   220  // ErrTaskReject will be returned if submit task is rejected
   221  var ErrTaskReject = errors.New("Submit task is rejected")
   222  
   223  var closeQueue = signalTask{id: "closeQueue"}
   224  
   225  // NewRoutinePool creates a RoutinePool instance
   226  func NewRoutinePool(maxWorkerCnt, cacheCnt int) Pool {
   227  	if maxWorkerCnt <= 0 {
   228  		maxWorkerCnt = runtime.NumCPU()
   229  	}
   230  
   231  	pool := &RoutinePool{
   232  		cacheCnt:   cacheCnt,
   233  		wg:         new(sync.WaitGroup),
   234  		lock:       new(sync.Mutex),
   235  		shutDownWg: new(sync.WaitGroup),
   236  		autoTune:   0,
   237  	}
   238  	pool.isShutDown = 0
   239  	pool.maxWorkerCnt += int64(maxWorkerCnt)
   240  	if pool.cacheCnt <= 0 {
   241  		pool.taskQueue = make(chan Task)
   242  	} else {
   243  		pool.taskQueue = make(chan Task, pool.cacheCnt)
   244  	}
   245  	pool.workers = make(map[string]*worker, pool.maxWorkerCnt)
   246  	// dispatchQueue must not have length
   247  	pool.dispatchQueue = make(chan Task)
   248  	pool.dispatcher()
   249  
   250  	return pool
   251  }
   252  
   253  // EnableAutoTune sets the autoTune enabled
   254  func (pool *RoutinePool) EnableAutoTune() {
   255  	atomic.StoreInt32(&pool.autoTune, 1)
   256  }
   257  
   258  func (pool *RoutinePool) checkStatus(t Task) error {
   259  	if t == nil {
   260  		return ErrTaskInvalid
   261  	}
   262  
   263  	if atomic.LoadInt32(&pool.isShutDown) == 1 {
   264  		return ErrPoolShutDown
   265  	}
   266  	return nil
   267  }
   268  
   269  func (pool *RoutinePool) dispatcher() {
   270  	pool.shutDownWg.Add(1)
   271  	go func() {
   272  		for {
   273  			task, ok := <-pool.dispatchQueue
   274  			if !ok {
   275  				break
   276  			}
   277  
   278  			if task == closeQueue {
   279  				close(pool.taskQueue)
   280  				pool.shutDownWg.Done()
   281  				continue
   282  			}
   283  
   284  			if pool.GetWorkerCnt() < pool.GetMaxWorkerCnt() {
   285  				pool.addWorker()
   286  			}
   287  
   288  			pool.taskQueue <- task
   289  		}
   290  	}()
   291  }
   292  
   293  // AddMaxWorkerCnt sets the maxWorkerCnt field's value and returns it
   294  func (pool *RoutinePool) AddMaxWorkerCnt(value int64) int64 {
   295  	if atomic.LoadInt32(&pool.autoTune) == 1 {
   296  		return pool.basicPool.AddMaxWorkerCnt(value)
   297  	}
   298  	return pool.GetMaxWorkerCnt()
   299  }
   300  
   301  func (pool *RoutinePool) addWorker() {
   302  	if atomic.LoadInt32(&pool.autoTune) == 1 {
   303  		pool.lock.Lock()
   304  		defer pool.lock.Unlock()
   305  	}
   306  	w := &worker{}
   307  	w.name = fmt.Sprintf("woker-%d", len(pool.workers))
   308  	w.taskQueue = pool.taskQueue
   309  	w.wg = pool.wg
   310  	pool.AddWorkerCnt(1)
   311  	w.pool = pool
   312  	pool.workers[w.name] = w
   313  	pool.wg.Add(1)
   314  	w.start()
   315  }
   316  
   317  func (pool *RoutinePool) autoTuneWorker(w *worker) bool {
   318  	if atomic.LoadInt32(&pool.autoTune) == 0 {
   319  		return false
   320  	}
   321  
   322  	if w == nil {
   323  		return false
   324  	}
   325  
   326  	workerCnt := pool.GetWorkerCnt()
   327  	maxWorkerCnt := pool.GetMaxWorkerCnt()
   328  	if workerCnt > maxWorkerCnt && atomic.CompareAndSwapInt64(&pool.workerCnt, workerCnt, workerCnt-1) {
   329  		pool.lock.Lock()
   330  		defer pool.lock.Unlock()
   331  		delete(pool.workers, w.name)
   332  		w.wg.Done()
   333  		w.release()
   334  		return true
   335  	}
   336  
   337  	return false
   338  }
   339  
   340  // ExecuteFunc creates a funcWrapper instance with the specified function and calls the Execute function
   341  func (pool *RoutinePool) ExecuteFunc(f func() interface{}) {
   342  	fw := &funcWrapper{
   343  		f: f,
   344  	}
   345  	pool.Execute(fw)
   346  }
   347  
   348  // Execute pushes the specified task to the dispatchQueue
   349  func (pool *RoutinePool) Execute(t Task) {
   350  	if t != nil {
   351  		pool.dispatchQueue <- t
   352  	}
   353  }
   354  
   355  // SubmitFunc creates a funcWrapper instance with the specified function and calls the Submit function
   356  func (pool *RoutinePool) SubmitFunc(f func() interface{}) (Future, error) {
   357  	fw := &funcWrapper{
   358  		f: f,
   359  	}
   360  	return pool.Submit(fw)
   361  }
   362  
   363  // Submit pushes the specified task to the dispatchQueue, and returns the FutureResult and error info
   364  func (pool *RoutinePool) Submit(t Task) (Future, error) {
   365  	if err := pool.checkStatus(t); err != nil {
   366  		return nil, err
   367  	}
   368  	f := &FutureResult{}
   369  	f.resultChan = make(chan interface{}, 1)
   370  	tw := &taskWrapper{
   371  		t: t,
   372  		f: f,
   373  	}
   374  	pool.dispatchQueue <- tw
   375  	return f, nil
   376  }
   377  
   378  // SubmitWithTimeout pushes the specified task to the dispatchQueue, and returns the FutureResult and error info.
   379  // Also takes a timeout value, will return ErrSubmitTimeout if it does't complete within that time.
   380  func (pool *RoutinePool) SubmitWithTimeout(t Task, timeout int64) (Future, error) {
   381  	if timeout <= 0 {
   382  		return pool.Submit(t)
   383  	}
   384  	if err := pool.checkStatus(t); err != nil {
   385  		return nil, err
   386  	}
   387  	timeoutChan := make(chan bool, 1)
   388  	go func() {
   389  		time.Sleep(time.Duration(time.Millisecond * time.Duration(timeout)))
   390  		timeoutChan <- true
   391  		close(timeoutChan)
   392  	}()
   393  
   394  	f := &FutureResult{}
   395  	f.resultChan = make(chan interface{}, 1)
   396  	tw := &taskWrapper{
   397  		t: t,
   398  		f: f,
   399  	}
   400  	select {
   401  	case pool.dispatchQueue <- tw:
   402  		return f, nil
   403  	case _, ok := <-timeoutChan:
   404  		if ok {
   405  			return nil, ErrSubmitTimeout
   406  		}
   407  		return nil, ErrSubmitTimeout
   408  	}
   409  }
   410  
   411  func (pool *RoutinePool) beforeCloseDispatchQueue() {
   412  	if !atomic.CompareAndSwapInt32(&pool.isShutDown, 0, 1) {
   413  		return
   414  	}
   415  	pool.dispatchQueue <- closeQueue
   416  	pool.wg.Wait()
   417  }
   418  
   419  func (pool *RoutinePool) doCloseDispatchQueue() {
   420  	close(pool.dispatchQueue)
   421  	pool.shutDownWg.Wait()
   422  }
   423  
   424  // ShutDown closes the RoutinePool instance
   425  func (pool *RoutinePool) ShutDown() {
   426  	pool.beforeCloseDispatchQueue()
   427  	pool.doCloseDispatchQueue()
   428  	for _, w := range pool.workers {
   429  		w.release()
   430  	}
   431  	pool.workers = nil
   432  	pool.taskQueue = nil
   433  	pool.dispatchQueue = nil
   434  }
   435  
   436  // NoChanPool defines the coroutine pool struct
   437  type NoChanPool struct {
   438  	basicPool
   439  	wg     *sync.WaitGroup
   440  	tokens chan interface{}
   441  }
   442  
   443  // NewNochanPool creates a new NoChanPool instance
   444  func NewNochanPool(maxWorkerCnt int) Pool {
   445  	if maxWorkerCnt <= 0 {
   446  		maxWorkerCnt = runtime.NumCPU()
   447  	}
   448  
   449  	pool := &NoChanPool{
   450  		wg:     new(sync.WaitGroup),
   451  		tokens: make(chan interface{}, maxWorkerCnt),
   452  	}
   453  	pool.isShutDown = 0
   454  	pool.AddMaxWorkerCnt(int64(maxWorkerCnt))
   455  
   456  	for i := 0; i < maxWorkerCnt; i++ {
   457  		pool.tokens <- struct{}{}
   458  	}
   459  
   460  	return pool
   461  }
   462  
   463  func (pool *NoChanPool) acquire() {
   464  	<-pool.tokens
   465  }
   466  
   467  func (pool *NoChanPool) release() {
   468  	pool.tokens <- 1
   469  }
   470  
   471  func (pool *NoChanPool) execute(t Task) {
   472  	pool.wg.Add(1)
   473  	go func() {
   474  		pool.acquire()
   475  		defer func() {
   476  			pool.release()
   477  			pool.wg.Done()
   478  		}()
   479  		runTask(t)
   480  	}()
   481  }
   482  
   483  // ShutDown closes the NoChanPool instance
   484  func (pool *NoChanPool) ShutDown() {
   485  	if !atomic.CompareAndSwapInt32(&pool.isShutDown, 0, 1) {
   486  		return
   487  	}
   488  	pool.wg.Wait()
   489  }
   490  
   491  // Execute executes the specified task
   492  func (pool *NoChanPool) Execute(t Task) {
   493  	if t != nil {
   494  		pool.execute(t)
   495  	}
   496  }
   497  
   498  // ExecuteFunc creates a funcWrapper instance with the specified function and calls the Execute function
   499  func (pool *NoChanPool) ExecuteFunc(f func() interface{}) {
   500  	fw := &funcWrapper{
   501  		f: f,
   502  	}
   503  	pool.Execute(fw)
   504  }
   505  
   506  // Submit executes the specified task, and returns the FutureResult and error info
   507  func (pool *NoChanPool) Submit(t Task) (Future, error) {
   508  	if t == nil {
   509  		return nil, ErrTaskInvalid
   510  	}
   511  
   512  	f := &FutureResult{}
   513  	f.resultChan = make(chan interface{}, 1)
   514  	tw := &taskWrapper{
   515  		t: t,
   516  		f: f,
   517  	}
   518  
   519  	pool.execute(tw)
   520  	return f, nil
   521  }
   522  
   523  // SubmitFunc creates a funcWrapper instance with the specified function and calls the Submit function
   524  func (pool *NoChanPool) SubmitFunc(f func() interface{}) (Future, error) {
   525  	fw := &funcWrapper{
   526  		f: f,
   527  	}
   528  	return pool.Submit(fw)
   529  }