@@ -28,13 +28,11 @@ open class DefaultEventDispatcher: BackgroundingCallbacks, OPTEventDispatcher {
28
28
static let MAX_FAILURE_COUNT = 3
29
29
30
30
// default timerInterval
31
- var timerInterval : TimeInterval // every minute
31
+ var timerInterval : TimeInterval
32
32
// default batchSize.
33
33
// attempt to send events in batches with batchSize number of events combined
34
34
var batchSize : Int
35
- // start trimming the front of the queue when we get to over maxQueueSize
36
- // TODO: implement
37
- var maxQueueSize : Int = 30000
35
+ var maxQueueSize : Int
38
36
39
37
lazy var logger = OPTLoggerFactory . getLogger ( )
40
38
var backingStore : DataStoreType
@@ -47,35 +45,79 @@ open class DefaultEventDispatcher: BackgroundingCallbacks, OPTEventDispatcher {
47
45
// timer as a atomic property.
48
46
var timer : AtomicProperty < Timer > = AtomicProperty < Timer > ( )
49
47
50
- public init ( batchSize: Int = 10 , backingStore: DataStoreType = . file, dataStoreName: String = " OPTEventQueue " , timerInterval: TimeInterval = 60 * 1 ) {
51
- self . batchSize = batchSize > 0 ? batchSize : 1
48
+ public struct DefaultValues {
49
+ static public let batchSize = 10
50
+ static public let timeInterval : TimeInterval = 60 // secs
51
+ static public let maxQueueSize = 10000
52
+ }
53
+
54
+ public init ( batchSize: Int = DefaultValues . batchSize,
55
+ backingStore: DataStoreType = . file,
56
+ dataStoreName: String = " OPTEventQueue " ,
57
+ timerInterval: TimeInterval = DefaultValues . timeInterval,
58
+ maxQueueSize: Int = DefaultValues . maxQueueSize) {
59
+ self . batchSize = batchSize > 0 ? batchSize : DefaultValues . batchSize
52
60
self . backingStore = backingStore
53
61
self . backingStoreName = dataStoreName
54
62
self . timerInterval = timerInterval
63
+ self . maxQueueSize = maxQueueSize > 100 ? maxQueueSize : DefaultValues . maxQueueSize
55
64
56
65
switch backingStore {
57
66
case . file:
58
- self . dataStore = DataStoreQueueStackImpl < EventForDispatch > ( queueStackName: " OPTEventQueue " , dataStore: DataStoreFile < [ Data ] > ( storeName: backingStoreName) )
67
+ self . dataStore = DataStoreQueueStackImpl < EventForDispatch > ( queueStackName: " OPTEventQueue " ,
68
+ dataStore: DataStoreFile < [ Data ] > ( storeName: backingStoreName) )
59
69
case . memory:
60
- self . dataStore = DataStoreQueueStackImpl < EventForDispatch > ( queueStackName: " OPTEventQueue " , dataStore: DataStoreMemory < [ Data ] > ( storeName: backingStoreName) )
70
+ self . dataStore = DataStoreQueueStackImpl < EventForDispatch > ( queueStackName: " OPTEventQueue " ,
71
+ dataStore: DataStoreMemory < [ Data ] > ( storeName: backingStoreName) )
61
72
case . userDefaults:
62
- self . dataStore = DataStoreQueueStackImpl < EventForDispatch > ( queueStackName: " OPTEventQueue " , dataStore: DataStoreUserDefaults ( ) )
73
+ self . dataStore = DataStoreQueueStackImpl < EventForDispatch > ( queueStackName: " OPTEventQueue " ,
74
+ dataStore: DataStoreUserDefaults ( ) )
63
75
}
64
76
77
+
78
+ if self . maxQueueSize < self . batchSize {
79
+ self . logger. e ( . eventDispatcherConfigError( " batchSize cannot be bigger than maxQueueSize " ) )
80
+ self . maxQueueSize = self . batchSize
81
+ }
82
+
83
+ addProjectChangeNotificationObservers ( )
84
+
65
85
subscribe ( )
66
86
}
67
87
68
88
deinit {
69
- timer. performAtomic { ( timer) in
70
- timer. invalidate ( )
71
- }
89
+ stopTimer ( )
90
+
72
91
unsubscribe ( )
73
92
}
74
93
94
+ func addProjectChangeNotificationObservers( ) {
95
+ NotificationCenter . default. addObserver ( forName: . didReceiveOptimizelyProjectIdChange, object: nil , queue: nil ) { ( notif) in
96
+ self . logger. d ( " Event flush triggered by datafile projectId change " )
97
+ self . flushEvents ( )
98
+ }
99
+
100
+ NotificationCenter . default. addObserver ( forName: . didReceiveOptimizelyRevisionChange, object: nil , queue: nil ) { ( notif) in
101
+ self . logger. d ( " Event flush triggered by datafile revision change " )
102
+ self . flushEvents ( )
103
+ }
104
+ }
105
+
75
106
open func dispatchEvent( event: EventForDispatch , completionHandler: DispatchCompletionHandler ? ) {
107
+ guard dataStore. count < maxQueueSize else {
108
+ let error = OptimizelyError . eventDispatchFailed ( " EventQueue is full " )
109
+ self . logger. e ( error)
110
+ completionHandler ? ( . failure( error) )
111
+ return
112
+ }
113
+
76
114
dataStore. save ( item: event)
77
115
78
- setTimer ( )
116
+ if dataStore. count >= batchSize {
117
+ flushEvents ( )
118
+ } else {
119
+ startTimer ( )
120
+ }
79
121
80
122
completionHandler ? ( . success( event. body) )
81
123
}
@@ -88,92 +130,50 @@ open class DefaultEventDispatcher: BackgroundingCallbacks, OPTEventDispatcher {
88
130
dispatcher. async {
89
131
// we don't remove anthing off of the queue unless it is successfully sent.
90
132
var failureCount = 0
91
- // if we can't batch the events because they are not from the same project or
92
- // are being sent to a different url. we set the batchSizeHolder to batchSize
93
- // and batchSize to 1 until we have sent the last batch that couldn't be batched.
94
- var batchSizeHolder = 0
95
- // the batch send count if the events failed to be batched.
96
- var sendCount = 0
97
-
98
- let failedBatch = { ( ) -> Void in
99
- // hold the batch size
100
- batchSizeHolder = self . batchSize
101
- // set it to 1 until the last batch that couldn't be batched is sent
102
- self . batchSize = 1
103
- }
104
133
105
- let resetBatch = { ( ) -> Void in
106
- if batchSizeHolder != 0 {
107
- self . batchSize = batchSizeHolder
108
- sendCount = 0
109
- batchSizeHolder = 0
134
+ func removeStoredEvents( num: Int ) {
135
+ if let removedItem = self . dataStore. removeFirstItems ( count: num) , removedItem. count > 0 {
136
+ // avoid event-log-message preparation overheads with closure-logging
137
+ self . logger. d ( { " Removed stored \( num) events starting with \( removedItem. first!) " } )
138
+ } else {
139
+ self . logger. e ( " Failed to removed \( num) events " )
110
140
}
111
-
112
141
}
142
+
113
143
while let eventsToSend: [ EventForDispatch ] = self . dataStore. getFirstItems ( count: self . batchSize) {
114
- let actualEventsSize = eventsToSend. count
115
- var eventToSend = eventsToSend. batch ( )
116
- if eventToSend != nil {
117
- // we merged the event and ready for batch
118
- // if the bacth size is not equal to the actual event size,
119
- // then setup the batchSizeHolder to be the size of the event.
120
- if actualEventsSize != self . batchSize {
121
- batchSizeHolder = self . batchSize
122
- self . batchSize = actualEventsSize
123
- sendCount = actualEventsSize - 1
124
- }
125
- } else {
126
- failedBatch ( )
127
- // just send the first one and let the rest be sent until sendCount == batchSizeHolder
128
- eventToSend = eventsToSend. first
129
- }
144
+ let ( numEvents, batched) = eventsToSend. batch ( )
130
145
131
- guard let event = eventToSend else {
132
- self . logger. e ( . eventBatchFailed)
133
- resetBatch ( )
134
- break
135
- }
146
+ guard numEvents > 0 else { break }
147
+
148
+ guard let batchEvent = batched else {
149
+ // discard an invalid event that causes batching failure
150
+ // - if an invalid event is found while batching, it batches all the valid ones before the invalid one and sends it out.
151
+ // - when trying to batch next, it finds the invalid one at the header. It discards that specific invalid one and continue batching next ones.
136
152
153
+ removeStoredEvents ( num: 1 )
154
+ continue
155
+ }
156
+
137
157
// we've exhuasted our failure count. Give up and try the next time a event
138
158
// is queued or someone calls flush.
139
159
if failureCount > DefaultEventDispatcher . MAX_FAILURE_COUNT {
140
160
self . logger. e ( . eventSendRetyFailed( failureCount) )
141
- failureCount = 0
142
- resetBatch ( )
143
161
break
144
162
}
145
-
163
+
146
164
// make the send event synchronous. enter our notify
147
165
self . notify. enter ( )
148
- self . sendEvent ( event: event ) { ( result) -> Void in
166
+ self . sendEvent ( event: batchEvent ) { ( result) -> Void in
149
167
switch result {
150
168
case . failure( let error) :
151
169
self . logger. e ( error. reason)
152
170
failureCount += 1
153
171
case . success:
154
172
// we succeeded. remove the batch size sent.
155
- if let removedItem: [ EventForDispatch ] = self . dataStore. removeFirstItems ( count: self . batchSize) {
156
- if self . batchSize == 1 && removedItem. first != event {
157
- self . logger. e ( " Removed event different from sent event " )
158
- } else {
159
- // avoid event-log-message preparation overheads with closure-logging
160
- self . logger. d ( { " Successfully sent event: \( event) " } )
161
- }
162
- } else {
163
- self . logger. e ( " Removed event nil for sent item " )
164
- }
173
+ removeStoredEvents ( num: numEvents)
174
+
165
175
// reset failureCount
166
176
failureCount = 0
167
- // did we have to send a batch one at a time?
168
- if batchSizeHolder != 0 {
169
- sendCount += 1
170
- // have we sent all the events in this batch?
171
- if sendCount == self . batchSize {
172
- resetBatch ( )
173
- }
174
- } else {
175
- // batch had batchSize items
176
- }
177
177
}
178
178
// our send is done.
179
179
self . notify. leave ( )
@@ -183,7 +183,6 @@ open class DefaultEventDispatcher: BackgroundingCallbacks, OPTEventDispatcher {
183
183
self . notify. wait ( )
184
184
}
185
185
}
186
-
187
186
}
188
187
189
188
open func sendEvent( event: EventForDispatch , completionHandler: @escaping DispatchCompletionHandler ) {
@@ -210,21 +209,18 @@ open class DefaultEventDispatcher: BackgroundingCallbacks, OPTEventDispatcher {
210
209
}
211
210
212
211
func applicationDidEnterBackground( ) {
213
- timer. performAtomic { ( timer) in
214
- timer. invalidate ( )
215
- }
216
- timer. property = nil
212
+ stopTimer ( )
217
213
218
214
flushEvents ( )
219
215
}
220
216
221
217
func applicationDidBecomeActive( ) {
222
218
if dataStore. count > 0 {
223
- setTimer ( )
219
+ startTimer ( )
224
220
}
225
221
}
226
222
227
- func setTimer ( ) {
223
+ func startTimer ( ) {
228
224
// timer is activated only for iOS10+ and non-zero interval value
229
225
guard #available( iOS 10 . 0 , tvOS 10 . 0 , * ) , timerInterval > 0 else {
230
226
flushEvents ( )
@@ -237,18 +233,22 @@ open class DefaultEventDispatcher: BackgroundingCallbacks, OPTEventDispatcher {
237
233
// should check here again
238
234
guard self . timer. property == nil else { return }
239
235
240
- self . timer. property = Timer . scheduledTimer ( withTimeInterval: self . timerInterval, repeats: true ) { ( timer ) in
236
+ self . timer. property = Timer . scheduledTimer ( withTimeInterval: self . timerInterval, repeats: true ) { _ in
241
237
self . dispatcher. async {
242
- if self . dataStore. count == 0 {
243
- self . timer. performAtomic { ( timer) in
244
- timer. invalidate ( )
245
- }
246
- self . timer. property = nil
247
- } else {
238
+ if self . dataStore. count > 0 {
248
239
self . flushEvents ( )
240
+ } else {
241
+ self . stopTimer ( )
249
242
}
250
243
}
251
244
}
252
245
}
253
246
}
247
+
248
+ func stopTimer( ) {
249
+ timer. performAtomic { ( timer) in
250
+ timer. invalidate ( )
251
+ }
252
+ timer. property = nil
253
+ }
254
254
}
0 commit comments