@@ -67,6 +67,22 @@ public class EventPlatformClient: NSObject, SamplingControllerDelegate {
6767 let samplingController : SamplingController
6868 let storageManager : StorageManager ?
6969
70+ /**
71+ * Store events until the library is finished initializing
72+ *
73+ * The EPC library makes an HTTP request to a remote stream configuration service for information
74+ * about how to evaluate incoming event data. Until this initialization is complete, we store any incoming
75+ * events in this buffer.
76+ *
77+ * Only modify (append events to, remove events from) asynchronously via `queue.async`
78+ */
79+ private var inputBuffer : [ ( Data , Stream ) ] = [ ]
80+
81+ /**
82+ * Maximum number of events allowed in the input buffer
83+ */
84+ private let inbutBufferLimit = 128
85+
7086 /**
7187 * Streams are the event stream identifiers that can be utilized with the EventPlatformClientLibrary. They should
7288 * correspond to the `$id` of a schema in
@@ -142,7 +158,7 @@ public class EventPlatformClient: NSObject, SamplingControllerDelegate {
142158 /**
143159 * Holds each stream's configuration.
144160 */
145- private var streamConfigurations : [ Stream : StreamConfiguration ] {
161+ private var streamConfigurations : [ Stream : StreamConfiguration ] ? {
146162 get {
147163 queue. sync {
148164 return _streamConfigurations
@@ -154,7 +170,7 @@ public class EventPlatformClient: NSObject, SamplingControllerDelegate {
154170 }
155171 }
156172 }
157- private var _streamConfigurations : [ Stream : StreamConfiguration ] = [ : ]
173+ private var _streamConfigurations : [ Stream : StreamConfiguration ] ? = nil
158174
159175 /**
160176 * Updated when app enters background, used for determining if the session has
@@ -330,6 +346,10 @@ public class EventPlatformClient: NSObject, SamplingControllerDelegate {
330346 DDLogDebug ( " EPC: Downloaded stream configs (raw): \( raw) " )
331347 }
332348 #endif
349+ guard let storageManager = self . storageManager else {
350+ DDLogError ( " Storage manager not initialized; this shouldn't happen! " )
351+ return
352+ }
333353 struct StreamConfigurationsJSON : Codable {
334354 let streams : [ String : StreamConfiguration ]
335355 }
@@ -342,8 +362,22 @@ public class EventPlatformClient: NSObject, SamplingControllerDelegate {
342362 guard let stream = Stream ( rawValue: kv. key) else {
343363 return
344364 }
345- result [ stream] = kv. value
365+ result ? [ stream] = kv. value
346366 } )
367+
368+ // Process event buffer after making stream configs available
369+ // NOTE: If any event is re-submitted while streamConfigurations
370+ // is still being set (asynchronously), they will just go back to
371+ // input buffer.
372+ while let ( data, stream) = inputBufferPopFirst ( ) {
373+ guard let config = streamConfigurations ? [ stream] else {
374+ continue
375+ }
376+ guard samplingController. inSample ( stream: stream, config: config) else {
377+ continue
378+ }
379+ storageManager. push ( data: data, stream: stream)
380+ }
347381 } catch let error {
348382 DDLogError ( " EPC: Problem processing JSON payload from response: \( error) " )
349383 }
@@ -354,7 +388,7 @@ public class EventPlatformClient: NSObject, SamplingControllerDelegate {
354388 * fire-and-forget fashion
355389 */
356390 private func postAllScheduled( _ completion: ( ( ) -> Void ) ? = nil ) {
357- guard let storageManager = self . storageManager else {
391+ guard let storageManager = self . storageManager, let streamConfigs = self . streamConfigurations else {
358392 completion ? ( )
359393 return
360394 }
@@ -371,9 +405,9 @@ public class EventPlatformClient: NSObject, SamplingControllerDelegate {
371405 for event in events {
372406 group. enter ( )
373407
374- guard let streamConfig = self . streamConfigurations [ event. stream] else {
408+ guard let streamConfig = streamConfigs [ event. stream] else {
375409 /// Stream is not configured.
376- DDLogWarn ( " EPC: Event submitted for stream ' \( event. stream. rawValue) ' but only the following streams are configured: \( self . streamConfigurations . keys. map ( \. rawValue) . joined ( separator: " , " ) ) " )
410+ DDLogWarn ( " EPC: Event submitted for stream ' \( event. stream. rawValue) ' but only the following streams are configured: \( streamConfigs . keys. map ( \. rawValue) . joined ( separator: " , " ) ) " )
377411 storageManager. markPurgeable ( event: event)
378412 group. leave ( )
379413 continue
@@ -576,6 +610,18 @@ public class EventPlatformClient: NSObject, SamplingControllerDelegate {
576610 DDLogDebug ( " EPC: Scheduling event to be sent to \( EventPlatformClient . eventIntakeURI) with POST body: \n \( jsonString) " )
577611 #endif
578612
613+ guard let streamConfigs = streamConfigurations else {
614+ appendEventToInputBuffer ( data: data, stream: stream)
615+ return
616+ }
617+
618+ guard let config = streamConfigs [ stream] else {
619+ DDLogDebug ( " EPC: Event submitted to ' \( stream) ' but only the following streams are configured: \( streamConfigs. keys. map ( \. rawValue) . joined ( separator: " , " ) ) " )
620+ return
621+ }
622+ guard samplingController. inSample ( stream: stream, config: config) else {
623+ return
624+ }
579625 storageManager. push ( data: data, stream: stream)
580626 } catch let error {
581627 DDLogError ( " EPC: \( error. localizedDescription) " )
@@ -584,6 +630,53 @@ public class EventPlatformClient: NSObject, SamplingControllerDelegate {
584630 }
585631}
586632
633+ //MARK: Thread-safe accessors for collection properties
634+ private extension EventPlatformClient {
635+
636+ /**
637+ * Thread-safe synchronous retrieval of buffered events
638+ */
639+ func getInputBuffer( ) -> [ ( Data , Stream ) ] {
640+ queue. sync {
641+ return self . inputBuffer
642+ }
643+ }
644+
645+ /**
646+ * Thread-safe synchronous buffering of an event
647+ * - Parameter event: event to be buffered
648+ */
649+ func appendEventToInputBuffer( data: Data , stream: Stream ) {
650+ queue. sync {
651+ /*
652+ * Check if input buffer has reached maximum allowed size. Practically
653+ * speaking, there should not have been over a hundred events
654+ * generated when the user first launches the app and before the
655+ * stream configuration has been downloaded and becomes available. In
656+ * such a case we're just going to start clearing out the oldest
657+ * events to make room for new ones.
658+ */
659+ if self . inputBuffer. count == self . inbutBufferLimit {
660+ _ = self . inputBuffer. remove ( at: 0 )
661+ }
662+ self . inputBuffer. append ( ( data, stream) )
663+ }
664+ }
665+
666+
667+ /**
668+ * Thread-safe synchronous removal of first buffered event
669+ * - Returns: a previously buffered event
670+ */
671+ func inputBufferPopFirst( ) -> ( Data , Stream ) ? {
672+ queue. sync {
673+ if self . inputBuffer. isEmpty {
674+ return nil
675+ }
676+ return self . inputBuffer. remove ( at: 0 )
677+ }
678+ }
679+ }
587680
588681//MARK: NetworkIntegration
589682
0 commit comments