github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/http/longpoll/events.go (about) 1 package longpoll 2 3 import ( 4 "container/list" 5 "errors" 6 "fmt" 7 "time" 8 ) 9 10 // lpEvent is a longpoll event. This type has a Timestamp as milliseconds since 11 // epoch (UTC), a string category, and an arbitrary Data payload. 12 // The category is the subscription category/topic that clients can listen for 13 // via longpolling. The Data payload can be anything that is JSON serializable 14 // via the encoding/json library's json.Marshal function. 15 type lpEvent struct { 16 // Timestamp is milliseconds since epoch to match javascrits Date.getTime() 17 Timestamp int64 `json:"timestamp"` 18 Category string `json:"category"` 19 // NOTE: Data can be anything that is able to passed to json.Marshal() 20 Data interface{} `json:"data"` 21 } 22 23 // eventResponse is the json response that carries longpoll events. 24 type eventResponse struct { 25 Events *[]lpEvent `json:"events"` 26 } 27 28 // eventBuffer is a buffer of Events that adds new events to the front/root and 29 // and old events are removed from the back/tail when the buffer reaches it's 30 // maximum capacity. 31 // NOTE: this add-new-to-front/remove-old-from-back behavior is fairly 32 // efficient since it is implemented as a ring with root.prev being the tail. 33 // Unlike an array, we don't have to shift every element when something gets 34 // added to the front, and because our root has a root.prev reference, we can 35 // quickly jump from the root to the tail instead of having to follow every 36 // node's node.next field to finally reach the end. 37 // For more details on our list's implementation, see: 38 // https://golang.org/src/container/list/list.go 39 type eventBuffer struct { 40 *list.List 41 MaxBufferSize int 42 // keeping track of this allows for more efficient event TTL expiration purges: 43 // time in milliseconds since epoch since thats what lpEvent types use 44 // for Timestamps 45 oldestEventTime int64 46 } 47 48 // QueueEvent adds a new longpoll Event to the front of our buffer and removes 49 // the oldest event from the back of the buffer if we're already at maximum 50 // capacity. 51 func (eb *eventBuffer) QueueEvent(event *lpEvent) error { 52 if event == nil { 53 return errors.New("event was nil") 54 } 55 // Cull our buffer if we're at max capacity 56 if eb.List.Len() >= eb.MaxBufferSize { 57 oldestEvent := eb.List.Back() 58 if oldestEvent != nil { 59 eb.List.Remove(oldestEvent) 60 } 61 } 62 // Add event to front of our list 63 eb.List.PushFront(event) 64 // Update oldestEventTime with the time of our least recent event (at back) 65 // keeping track of this allows for more efficient event TTL expiration purges 66 if lastElement := eb.List.Back(); lastElement != nil { 67 lastEvent, ok := lastElement.Value.(*lpEvent) 68 if !ok { 69 return fmt.Errorf("Found non-event type in event buffer.") 70 } 71 eb.oldestEventTime = lastEvent.Timestamp 72 } 73 return nil 74 } 75 76 // GetEventsSnce will return all of the Events in our buffer that occurred after 77 // the given input time (since). Returns an error value if there are any 78 // objects that aren't an Event type in the buffer. (which would be weird...) 79 // Optionally removes returned events from the eventBuffer if told to do so by 80 // deleteFetchedEvents argument. 81 func (eb *eventBuffer) GetEventsSince(since time.Time, 82 deleteFetchedEvents bool) ([]lpEvent, error) { 83 events := make([]lpEvent, 0) 84 // NOTE: events are bufferd with the most recent event at the front. 85 // So we want to start our search at the front of the buffer and stop 86 // searching once we've reached events that are older than the 'since' 87 // argument. But we want to return the subset of events in chronological 88 // order, which is least recent in front. So do our search from the 89 // start so we can cut out early, but then iterate back from our last 90 // item we want to return as a result. Doing this avoids having to capture 91 // results and then create another copy of the results but in reverse 92 // order. 93 var lastGoodItem *list.Element 94 // Search forward until we reach events that are too old 95 for element := eb.List.Front(); element != nil; element = element.Next() { 96 event, ok := element.Value.(*lpEvent) 97 if !ok { 98 return events, fmt.Errorf("Found non-event type in event buffer.") 99 } 100 // is event time after 'since' time arg? convert 'since' to epoch ms 101 if event.Timestamp > since.UnixNano()/int64(time.Millisecond) { 102 lastGoodItem = element 103 } else { 104 // we've reached items that are too old, they occurred before or on 105 // 'since' so we don't care about anything after this point. 106 break 107 } 108 } 109 // Now accumulate results in the correct chronological order starting from 110 // our oldest, valid Event that occurrs after 'since' 111 if lastGoodItem != nil { 112 // Tracked outside of loop conditional to allow delete while iterating: 113 var prev *list.Element 114 for element := lastGoodItem; element != nil; element = prev { 115 event, ok := element.Value.(*lpEvent) 116 if !ok { 117 return events, fmt.Errorf( 118 "Found non-event type in event buffer.") 119 } 120 // we already know this event is after 'since' 121 events = append(events, 122 lpEvent{event.Timestamp, event.Category, event.Data}) 123 // Advance iteration before List.Remove() invalidates element.prev 124 prev = element.Prev() 125 // Now safely remove from list if told to do so: 126 if deleteFetchedEvents { 127 eb.List.Remove(element) // element.Prev() now == nil 128 } 129 } 130 } 131 return events, nil 132 } 133 134 func (eb *eventBuffer) DeleteEventsOlderThan(olderThanTimeMs int64) error { 135 if eb.List.Len() == 0 || eb.oldestEventTime > olderThanTimeMs { 136 // Either no events or the the oldest event is more recent than 137 // olderThanTimeMs, so nothing could possibly be expired. 138 // skip searching list 139 return nil 140 } 141 // Search list in reverse (starting from the back) removing expired events 142 // and updating eb.oldestEventTime as we remove stale events. 143 // NOTE: we iterate over list in reverse since oldest elements are at 144 // the back, newest up front. 145 var prev *list.Element 146 for element := eb.List.Back(); element != nil; element = prev { 147 event, ok := element.Value.(*lpEvent) 148 if !ok { 149 return fmt.Errorf("Found non-event type in event buffer.") 150 } 151 // Advance iteration before List.Remove() invalidates element.prev 152 prev = element.Prev() 153 // Update oldestEventTime to the current event's Timestamp 154 eb.oldestEventTime = event.Timestamp 155 // Now able to safely remove from list event is too old: 156 if event.Timestamp <= olderThanTimeMs { 157 eb.List.Remove(element) // element.Prev() now == nil 158 } else { 159 // element is too new, stop checking since events are only going to 160 // get even more recent as we get closer to the front of the list 161 return nil 162 } 163 } 164 return nil 165 }