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  }