Skip to content

Commit c0faca2

Browse files
author
Michael Holloway
committed
Restore input buffer
Restores the input buffer for use before stream configs are fetched on startup. Before stream configs are fetched, events go in the input buffer. Upon receiving stream configs, they are evaluated and moved to the DB if in a configured stream and in sample. Stream configs and events in the input buffer do not survive app termination. Events in the DB do survive app termination.
1 parent 61d421a commit c0faca2

File tree

4 files changed

+101
-11
lines changed

4 files changed

+101
-11
lines changed

WMF Framework/Event Platform/EPEventRecord+CoreDataProperties.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,5 @@ extension EPEventRecord {
1111
@NSManaged public var stream: String
1212
@NSManaged public var recorded: Date?
1313
@NSManaged public var purgeable: Bool
14-
@NSManaged public var userAgent: String?
1514

1615
}

WMF Framework/Event Platform/EventPlatformClient.swift

Lines changed: 99 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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

WMF Framework/Event Platform/EventPlatformEvents.xcdatamodeld/EventPlatformEvents.xcdatamodel/contents

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@
55
<attribute name="purgeable" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
66
<attribute name="recorded" attributeType="Date" usesScalarValueType="NO"/>
77
<attribute name="stream" attributeType="String"/>
8-
<attribute name="userAgent" optional="YES" attributeType="String"/>
98
<fetchIndex name="byRecordedIndex">
109
<fetchIndexElement property="recorded" type="Binary" order="ascending"/>
1110
</fetchIndex>
1211
</entity>
1312
<elements>
14-
<element name="WMFEPEventRecord" positionX="-63" positionY="-18" width="128" height="104"/>
13+
<element name="WMFEPEventRecord" positionX="-63" positionY="-18" width="128" height="89"/>
1514
</elements>
1615
</model>

WMF Framework/Event Platform/StorageManager.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ public class StorageManager: NSObject {
6161
record.data = data
6262
record.stream = stream.rawValue
6363
record.recorded = now
64-
record.userAgent = WikipediaAppUtils.versionedUserAgent()
6564

6665
DDLogDebug("EPC StorageManager: \(record.objectID) recorded!")
6766

@@ -107,7 +106,7 @@ public class StorageManager: NSObject {
107106
return
108107
}
109108
guard let record = try moc.existingObject(with: moid) as? EPEventRecord else {
110-
DDLogWarn("EPC: Tried to mark managed object \(moid) as purgeable, but it was not found.")
109+
DDLogWarn("EPC: Tried to mark managed object \(moid) as purgeable, but it was not found")
111110
return
112111
}
113112
record.purgeable = true

0 commit comments

Comments
 (0)