├── LICENSE ├── README.md ├── RELEASE_NOTES.md ├── example_test.go ├── go.mod ├── queue.go └── queue_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015-2023 Brett Vickers 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its 14 | contributors may be used to endorse or promote products derived from this 15 | software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 18 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 19 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 20 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 21 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 24 | OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 25 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 26 | OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/beevik/timerqueue?status.svg)](https://godoc.org/github.com/beevik/timerqueue) 2 | 3 | timerqueue 4 | ========== 5 | 6 | The timerqueue package implements a priority queue for objects scheduled to 7 | perform actions at clock times. 8 | 9 | ## Example: Scheduling timers 10 | 11 | The following code declares an object implementing the `Timer` interface, 12 | creates a timerqueue, and adds three events to the timerqueue. 13 | 14 | ```go 15 | type event int 16 | 17 | func (e event) OnTimer(t time.Time) { 18 | fmt.Printf("event.OnTimer %d fired at %v\n", int(e), t) 19 | } 20 | 21 | queue := timerqueue.New() 22 | queue.Schedule(event(1), time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)) 23 | queue.Schedule(event(2), time.Date(2015, 1, 3, 0, 0, 0, 0, time.UTC)) 24 | queue.Schedule(event(3), time.Date(2015, 1, 2, 0, 0, 0, 0, time.UTC)) 25 | 26 | ``` 27 | 28 | ## Example: Peeking at the next timer to be scheduled 29 | 30 | Using the queue initialized in the first example, the following code 31 | examines the head of the timerqueue and outputs the id and time of 32 | the event found there. 33 | 34 | ```go 35 | e, t := queue.PeekFirst() 36 | if e != nil { 37 | fmt.Printf("Event %d will be first to fire at %v.\n", int(e.(event)), t) 38 | fmt.Printf("%d events remain in the timerqueue.", queue.Len()) 39 | } 40 | ``` 41 | 42 | Output: 43 | ``` 44 | Event 1 will be first to fire at 2015-01-01 00:00:00 +0000 UTC. 45 | 3 events remain in the timerqueue. 46 | ``` 47 | 48 | ## Example: Popping the next timer to be scheduled 49 | 50 | Using the queue initialized in the first example, this code 51 | removes the next timer to be executed until the queue is empty. 52 | 53 | ```go 54 | for queue.Len() > 0 { 55 | e, t := queue.PopFirst() 56 | fmt.Printf("Event %d fires at %v.\n", int(e.(event)), t) 57 | } 58 | ``` 59 | 60 | Output: 61 | ``` 62 | Event 1 fires at 2015-01-01 00:00:00 +0000 UTC. 63 | Event 3 fires at 2015-01-02 00:00:00 +0000 UTC. 64 | Event 2 fires at 2015-01-03 00:00:00 +0000 UTC. 65 | ``` 66 | 67 | ## Example: Issuing OnTimer callbacks with Advance 68 | 69 | The final example shows how to dispatch OnTimer callbacks to 70 | timers using the timerqueue's Advance method. 71 | 72 | Advance calls the OnTimer method for each timer scheduled 73 | before the requested time. Timers are removed from the timerqueue 74 | in order of their scheduling. 75 | 76 | ```go 77 | // Call the OnTimer method for each event scheduled before 78 | // January 10, 2015. Pop the called timer from the queue. 79 | queue.Advance(time.Date(2015, 1, 10, 0, 0, 0, 0, time.UTC)) 80 | ``` 81 | 82 | Output: 83 | ``` 84 | event.OnTimer 1 fired at 2015-01-01 00:00:00 +0000 UTC. 85 | event.OnTimer 3 fired at 2015-01-02 00:00:00 +0000 UTC. 86 | event.OnTimer 2 fired at 2015-01-03 00:00:00 +0000 UTC. 87 | ``` 88 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | Release v1.0.0 2 | ============== 3 | 4 | Initial release. 5 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package timerqueue_test 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/beevik/timerqueue" 8 | ) 9 | 10 | type event int 11 | 12 | func (e event) OnTimer(t time.Time) { 13 | fmt.Printf(" Event %d executed at %v\n", int(e), t) 14 | } 15 | 16 | // Schedule several events with a timerqueue, and dispatch 17 | // them by calling Advance. 18 | func ExampleQueue() { 19 | queue := timerqueue.New() 20 | 21 | // Schedule an event each day from Jan 1 to Jan 7, 2015. 22 | tm := time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC) 23 | for i := 1; i <= 7; i++ { 24 | queue.Schedule(event(i), tm) 25 | tm = tm.Add(24 * time.Hour) 26 | } 27 | 28 | fmt.Println("Advancing to Jan 4...") 29 | queue.Advance(time.Date(2015, 1, 4, 0, 0, 0, 0, time.UTC)) 30 | 31 | fmt.Println("Advancing to Jan 10...") 32 | queue.Advance(time.Date(2015, 1, 10, 0, 0, 0, 0, time.UTC)) 33 | 34 | // Output: 35 | // Advancing to Jan 4... 36 | // Event 1 executed at 2015-01-01 00:00:00 +0000 UTC 37 | // Event 2 executed at 2015-01-02 00:00:00 +0000 UTC 38 | // Event 3 executed at 2015-01-03 00:00:00 +0000 UTC 39 | // Event 4 executed at 2015-01-04 00:00:00 +0000 UTC 40 | // Advancing to Jan 10... 41 | // Event 5 executed at 2015-01-05 00:00:00 +0000 UTC 42 | // Event 6 executed at 2015-01-06 00:00:00 +0000 UTC 43 | // Event 7 executed at 2015-01-07 00:00:00 +0000 UTC 44 | } 45 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/beevik/timerqueue 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /queue.go: -------------------------------------------------------------------------------- 1 | // Package timerqueue implements a priority queue for objects scheduled at a 2 | // particular time. 3 | package timerqueue 4 | 5 | import ( 6 | "container/heap" 7 | "errors" 8 | "time" 9 | ) 10 | 11 | // Timer is an interface that types implement to schedule and receive OnTimer 12 | // callbacks. 13 | type Timer interface { 14 | OnTimer(t time.Time) 15 | } 16 | 17 | // Queue is a time-sorted collection of Timer objects. 18 | type Queue struct { 19 | heap timerHeap 20 | table map[Timer]*timerData 21 | } 22 | 23 | type timerData struct { 24 | timer Timer 25 | time time.Time 26 | index int 27 | } 28 | 29 | // New creates a new timer priority queue. 30 | func New() *Queue { 31 | return &Queue{ 32 | table: make(map[Timer]*timerData), 33 | } 34 | } 35 | 36 | // Len returns the current number of timer objects in the queue. 37 | func (q *Queue) Len() int { 38 | return len(q.heap) 39 | } 40 | 41 | // Schedule schedules a timer for exectuion at time tm. If the 42 | // timer was already scheduled, it is rescheduled. 43 | func (q *Queue) Schedule(t Timer, tm time.Time) { 44 | if data, ok := q.table[t]; !ok { 45 | data = &timerData{t, tm, 0} 46 | heap.Push(&q.heap, data) 47 | q.table[t] = data 48 | } else { 49 | data.time = tm 50 | heap.Fix(&q.heap, data.index) 51 | } 52 | } 53 | 54 | // Unschedule unschedules a timer's execution. 55 | func (q *Queue) Unschedule(t Timer) { 56 | if data, ok := q.table[t]; ok { 57 | heap.Remove(&q.heap, data.index) 58 | delete(q.table, t) 59 | } 60 | } 61 | 62 | // GetTime returns the time at which the timer is scheduled. 63 | // If the timer isn't currently scheduled, an error is returned. 64 | func (q *Queue) GetTime(t Timer) (tm time.Time, err error) { 65 | if data, ok := q.table[t]; ok { 66 | return data.time, nil 67 | } 68 | return time.Time{}, errors.New("timerqueue: timer not scheduled") 69 | } 70 | 71 | // IsScheduled returns true if the timer is currently scheduled. 72 | func (q *Queue) IsScheduled(t Timer) bool { 73 | _, ok := q.table[t] 74 | return ok 75 | } 76 | 77 | // Clear unschedules all currently scheduled timers. 78 | func (q *Queue) Clear() { 79 | q.heap, q.table = nil, make(map[Timer]*timerData) 80 | } 81 | 82 | // PopFirst removes and returns the next timer to be scheduled and 83 | // the time at which it is scheduled to run. 84 | func (q *Queue) PopFirst() (t Timer, tm time.Time) { 85 | if len(q.heap) > 0 { 86 | data := heap.Pop(&q.heap).(*timerData) 87 | delete(q.table, data.timer) 88 | return data.timer, data.time 89 | } 90 | return nil, time.Time{} 91 | } 92 | 93 | // PeekFirst returns the next timer to be scheduled and the time 94 | // at which it is scheduled to run. It does not modify the contents 95 | // of the timer queue. 96 | func (q *Queue) PeekFirst() (t Timer, tm time.Time) { 97 | if len(q.heap) > 0 { 98 | return q.heap[0].timer, q.heap[0].time 99 | } 100 | return nil, time.Time{} 101 | } 102 | 103 | // Advance executes OnTimer callbacks for all timers scheduled to be 104 | // run before the time 'tm'. Executed timers are removed from the 105 | // timer queue. 106 | func (q *Queue) Advance(tm time.Time) { 107 | for len(q.heap) > 0 && !tm.Before(q.heap[0].time) { 108 | data := q.heap[0] 109 | heap.Remove(&q.heap, data.index) 110 | delete(q.table, data.timer) 111 | data.timer.OnTimer(data.time) 112 | } 113 | } 114 | 115 | /* 116 | * timerHeap 117 | */ 118 | 119 | type timerHeap []*timerData 120 | 121 | func (h timerHeap) Len() int { 122 | return len(h) 123 | } 124 | 125 | func (h timerHeap) Less(i, j int) bool { 126 | return h[i].time.Before(h[j].time) 127 | } 128 | 129 | func (h timerHeap) Swap(i, j int) { 130 | h[i], h[j] = h[j], h[i] 131 | h[i].index, h[j].index = i, j 132 | } 133 | 134 | func (h *timerHeap) Push(x interface{}) { 135 | data := x.(*timerData) 136 | *h = append(*h, data) 137 | data.index = len(*h) - 1 138 | } 139 | 140 | func (h *timerHeap) Pop() interface{} { 141 | n := len(*h) 142 | data := (*h)[n-1] 143 | *h = (*h)[:n-1] 144 | data.index = -1 145 | return data 146 | } 147 | -------------------------------------------------------------------------------- /queue_test.go: -------------------------------------------------------------------------------- 1 | package timerqueue 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | type object struct { 10 | value int 11 | } 12 | 13 | var executed int 14 | 15 | func (o *object) OnTimer(t time.Time) { 16 | executed++ 17 | } 18 | 19 | func populateQueue(t *testing.T, now time.Time) *Queue { 20 | q := New() 21 | 22 | count := 300 23 | objects := make([]*object, count) 24 | 25 | // Add a bunch of objects to the queue in random order. 26 | for i, j := range rand.Perm(count) { 27 | tm := now.Add(time.Duration(i+1) * time.Hour) 28 | objects[j] = &object{j} 29 | q.Schedule(objects[j], tm) 30 | } 31 | 32 | // Reschedule all the objects in a different random order. 33 | for i, j := range rand.Perm(count) { 34 | tm := now.Add(time.Duration(i+1) * time.Hour) 35 | q.Schedule(objects[j], tm) 36 | } 37 | 38 | if q.Len() != count { 39 | t.Error("invalid queue length:", q.Len()) 40 | } 41 | 42 | return q 43 | } 44 | 45 | func TestEmptyQueue(t *testing.T) { 46 | queue := New() 47 | 48 | o, _ := queue.PeekFirst() 49 | if o != nil { 50 | t.Error("Expected nil peek") 51 | } 52 | 53 | o, _ = queue.PopFirst() 54 | if o != nil { 55 | t.Error("Expected nil pop") 56 | } 57 | } 58 | 59 | func TestQueue(t *testing.T) { 60 | for iter := 0; iter < 100; iter++ { 61 | now := time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC) 62 | queue := populateQueue(t, now) 63 | 64 | // Make sure objects are removed from the queue in order. 65 | for prev := now; queue.Len() > 0; { 66 | _, ptm := queue.PeekFirst() 67 | _, tm := queue.PopFirst() 68 | if tm != ptm { 69 | t.Errorf("Peek/Pop mismatch.\n") 70 | } 71 | if tm.Sub(prev) != time.Hour { 72 | t.Errorf("Invalid queue ordering.\n"+ 73 | " Got: %v\n"+ 74 | "Expected: %v\n", tm, prev.Add(time.Hour)) 75 | } 76 | prev = tm 77 | } 78 | } 79 | } 80 | 81 | func TestAdvance(t *testing.T) { 82 | for iter := 0; iter < 100; iter++ { 83 | now := time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC) 84 | queue := populateQueue(t, now) 85 | 86 | executed = 0 87 | count := queue.Len() 88 | lastTime := now.Add(time.Duration(count) * time.Hour) 89 | 90 | for adv := 0; adv < 5; adv++ { 91 | queue.Advance(lastTime) 92 | if executed != count { 93 | t.Errorf("Advance failed.\n"+ 94 | "Should have executed %d times.\n"+ 95 | "Only executed %d times.\n", count, executed) 96 | } 97 | } 98 | } 99 | } 100 | --------------------------------------------------------------------------------