github.com/mailru/activerecord@v1.12.2/pkg/iproto/util/pool/pool.go (about)

     1  package pool
     2  
     3  import (
     4  	"container/list"
     5  	"errors"
     6  	"runtime"
     7  	"sync"
     8  	"time"
     9  
    10  	ptime "github.com/mailru/activerecord/pkg/iproto/util/time"
    11  	"golang.org/x/net/context"
    12  )
    13  
    14  const (
    15  	DefaultExtraWorkerTTL = time.Second
    16  )
    17  
    18  var (
    19  	ErrConfigMalformed        = errors.New("malformed pool config: max number of workers is lower than number of unstoppable workers")
    20  	ErrConfigQueueUnstoppable = errors.New("malformed pool config: queue is buffered, but unstoppable workers set to 0")
    21  	ErrUnavailable            = errors.New("pool: temporarily unavailable")
    22  	ErrPoolClosed             = errors.New("pool: closed")
    23  )
    24  
    25  // Task represents task that need to be executed by some worker.
    26  type Task interface {
    27  	Run()
    28  }
    29  
    30  type TaskFunc func()
    31  
    32  func (fn TaskFunc) Run() { fn() }
    33  
    34  type Config struct {
    35  	// Number of workers that are always running.
    36  	UnstoppableWorkers int
    37  
    38  	// Maximum number of workers that could be spawned.
    39  	// It includes UnstoppableWorkers count.
    40  	// If MaxWorkers is <= 0 then it interpreted as extra workers are unlimited.
    41  	// If MaxWorkers is in range [1, UnstoppableWorkers) then config is malformed.
    42  	MaxWorkers int
    43  
    44  	// When work flow becomes too much for unstoppable workers,
    45  	// Pool could spawn extra worker if it fits max workers limit.
    46  	// Spawned worker will be alive for this amount of time.
    47  	ExtraWorkerTTL time.Duration
    48  
    49  	// This parameter manages the size of work queue.
    50  	// The smaller this value, the greater the probability of
    51  	// spawning the extra worker. And vice versa.
    52  	WorkQueueSize int
    53  
    54  	// QueueTiming is used to calculate duration from task receiving
    55  	// to begining of task execution.
    56  	// QueueTiming stat.SimpleTiming
    57  
    58  	// ExecTiming is used to calculate duration of task execution.
    59  	// ExecTiming stat.SimpleTiming
    60  
    61  	// IdleTiming is used to calculate idle time of each worker.
    62  	// IdleTiming stat.SimpleTiming
    63  
    64  	// OnTaskIn, OnTaskOut are used to signal whan task is scheduled/pulled from queue.
    65  	OnTaskIn, OnTaskOut func()
    66  
    67  	// OnWorkerStart, OnWorkerStop are called on extra worker spawned/stopped.
    68  	OnWorkerStart, OnWorkerStop func()
    69  }
    70  
    71  func dummyStatPush() {}
    72  
    73  func (c *Config) withDefaults() (config Config) {
    74  	if c != nil {
    75  		config = *c
    76  	}
    77  
    78  	if config.ExtraWorkerTTL == 0 {
    79  		config.ExtraWorkerTTL = DefaultExtraWorkerTTL
    80  	}
    81  
    82  	// if config.QueueTiming == nil {
    83  	// 	config.QueueTiming = stat.DummyTiming
    84  	// }
    85  	// if config.ExecTiming == nil {
    86  	// 	config.ExecTiming = stat.DummyTiming
    87  	// }
    88  	// if config.IdleTiming == nil {
    89  	// 	config.IdleTiming = stat.DummyTiming
    90  	// }
    91  
    92  	if config.OnTaskIn == nil {
    93  		config.OnTaskIn = dummyStatPush
    94  	}
    95  
    96  	if config.OnTaskOut == nil {
    97  		config.OnTaskOut = dummyStatPush
    98  	}
    99  
   100  	if config.OnWorkerStart == nil {
   101  		config.OnWorkerStart = dummyStatPush
   102  	}
   103  
   104  	if config.OnWorkerStop == nil {
   105  		config.OnWorkerStop = dummyStatPush
   106  	}
   107  
   108  	return
   109  }
   110  
   111  func (c Config) check() error {
   112  	if c.MaxWorkers > 0 && c.MaxWorkers < c.UnstoppableWorkers {
   113  		return ErrConfigMalformed
   114  	}
   115  
   116  	// If work queue set to be buffered, but unstoppable workers are set to 0, then
   117  	// pool will not process tasks until work queue will be full.
   118  	if c.UnstoppableWorkers == 0 && c.WorkQueueSize > 0 {
   119  		return ErrConfigQueueUnstoppable
   120  	}
   121  
   122  	return nil
   123  }
   124  
   125  type Pool struct {
   126  	config Config
   127  
   128  	work   chan Task
   129  	sem    chan struct{}
   130  	kill   chan struct{}
   131  	done   chan struct{}
   132  	wg     sync.WaitGroup
   133  	noCork bool
   134  
   135  	mu   sync.RWMutex
   136  	list *list.List
   137  }
   138  
   139  func New(c *Config) (*Pool, error) {
   140  	config := c.withDefaults()
   141  
   142  	if err := config.check(); err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	p := &Pool{
   147  		config: config,
   148  		work:   make(chan Task, config.WorkQueueSize),
   149  		kill:   make(chan struct{}),
   150  		done:   make(chan struct{}),
   151  		list:   list.New(),
   152  	}
   153  
   154  	if config.MaxWorkers > 0 {
   155  		p.sem = make(chan struct{}, config.MaxWorkers)
   156  	}
   157  
   158  	for i := 0; i < config.UnstoppableWorkers; i++ {
   159  		if p.sem != nil {
   160  			p.sem <- struct{}{}
   161  		}
   162  
   163  		_ = p.spawn(nil, false)
   164  	}
   165  
   166  	return p, nil
   167  }
   168  
   169  // Must is a helper that wraps a call to a function returning (*Pool, error)
   170  // and panics if the error is non-nil.
   171  // It is intended for use in variable initializations with New() function.
   172  func Must(p *Pool, err error) *Pool {
   173  	if err != nil {
   174  		panic(err)
   175  	}
   176  
   177  	return p
   178  }
   179  
   180  func (p *Pool) SetNoCork(v bool) {
   181  	p.noCork = v
   182  }
   183  
   184  // Close terminates all spawned goroutines.
   185  func (p *Pool) Close() error {
   186  	p.mu.Lock()
   187  	select {
   188  	case <-p.kill:
   189  		// Close() was already called.
   190  		// Wait for done to be closed.
   191  		p.mu.Unlock()
   192  		<-p.done
   193  
   194  	default:
   195  		// Close the kill channel under the mutex to accomplish two things.
   196  		// First is to get rid from concurrent wg.Add() calls while waiting for
   197  		// wg.Done() below.
   198  		// Second is to avoid panics on concurrent Close() calls.
   199  		close(p.kill)
   200  		p.mu.Unlock()
   201  
   202  		// Wait for all workers exit.
   203  		p.wg.Wait()
   204  
   205  		// No more alive workers at this point. So we can safely fulfill work
   206  		// queue to avoid concurrent Schedule*() tasks get stucked in the work
   207  		// queue without progress.
   208  		if !p.noCork {
   209  			cork(p.work)
   210  		}
   211  
   212  		// Signal that pool is completely closed.
   213  		close(p.done)
   214  	}
   215  
   216  	return nil
   217  }
   218  
   219  // Done returns channel which closure means that pool is done with all its work.
   220  func (p *Pool) Done() <-chan struct{} {
   221  	return p.done
   222  }
   223  
   224  // Schedule makes task to be scheduled over pool's workers.
   225  // It returns non-nil only if pool become closed and task can not be executed
   226  // over its workers.
   227  func (p *Pool) Schedule(t Task) error {
   228  	return p.schedule(t, nil, nil)
   229  }
   230  
   231  // immediate indicates that task scheduling must complete or fail right now.
   232  var immediate = make(chan time.Time)
   233  
   234  // ScheduleImmediate makes task to be scheduled without waiting for free
   235  // workers. That is, if all workers are busy and pool is not rubber, then
   236  // ErrUnavailable is returned immediately.
   237  func (p *Pool) ScheduleImmediate(t Task) error {
   238  	return p.schedule(t, immediate, nil)
   239  }
   240  
   241  // ScheduleTimeout makes task to be scheduled over pool's workers.
   242  //
   243  // Non-nil error only available when tm is not 0. That is, if no workers are
   244  // available during timeout, it returns error.
   245  //
   246  // Zero timeout means that there are no timeout for awaiting for available
   247  // worker.
   248  func (p *Pool) ScheduleTimeout(tm time.Duration, t Task) error {
   249  	var timeout <-chan time.Time
   250  
   251  	if tm != 0 {
   252  		timer := ptime.AcquireTimer(tm)
   253  		defer ptime.ReleaseTimer(timer)
   254  		timeout = timer.C
   255  	}
   256  
   257  	return p.schedule(t, timeout, nil)
   258  }
   259  
   260  // ScheduleContext makes task to be scheduled over pool's workers.
   261  //
   262  // Non-nil error only available when ctx is done. That is, if no workers are
   263  // available during lifetime of context, it returns ctx.Err() call result.
   264  func (p *Pool) ScheduleContext(ctx context.Context, t Task) error {
   265  	if err := p.schedule(t, nil, ctx.Done()); err != nil {
   266  		return ctx.Err()
   267  	}
   268  
   269  	return nil
   270  }
   271  
   272  // ScheduleCustom makes task to be scheduled over pool's workers.
   273  //
   274  // Non-nil error only available when cancel closed. That is, if no workers are
   275  // available until closure of cancel chan, it returns error.
   276  //
   277  // This method useful for implementing custom cancellation logic by a caller.
   278  func (p *Pool) ScheduleCustom(cancel <-chan struct{}, t Task) error {
   279  	return p.schedule(t, nil, cancel)
   280  }
   281  
   282  // Barrier blocks until workers complete their current task.
   283  func (p *Pool) Barrier() {
   284  	_ = p.barrier(nil, nil)
   285  }
   286  
   287  // BarrierTimeout blocks until workers complete their current task or until
   288  // timeout is expired.
   289  func (p *Pool) BarrierTimeout(tm time.Duration) error {
   290  	var timeout <-chan time.Time
   291  
   292  	if tm != 0 {
   293  		timer := ptime.AcquireTimer(tm)
   294  		defer ptime.ReleaseTimer(timer)
   295  		timeout = timer.C
   296  	}
   297  
   298  	return p.barrier(timeout, nil)
   299  }
   300  
   301  // BarrierContext blocks until workers complete their current task or until
   302  // context is done. In case when err is non-nil err is always a result of
   303  // ctx.Err() call.
   304  func (p *Pool) BarrierContext(ctx context.Context) error {
   305  	if err := p.barrier(nil, ctx.Done()); err != nil {
   306  		return ctx.Err()
   307  	}
   308  
   309  	return nil
   310  }
   311  
   312  // BarrierCustom blocks until workers complete their current task or until
   313  // given cancelation channel is non-empty.
   314  func (p *Pool) BarrierCustom(cancel <-chan struct{}) error {
   315  	return p.barrier(nil, cancel)
   316  }
   317  
   318  // Wait blocks until all previously scheduled tasks are executed.
   319  func (p *Pool) Wait() {
   320  	_ = p.wait(nil, nil)
   321  }
   322  
   323  // WaitTimeout blocks until all previously scheduled tasks are executed or until
   324  // timeout is expired.
   325  func (p *Pool) WaitTimeout(tm time.Duration) error {
   326  	var timeout <-chan time.Time
   327  
   328  	if tm != 0 {
   329  		timer := ptime.AcquireTimer(tm)
   330  		defer ptime.ReleaseTimer(timer)
   331  		timeout = timer.C
   332  	}
   333  
   334  	return p.wait(timeout, nil)
   335  }
   336  
   337  // WaitContext blocks until all previously scheduled tasks are executed or
   338  // until context is done. In case when err is non-nil err is always a result of
   339  // ctx.Err() call.
   340  func (p *Pool) WaitContext(ctx context.Context) error {
   341  	if err := p.wait(nil, ctx.Done()); err != nil {
   342  		return ctx.Err()
   343  	}
   344  
   345  	return nil
   346  }
   347  
   348  // WaitCustom blocks until all previously scheduled tasks are executed or until
   349  // given cancelation channel is non-empty.
   350  func (p *Pool) WaitCustom(cancel <-chan struct{}) error {
   351  	return p.wait(nil, cancel)
   352  }
   353  
   354  // workers returns current alive workers list.
   355  func (p *Pool) workers() []*worker {
   356  	p.mu.RLock()
   357  	ws := make([]*worker, 0, p.list.Len())
   358  
   359  	for el := p.list.Front(); el != nil; el = el.Next() {
   360  		ws = append(ws, el.Value.(*worker))
   361  	}
   362  
   363  	p.mu.RUnlock()
   364  
   365  	return ws
   366  }
   367  
   368  // barrier spreads a technical task over all running workers and blocks until
   369  // every worker execute it. It returns earlier with non-nil error if timeout or
   370  // done channels are non-empty.
   371  func (p *Pool) barrier(timeout <-chan time.Time, done <-chan struct{}) error {
   372  	snap := make(chan struct{}, 1)
   373  	n, err := p.multicast(TaskFunc(func() {
   374  		select {
   375  		case snap <- struct{}{}:
   376  		case <-timeout:
   377  		case <-done:
   378  		}
   379  	}), timeout, done)
   380  
   381  	if err != nil {
   382  		return err
   383  	}
   384  
   385  	for i := 0; i < n; i++ {
   386  		select {
   387  		case <-snap:
   388  		case <-timeout:
   389  			return ErrUnavailable
   390  		case <-done:
   391  			return ErrUnavailable
   392  		}
   393  	}
   394  
   395  	return nil
   396  }
   397  
   398  // multicast makes task to be executed on every running worker.
   399  // It returns number of workers received the task and an error that could arise
   400  // only when timeout or done channels are non-nil and non-empty.
   401  //
   402  // Note that if pool is closed it does not returns ErrPoolClosed error.
   403  //
   404  // If some worker can not receive incoming task until timeout or done chanel it
   405  // stops trying to send the task to remaining workers.
   406  func (p *Pool) multicast(task Task, timeout <-chan time.Time, done <-chan struct{}) (n int, err error) {
   407  	// NOTE: if for some reasons it is neccessary to check that pool is closed
   408  	// and return ErrPoolClosed, then you MUST edit the barrier() method such
   409  	// that it will check the ErrPoolClosed case and make p.Done() waiting or
   410  	// its timeout or done channels closure.
   411  	// You will also need to edit the worker run() method, which is also rely
   412  	// on this behaviour iniside kill case.
   413  	for _, w := range p.workers() {
   414  		callMaybe(p.config.OnTaskIn)
   415  		t := p.wrapTask(task)
   416  
   417  		switch {
   418  		case timeout == immediate:
   419  			select {
   420  			case w.direct <- t:
   421  				n++
   422  			case <-w.done:
   423  				// Worker killed.
   424  			default:
   425  				return n, ErrUnavailable
   426  			}
   427  		default:
   428  			select {
   429  			case w.direct <- t:
   430  				n++
   431  			case <-w.done:
   432  				// Worker killed.
   433  			case <-timeout:
   434  				return n, ErrUnavailable
   435  			case <-done:
   436  				return n, ErrUnavailable
   437  			}
   438  		}
   439  	}
   440  
   441  	return n, nil
   442  }
   443  
   444  func (p *Pool) schedule(task Task, timeout <-chan time.Time, done <-chan struct{}) (err error) {
   445  	callMaybe(p.config.OnTaskIn)
   446  	w := p.wrapTask(task)
   447  
   448  	// First, try to put task into the work queue in optimistic manner.
   449  	select {
   450  	case <-p.kill:
   451  		// Drop it after Close() call
   452  		callMaybe(p.config.OnTaskOut)
   453  		return ErrPoolClosed
   454  	case p.work <- w:
   455  		return nil
   456  	default:
   457  	}
   458  
   459  	switch {
   460  	case p.sem == nil:
   461  		// If pool is configured to be "rubber" we just spawn worker
   462  		// without limits.
   463  	case timeout == immediate:
   464  		// Immediate timeout means that caller dont want to wait for
   465  		// scheduling. In that case our only option here is to try to spawn
   466  		// goroutine in non-blocking mode.
   467  		select {
   468  		case p.sem <- struct{}{}:
   469  		default:
   470  			err = ErrUnavailable
   471  		}
   472  	default:
   473  		// If pool is not "rubber", try to enqueue task into the work queue or
   474  		// spawn new worker until the timeout or done channel are filled.
   475  		select {
   476  		case p.work <- w:
   477  			return nil
   478  		case p.sem <- struct{}{}:
   479  		case <-timeout:
   480  			err = ErrUnavailable
   481  		case <-done:
   482  			err = ErrUnavailable
   483  		case <-p.kill:
   484  			err = ErrPoolClosed
   485  		}
   486  	}
   487  
   488  	if err == nil {
   489  		// We get here only if p.sem is nil or write to p.sem succeed.
   490  		err = p.spawn(w, true)
   491  	}
   492  
   493  	if err != nil {
   494  		callMaybe(p.config.OnTaskOut)
   495  	}
   496  
   497  	return err
   498  }
   499  
   500  func (p *Pool) wait(timeout <-chan time.Time, done <-chan struct{}) error {
   501  	crossed := make(chan struct{})
   502  	// Execution of this task means that all previous tasks from the work queue
   503  	// were received by worker(s). After that, we only need to wait them to
   504  	// finish their current labor.
   505  	bound := TaskFunc(func() {
   506  		close(crossed)
   507  	})
   508  	// Put the task manually to the queue without using schedule() method. This
   509  	// is done to avoid side-effects of schedule() like spawning extra worker
   510  	// which will execute this task out of queue order.
   511  	select {
   512  	case p.work <- bound:
   513  		// OK.
   514  	case <-p.done:
   515  		// No need to do additional work – pool closed and workers are done.
   516  		return nil
   517  
   518  	case <-timeout:
   519  		return ErrUnavailable
   520  	case <-done:
   521  		return ErrUnavailable
   522  	}
   523  	select {
   524  	case <-crossed:
   525  		return p.barrier(timeout, done)
   526  
   527  	case <-timeout:
   528  		return ErrUnavailable
   529  	case <-done:
   530  		return ErrUnavailable
   531  	}
   532  }
   533  
   534  func (p *Pool) spawn(t Task, extra bool) (closed error) {
   535  	// Prepare worker before taking the mutex.
   536  	// That is we act here in optimistic manner that pool is not closed.
   537  	w := &worker{
   538  		direct: make(chan Task, 1),
   539  		done:   make(chan struct{}),
   540  		work:   p.work,
   541  		kill:   p.kill,
   542  		noCork: p.noCork,
   543  		// idleTiming: p.config.IdleTiming,
   544  	}
   545  	if extra {
   546  		w.ttl = p.config.ExtraWorkerTTL
   547  	}
   548  
   549  	// We must synchronize workers wait group to avoid panic "WaitGroup is
   550  	// reused before previous Wait has returned". That is, there could be race
   551  	// when we spawning worker while pool is closing; in that case inside
   552  	// Close(): wg.Done() is not yet returned but its counter could reach the
   553  	// zero due to all previous workers are done; here inside spawn(): we can
   554  	// call wg.Add(1), which will produce the panic inside wg.Wait().
   555  	p.mu.Lock()
   556  	{
   557  		select {
   558  		case <-p.kill:
   559  			closed = ErrPoolClosed
   560  		default:
   561  			p.wg.Add(1)
   562  			w.elem = p.list.PushBack(w)
   563  		}
   564  	}
   565  	p.mu.Unlock()
   566  
   567  	if closed != nil {
   568  		if p.sem != nil {
   569  			// We can release token from the workers semaphore not under the
   570  			// mutex.
   571  			<-p.sem
   572  		}
   573  
   574  		return closed
   575  	}
   576  
   577  	w.onStop = func() {
   578  		if p.sem != nil {
   579  			<-p.sem
   580  		}
   581  
   582  		p.wg.Done()
   583  		callMaybe(p.config.OnWorkerStop)
   584  
   585  		p.mu.Lock()
   586  		p.list.Remove(w.elem)
   587  		p.mu.Unlock()
   588  	}
   589  	w.onStart = func() {
   590  		callMaybe(p.config.OnWorkerStart)
   591  	}
   592  
   593  	go w.run(t)
   594  
   595  	return nil
   596  }
   597  
   598  func (p *Pool) wrapTask(task Task) *taskWrapper {
   599  	w := getTaskWrapper()
   600  
   601  	w.task = task
   602  
   603  	// w.queueTimer = p.config.QueueTiming.Timer()
   604  	// w.execTimer = p.config.ExecTiming.Timer()
   605  
   606  	// w.queueTimer.Start()
   607  	w.onDequeue = func() {
   608  		// w.queueTimer.Stop()
   609  		p.config.OnTaskOut()
   610  	}
   611  
   612  	return w
   613  }
   614  
   615  // Schedule is a helper that returns function which purpose is to schedule
   616  // execution of next given function over p.
   617  func Schedule(p *Pool) func(func()) {
   618  	return func(fn func()) {
   619  		_ = p.Schedule(TaskFunc(fn))
   620  	}
   621  }
   622  
   623  // ScheduleTimeout is a helper that returns function which purpose is to
   624  // schedule execution of next given function over p with timeout.
   625  func ScheduleTimeout(p *Pool) func(time.Duration, func()) error {
   626  	return func(t time.Duration, fn func()) error {
   627  		return p.ScheduleTimeout(t, TaskFunc(fn))
   628  	}
   629  }
   630  
   631  // ScheduleTimeout is a helper that returns function which purpose is to
   632  // schedule execution of next given function over p with context.
   633  func ScheduleContext(p *Pool) func(context.Context, func()) error {
   634  	return func(ctx context.Context, fn func()) error {
   635  		return p.ScheduleContext(ctx, TaskFunc(fn))
   636  	}
   637  }
   638  
   639  // ScheduleTimeout is a helper that returns function which purpose is to
   640  // schedule execution of next given function over p with cancellation chan.
   641  func ScheduleCustom(p *Pool) func(chan struct{}, func()) error {
   642  	return func(ch chan struct{}, fn func()) error {
   643  		return p.ScheduleCustom(ch, TaskFunc(fn))
   644  	}
   645  }
   646  
   647  type worker struct {
   648  	elem *list.Element
   649  
   650  	work <-chan Task
   651  	kill <-chan struct{}
   652  
   653  	direct chan Task
   654  	done   chan struct{}
   655  
   656  	ttl time.Duration
   657  	// idleTiming stat.SimpleTiming
   658  	noCork bool
   659  
   660  	onStop  func()
   661  	onStart func()
   662  }
   663  
   664  func (w *worker) run(t Task) {
   665  	defer func() {
   666  		close(w.done)
   667  		w.onStop()
   668  	}()
   669  	w.onStart()
   670  
   671  	if t != nil {
   672  		t.Run()
   673  	}
   674  
   675  	var timeout <-chan time.Time
   676  
   677  	if w.ttl != 0 {
   678  		tm := ptime.AcquireTimer(w.ttl)
   679  		defer ptime.ReleaseTimer(tm)
   680  		timeout = tm.C
   681  	}
   682  
   683  	// idle := w.idleTiming.Timer()
   684  	// idle.Start()
   685  	/// XXX: wrap Stop with func, because of receiver is defined on moment of evaluation
   686  	// defer func() { idle.Stop() }()
   687  
   688  	run := func(t Task) {
   689  		// idle.Stop()
   690  		// XXX: spawn new timer, because realtime timer should be used only once
   691  		// idle = w.idleTiming.Timer()
   692  		// idle.Start()
   693  		t.Run()
   694  	}
   695  
   696  	for {
   697  		select {
   698  		case t := <-w.direct:
   699  			run(t)
   700  		case t := <-w.work:
   701  			run(t)
   702  		case <-timeout:
   703  			// Cork direct work queue because it could contain buffered tasks,
   704  			// which execution can block the user.
   705  			cork(w.direct)
   706  
   707  			return
   708  		case <-w.kill:
   709  			// Receving from w.kill means that pool is closing and no more
   710  			// tasks will be queued soon.
   711  			if w.noCork {
   712  				// Drop everything
   713  				return
   714  			}
   715  
   716  			// We can give a last chance to read some tasks from work queue and
   717  			// reduce the pressure on Close() calling goroutine by reducing
   718  			// amount of possible tasks executed during cork(p.work) there.
   719  			runtime.Gosched()
   720  
   721  			for {
   722  				// We do not handle here w.direct due no pool closure checking
   723  				// iniside multicast(). That is, multicast() tries to send
   724  				// direct message for every worker listed in p.list. Reading
   725  				// messages from w.direct inside this loop could produce
   726  				// infinite living worker if client calls multicast() in a
   727  				// loop. Thats why we just cork(w.direct) below.
   728  				select {
   729  				case t := <-w.work:
   730  					// No idle timer usage here.
   731  					t.Run()
   732  				default:
   733  					cork(w.direct)
   734  					return
   735  				}
   736  			}
   737  		}
   738  	}
   739  }
   740  
   741  type taskWrapper struct {
   742  	task Task
   743  	// queueTimer stat.Timer
   744  	// execTimer  stat.Timer
   745  	onDequeue func()
   746  }
   747  
   748  var taskWrapperPool sync.Pool
   749  
   750  func getTaskWrapper() *taskWrapper {
   751  	if w, _ := taskWrapperPool.Get().(*taskWrapper); w != nil {
   752  		return w
   753  	}
   754  
   755  	return &taskWrapper{}
   756  }
   757  
   758  func putTaskWrapper(w *taskWrapper) {
   759  	*w = taskWrapper{}
   760  	taskWrapperPool.Put(w)
   761  }
   762  
   763  func (w *taskWrapper) Run() {
   764  	w.onDequeue()
   765  
   766  	// w.execTimer.Start()
   767  	w.task.Run()
   768  	// w.execTimer.Stop()
   769  
   770  	putTaskWrapper(w)
   771  }
   772  
   773  func callMaybe(fn func()) {
   774  	if fn != nil {
   775  		fn()
   776  	}
   777  }
   778  
   779  // cork fulfills given work channel with stub tasks. It executes every task
   780  // found in queue during corking. It returns when all tasks in queue are stubs.
   781  //
   782  // Note that no one goroutine must own the work channel while calling cork.
   783  // In other case it could lead to spinlock.
   784  func cork(work chan Task) {
   785  	n := cap(work)
   786  	for i := 0; i != n; {
   787  	corking:
   788  		for i = 0; i < n; i++ {
   789  			select {
   790  			case work <- stubTask:
   791  			default:
   792  				// Blocked to write due to some lost task in the queue.
   793  				break corking
   794  			}
   795  		}
   796  		// Drain the work queue and execute non stub tasks that stucked in the
   797  		// queue after Close() call.
   798  		//
   799  		// NOTE: it runs only when we does not fulfilled work queue with stubs
   800  		//       in loop from above (i != n).
   801  		for j := 0; i != n && j < n; j++ {
   802  			w := <-work
   803  			if w != stubTask {
   804  				w.Run()
   805  			}
   806  		}
   807  	}
   808  }
   809  
   810  type panicTask struct{}
   811  
   812  func (s panicTask) Run() {
   813  	panic("pool: this task must never be executed")
   814  }
   815  
   816  var stubTask = panicTask{}