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  }