github.com/jeffjen/go-libkv@v0.0.0-20151212051932-5df59a45a168/timer/timer.go (about) 1 // Package timer provides basic schedule primitives for user defined methods to 2 // be engaged after set period. 3 package timer 4 5 import ( 6 "container/heap" 7 "fmt" 8 "sync" 9 "time" 10 ) 11 12 const ( 13 IDLE int = iota 14 STARTED 15 STOPPED 16 ) 17 18 // schedIdx is how we map job identifier to position in the queue. 19 type schedIdx struct { 20 sync.RWMutex 21 i map[int64]int 22 } 23 24 type Timer struct { 25 begin int64 `desc: job identifier counter` 26 27 inn chan *ticket `desc: input channel for incoming job ticket` 28 sync chan *ticket `desc: input channel for update request` 29 pq priority `desc: priority queue for the scheduler` 30 halt chan struct{} `desc: channel as stopping condition` 31 end chan struct{} `desc: channel to wait on stop completion` 32 state int `desc: mark state of the Timer` 33 lookup *schedIdx `desc: lookup table from job id to priority queue index` 34 } 35 36 func (t *Timer) repeat(job *ticket) (ack bool) { 37 t.lookup.Lock() 38 defer t.lookup.Unlock() 39 if _, ack = t.lookup.i[job.iden]; ack { 40 job.a = time.Now().Add(job.d) 41 heap.Push(&t.pq, job) 42 } 43 return 44 } 45 46 // pop removes an item for the dispatch lookup table. 47 func (t *Timer) pop(iden int64) (ack bool) { 48 t.lookup.Lock() 49 defer t.lookup.Unlock() 50 if _, ack = t.lookup.i[iden]; ack { 51 delete(t.lookup.i, iden) 52 } 53 return 54 } 55 56 // place adds one job to the Timer object. 57 func (t *Timer) place(job *ticket) { 58 t.lookup.Lock() 59 defer t.lookup.Unlock() 60 t.lookup.i[job.iden] = job.pos 61 } 62 63 // dispatch finds expired timer and schedule job on its own goroutine. 64 func (t *Timer) dispatch() { 65 now := time.Now() 66 for ok := true; ok; { 67 if len(t.pq) > 0 && now.After(t.pq[0].a) { 68 job := heap.Pop(&t.pq).(*ticket) 69 if job.r { 70 if t.repeat(job) { 71 if job.ntask < job.concurr { 72 job.ntask += 1 // increment task counter 73 go func() { 74 job.h.Done(job.iden) // fire the worker on timer 75 job.ntask -= 1 // job done, -1 task counter 76 }() 77 } 78 } 79 } else { 80 if t.pop(job.iden) { 81 go job.h.Done(job.iden) // fire the worker on timer 82 } 83 } 84 } else { 85 ok = false 86 } 87 } 88 } 89 90 // Tic starts the Timer in a goroutine. 91 // Accepts incoming schedudle request via SchedFunc and Sched. 92 func (t *Timer) Tic() { 93 if t.state != IDLE { 94 panic(fmt.Errorf("timer must be in IDLE to Tic")) 95 } else { 96 t.state = STARTED 97 } 98 99 t.halt, t.end = make(chan struct{}), make(chan struct{}) 100 101 var wg sync.WaitGroup 102 wg.Add(1) 103 104 go func() { 105 defer wg.Done() 106 var alarm <-chan time.Time 107 for yay := true; yay; { 108 // determine the closest future about to happen 109 if len(t.pq) == 0 { 110 alarm = time.After(1 * time.Hour) 111 } else { 112 diff := t.pq[0].a.Sub(time.Now()) 113 if diff < 0 { 114 t.dispatch() 115 continue 116 } 117 alarm = time.After(diff) 118 } 119 120 select { 121 case <-t.halt: 122 yay = false // someone closed the light, quit 123 124 case job := <-t.inn: 125 heap.Push(&t.pq, job) 126 t.place(job) 127 128 case old_job := <-t.sync: 129 heap.Fix(&t.pq, old_job.pos) 130 t.place(old_job) 131 132 case <-alarm: 133 t.dispatch() 134 } 135 } 136 }() 137 138 go func() { // wait for timer runner and collect it 139 wg.Wait() 140 close(t.end) 141 }() 142 } 143 144 // Toc stops the Timer and all schedudled handler are dropped. 145 // Stopped timer cannot be restarted. 146 func (t *Timer) Toc() { 147 close(t.halt) 148 for _ = range t.end { 149 } 150 t.state = STOPPED 151 } 152 153 // RepeatFunc accepts a time.Duration, concurrent limit, and a handle function. 154 // handle function is invoked in its own goroutine at set interval. 155 // Only set concurrent limit of task will be spawned at any given moment 156 // Returns an identifier for caller to Cancel. 157 func (t *Timer) RepeatFunc(d time.Duration, concurr int, handle func(int64)) (iden int64) { 158 t.begin, iden = t.begin+1, t.begin 159 t.inn <- &ticket{ 160 a: time.Now().Add(d), 161 h: HandlerFunc(handle), 162 iden: iden, 163 r: true, 164 d: d, 165 concurr: concurr, 166 ntask: 0, 167 } 168 return 169 } 170 171 // Repeat accepts a time.Duration, concurrent limit, and a Handler interface. 172 // handler is invoked in its own goroutine at set interval. 173 // Only set concurrent limit of task will be spawned at any given moment 174 // Returns an identifier for caller to Cancel. 175 func (t *Timer) Repeat(d time.Duration, concurr int, handle Handler) (iden int64) { 176 t.begin, iden = t.begin+1, t.begin 177 t.inn <- &ticket{ 178 a: time.Now().Add(d), 179 h: handle, 180 iden: iden, 181 r: true, 182 d: d, 183 concurr: concurr, 184 ntask: 0, 185 } 186 return 187 } 188 189 // SchedFunc accepts time.Time object and a handle function. 190 // handle function is invoked in its own goroutine at designated time. 191 // Returns an identifier for caller to Cancel or Update. 192 func (t *Timer) SchedFunc(c time.Time, handle func(int64)) (iden int64) { 193 t.begin, iden = t.begin+1, t.begin 194 t.inn <- &ticket{a: c, h: HandlerFunc(handle), iden: iden} 195 return 196 } 197 198 // Sched accepts time.Time object and a Handler interface. 199 // handler is invoked in its own goroutine when at designated time. 200 // Returns an identifier for caller to Cancel or Update. 201 func (t *Timer) Sched(c time.Time, handle Handler) (iden int64) { 202 t.begin, iden = t.begin+1, t.begin 203 t.inn <- &ticket{a: c, h: handle, iden: iden} 204 return 205 } 206 207 // Update takes an identifier by SchedFunc or Sched and reschedules its TTL. 208 func (t *Timer) Update(iden int64, c time.Time) { 209 t.lookup.RLock() 210 defer t.lookup.RUnlock() 211 if pos, ok := t.lookup.i[iden]; ok { 212 job := t.pq[pos] 213 job.a = c 214 t.sync <- job 215 } 216 } 217 218 // Cancel takes an identifier by SchedFunc or Sched and disables the handler. 219 // Effectively prevents the handler to be invoked. 220 func (t *Timer) Cancel(iden int64) { 221 t.pop(iden) 222 } 223 224 // NewTimer creates a Timer object. Timer is initialized by not engaged. 225 // To enage the Timer, invoke Tic. 226 // To stop the Timer, invoke Toc. 227 func NewTimer() (t *Timer) { 228 pri := make(priority, 0) 229 heap.Init(&pri) 230 return &Timer{ 231 begin: 1, // initialized to positive value 232 state: IDLE, 233 inn: make(chan *ticket, 1), 234 sync: make(chan *ticket, 1), 235 pq: pri, 236 halt: nil, 237 end: nil, 238 lookup: &schedIdx{i: make(map[int64]int)}, 239 } 240 }