github.com/geph-official/geph2@v0.22.6-0.20210211030601-f527cb59b0df/libs/kcp-go/timedsched.go (about)

     1  package kcp
     2  
     3  import (
     4  	"container/heap"
     5  	"runtime"
     6  	"sync"
     7  	"time"
     8  )
     9  
    10  // SystemTimedSched is the library level timed-scheduler
    11  var SystemTimedSched = NewTimedSched(runtime.NumCPU())
    12  
    13  type timedFunc struct {
    14  	execute func()
    15  	ts      time.Time
    16  }
    17  
    18  // a heap for sorted timed function
    19  type timedFuncHeap []timedFunc
    20  
    21  func (h timedFuncHeap) Len() int            { return len(h) }
    22  func (h timedFuncHeap) Less(i, j int) bool  { return h[i].ts.Before(h[j].ts) }
    23  func (h timedFuncHeap) Swap(i, j int)       { h[i], h[j] = h[j], h[i] }
    24  func (h *timedFuncHeap) Push(x interface{}) { *h = append(*h, x.(timedFunc)) }
    25  func (h *timedFuncHeap) Pop() interface{} {
    26  	old := *h
    27  	n := len(old)
    28  	x := old[n-1]
    29  	old[n-1].execute = nil // avoid memory leak
    30  	*h = old[0 : n-1]
    31  	return x
    32  }
    33  
    34  // TimedSched represents the control struct for timed parallel scheduler
    35  type TimedSched struct {
    36  	// prepending tasks
    37  	prependTasks    []timedFunc
    38  	prependLock     sync.Mutex
    39  	chPrependNotify chan struct{}
    40  
    41  	// tasks will be distributed through chTask
    42  	chTask chan timedFunc
    43  
    44  	dieOnce sync.Once
    45  	die     chan struct{}
    46  }
    47  
    48  // NewTimedSched creates a parallel-scheduler with given parallelization
    49  func NewTimedSched(parallel int) *TimedSched {
    50  	ts := new(TimedSched)
    51  	ts.chTask = make(chan timedFunc)
    52  	ts.die = make(chan struct{})
    53  	ts.chPrependNotify = make(chan struct{}, 1)
    54  
    55  	for i := 0; i < parallel; i++ {
    56  		go ts.sched()
    57  	}
    58  	go ts.prepend()
    59  	return ts
    60  }
    61  
    62  func (ts *TimedSched) sched() {
    63  	var tasks timedFuncHeap
    64  	timer := time.NewTimer(0)
    65  	for {
    66  		select {
    67  		case task := <-ts.chTask:
    68  			now := time.Now()
    69  			if now.After(task.ts) {
    70  				// already delayed! execute immediately
    71  				//log.Println("delay", now.Sub(task.ts))
    72  				task.execute()
    73  			} else {
    74  				heap.Push(&tasks, task)
    75  				// activate timer if timer has hibernated due to 0 tasks.
    76  				if tasks.Len() == 1 {
    77  					timer.Reset(task.ts.Sub(now))
    78  				}
    79  			}
    80  		case <-timer.C:
    81  			for tasks.Len() > 0 {
    82  				now := time.Now()
    83  				if now.After(tasks[0].ts) {
    84  					task := heap.Pop(&tasks).(timedFunc)
    85  					//log.Println("delay", now.Sub(task.ts))
    86  					task.execute()
    87  				} else {
    88  					timer.Reset(tasks[0].ts.Sub(now))
    89  					break
    90  				}
    91  			}
    92  		case <-ts.die:
    93  			return
    94  		}
    95  	}
    96  }
    97  
    98  func (ts *TimedSched) prepend() {
    99  	var tasks []timedFunc
   100  	for {
   101  		select {
   102  		case <-ts.chPrependNotify:
   103  			ts.prependLock.Lock()
   104  			// keep cap to reuse slice
   105  			if cap(tasks) < cap(ts.prependTasks) {
   106  				tasks = make([]timedFunc, 0, cap(ts.prependTasks))
   107  			}
   108  			tasks = tasks[:len(ts.prependTasks)]
   109  			copy(tasks, ts.prependTasks)
   110  			for k := range ts.prependTasks {
   111  				ts.prependTasks[k].execute = nil // avoid memory leak
   112  			}
   113  			ts.prependTasks = ts.prependTasks[:0]
   114  			ts.prependLock.Unlock()
   115  
   116  			for k := range tasks {
   117  				select {
   118  				case ts.chTask <- tasks[k]:
   119  					tasks[k].execute = nil // avoid memory leak
   120  				case <-ts.die:
   121  					return
   122  				}
   123  			}
   124  			tasks = tasks[:0]
   125  		case <-ts.die:
   126  			return
   127  		}
   128  	}
   129  }
   130  
   131  // Put a function 'f' awaiting to be executed at 'deadline'
   132  func (ts *TimedSched) Put(f func(), deadline time.Time) {
   133  	ts.prependLock.Lock()
   134  	ts.prependTasks = append(ts.prependTasks, timedFunc{f, deadline})
   135  	ts.prependLock.Unlock()
   136  
   137  	select {
   138  	case ts.chPrependNotify <- struct{}{}:
   139  	default:
   140  	}
   141  }
   142  
   143  // Close terminates this scheduler
   144  func (ts *TimedSched) Close() { ts.dieOnce.Do(func() { close(ts.die) }) }