3
3
#if HOCKEYSDK_FEATURE_METRICS
4
4
5
5
#import " HockeySDKPrivate.h"
6
+ #import " BITHockeyManager.h"
6
7
#import " BITChannelPrivate.h"
7
8
#import " BITHockeyHelper.h"
8
9
#import " BITTelemetryContext.h"
12
13
#import " BITData.h"
13
14
#import " BITDevice.h"
14
15
#import " BITPersistencePrivate.h"
16
+ #import < libkern/OSAtomic.h>
17
+
15
18
16
19
static char *const BITDataItemsOperationsQueue = " net.hockeyapp.senderQueue" ;
17
- char *BITSafeJsonEventsString ;
20
+ char *BITTelemetryEventBuffer ;
18
21
19
22
NSString *const BITChannelBlockedNotification = @" BITChannelBlockedNotification" ;
20
23
@@ -42,7 +45,7 @@ @implementation BITChannel
42
45
43
46
- (instancetype )init {
44
47
if ((self = [super init ])) {
45
- bit_resetSafeJsonStream (&BITSafeJsonEventsString );
48
+ bit_resetEventBuffer (&BITTelemetryEventBuffer );
46
49
_dataItemCount = 0 ;
47
50
if (bit_isDebuggerAttached ()) {
48
51
_maxBatchSize = BITDebugMaxBatchSize;
@@ -80,7 +83,6 @@ - (void) registerObservers {
80
83
void (^notificationBlock)(NSNotification *note) = ^(NSNotification __unused *note) {
81
84
typeof (self) strongSelf = weakSelf;
82
85
if ([strongSelf timerIsRunning ]) {
83
- [strongSelf persistDataItemQueue ];
84
86
85
87
/* *
86
88
* From the documentation for applicationDidEnterBackground:
@@ -93,12 +95,15 @@ - (void) registerObservers {
93
95
[sharedApplication endBackgroundTask: _backgroundTask];
94
96
_backgroundTask = UIBackgroundTaskInvalid;
95
97
}];
98
+
99
+ // Do background work that will be done in it's own async queue.
100
+ [strongSelf persistDataItemQueue: &BITTelemetryEventBuffer];
96
101
}
97
102
};
98
103
self.appDidEnterBackgroundObserver = [[NSNotificationCenter defaultCenter ] addObserverForName: UIApplicationDidEnterBackgroundNotification
99
- object: nil
100
- queue: NSOperationQueue .mainQueue
101
- usingBlock: notificationBlock];
104
+ object: nil
105
+ queue: NSOperationQueue .mainQueue
106
+ usingBlock: notificationBlock];
102
107
}
103
108
}
104
109
@@ -123,59 +128,89 @@ - (BOOL)isQueueBusy {
123
128
return self.channelBlocked ;
124
129
}
125
130
126
- - (void )persistDataItemQueue {
131
+ - (void )persistDataItemQueue : ( char **) eventBuffer {
127
132
[self invalidateTimer ];
128
- if (!BITSafeJsonEventsString || strlen (BITSafeJsonEventsString) == 0 ) {
133
+
134
+ // Make sure string (which points to BITTelemetryEventBuffer) is not changed.
135
+ char *previousBuffer = NULL ;
136
+ char *newEmptyString = NULL ;
137
+ do {
138
+ newEmptyString = strdup (" " );
139
+ previousBuffer = *eventBuffer;
140
+
141
+ // This swaps pointers and makes sure eventBuffer now has the balue of newEmptyString.
142
+ if (OSAtomicCompareAndSwapPtr (previousBuffer, newEmptyString, (void *)eventBuffer)) {
143
+ @synchronized (self) {
144
+ self.dataItemCount = 0 ;
145
+ }
146
+ break ;
147
+ }
148
+ } while (true );
149
+
150
+ // Nothing to persist, freeing memory and existing.
151
+ if (!previousBuffer || strlen (previousBuffer) == 0 ) {
152
+ free (previousBuffer);
129
153
return ;
130
154
}
131
155
132
- NSData *bundle = [NSData dataWithBytes: BITSafeJsonEventsString length: strlen (BITSafeJsonEventsString)];
156
+ // Persist the data
157
+ NSData *bundle = [NSData dataWithBytes: previousBuffer length: strlen (previousBuffer)];
133
158
[self .persistence persistBundle: bundle];
159
+ free (previousBuffer);
134
160
135
161
// Reset both, the async-signal-safe and item counter.
136
162
[self resetQueue ];
137
163
}
138
164
165
+ // Resets the event buffer and count of events in the queue.
139
166
- (void )resetQueue {
140
- bit_resetSafeJsonStream (&BITSafeJsonEventsString);
141
- self.dataItemCount = 0 ;
167
+ @synchronized (self) {
168
+ bit_resetEventBuffer (&BITTelemetryEventBuffer);
169
+ self.dataItemCount = 0 ;
170
+ }
142
171
}
143
172
144
173
#pragma mark - Adding to queue
145
174
146
175
- (void )enqueueTelemetryItem : (BITTelemetryData *)item {
147
176
148
177
if (!item) {
149
- // Case 1: Item is nil: Do not enqueue item and abort operation
178
+
179
+ // Item is nil: Do not enqueue item and abort operation.
150
180
BITHockeyLogWarning (@" WARNING: TelemetryItem was nil." );
151
181
return ;
152
182
}
153
183
184
+ // First assigning self to weakSelf and then assigning this to strongSelf in the block is not very intuitive, this
185
+ // blog post explains it very well: https://dhoerl.wordpress.com/2013/04/23/i-finally-figured-out-weakself-and-strongself/
154
186
__weak typeof (self) weakSelf = self;
155
187
dispatch_async (self.dataItemsOperations , ^{
156
- typeof (self) strongSelf = weakSelf;
157
188
189
+ typeof (self) strongSelf = weakSelf;
158
190
if (strongSelf.isQueueBusy ) {
159
- // Case 2: Channel is in blocked state: Trigger sender, start timer to check after again after a while and abort operation.
191
+
192
+ // Case 1: Channel is in blocked state: Trigger sender, start timer to check after again after a while and abort operation.
160
193
BITHockeyLogDebug (@" INFO: The channel is saturated. %@ was dropped." , item.debugDescription );
161
194
if (![strongSelf timerIsRunning ]) {
162
195
[strongSelf startTimer ];
163
196
}
164
197
return ;
165
198
}
166
199
167
- // Enqueue item
168
- NSDictionary *dict = [self dictionaryForTelemetryData: item];
169
- [strongSelf appendDictionaryToJsonStream: dict];
170
-
171
- if (strongSelf.dataItemCount >= self.maxBatchSize ) {
172
- // Case 3: Max batch count has been reached, so write queue to disk and delete all items.
173
- [strongSelf persistDataItemQueue ];
174
-
175
- } else if (strongSelf.dataItemCount == 1 ) {
176
- // Case 4: It is the first item, let's start the timer.
177
- if (![strongSelf timerIsRunning ]) {
178
- [strongSelf startTimer ];
200
+ // Enqueue item.
201
+ @synchronized (self) {
202
+ NSDictionary *dict = [strongSelf dictionaryForTelemetryData: item];
203
+ [strongSelf appendDictionaryToEventBuffer: dict];
204
+ if (strongSelf.dataItemCount >= strongSelf.maxBatchSize ) {
205
+
206
+ // Case 2: Max batch count has been reached, so write queue to disk and delete all items.
207
+ [strongSelf persistDataItemQueue: &BITTelemetryEventBuffer];
208
+ } else if (strongSelf.dataItemCount > 0 ) {
209
+
210
+ // Case 3: It is the first item, let's start the timer.
211
+ if (![strongSelf timerIsRunning ]) {
212
+ [strongSelf startTimer ];
213
+ }
179
214
}
180
215
}
181
216
});
@@ -223,39 +258,72 @@ - (NSString *)serializeDictionaryToJSONString:(NSDictionary *)dictionary {
223
258
224
259
#pragma mark JSON Stream
225
260
226
- - (void )appendDictionaryToJsonStream : (NSDictionary *)dictionary {
261
+ - (void )appendDictionaryToEventBuffer : (NSDictionary *)dictionary {
227
262
if (dictionary) {
228
263
NSString *string = [self serializeDictionaryToJSONString: dictionary];
229
264
230
265
// Since we can't persist every event right away, we write it to a simple C string.
231
266
// This can then be written to disk by a signal handler in case of a crash.
232
- bit_appendStringToSafeJsonStream (string, &(BITSafeJsonEventsString));
233
- self.dataItemCount += 1 ;
267
+ @synchronized (self) {
268
+ bit_appendStringToEventBuffer (string, &BITTelemetryEventBuffer);
269
+ self.dataItemCount += 1 ;
270
+ }
234
271
}
235
272
}
236
273
237
- void bit_appendStringToSafeJsonStream (NSString *string, char **jsonString) {
238
- if (jsonString == NULL ) { return ; }
274
+ void bit_appendStringToEventBuffer (NSString *string, char **eventBuffer) {
275
+ if (eventBuffer == NULL ) {
276
+ return ;
277
+ }
239
278
240
- if (!string) { return ; }
279
+ if (!string) {
280
+ return ;
281
+ }
241
282
242
- if (*jsonString == NULL || strlen (*jsonString ) == 0 ) {
243
- bit_resetSafeJsonStream (jsonString );
283
+ if (*eventBuffer == NULL || strlen (*eventBuffer ) == 0 ) {
284
+ bit_resetEventBuffer (eventBuffer );
244
285
}
245
286
246
- if (string.length == 0 ) { return ; }
287
+ if (string.length == 0 ) {
288
+ return ;
289
+ }
247
290
248
- char *new_string = NULL ;
249
- // Concatenate old string with new JSON string and add a comma.
250
- asprintf (&new_string, " %s%.*s \n " , *jsonString, (int )MIN (string.length , (NSUInteger )INT_MAX), string.UTF8String );
251
- free (*jsonString);
252
- *jsonString = new_string;
291
+ do {
292
+ char *newBuffer = NULL ;
293
+ char *previousBuffer = *eventBuffer;
294
+
295
+ // Concatenate old string with new JSON string and add a comma.
296
+ asprintf (&newBuffer, " %s%.*s \n " , previousBuffer, (int )MIN (string.length , (NSUInteger )INT_MAX), string.UTF8String );
297
+
298
+ // Compare newBuffer and previousBuffer. If they point to the same address, we are safe to use them.
299
+ if (OSAtomicCompareAndSwapPtr (previousBuffer, newBuffer, (void *)eventBuffer)) {
300
+
301
+ // Free the intermediate pointer.
302
+ free (previousBuffer);
303
+ return ;
304
+ } else {
305
+
306
+ // newBuffer has been changed by another thread.
307
+ free (newBuffer);
308
+ }
309
+ } while (true );
253
310
}
254
311
255
- void bit_resetSafeJsonStream (char **string) {
256
- if (!string) { return ; }
257
- free (*string);
258
- *string = strdup (" " );
312
+ void bit_resetEventBuffer (char **eventBuffer) {
313
+ if (!eventBuffer) { return ; }
314
+
315
+ char *newEmptyString = NULL ;
316
+ char *prevString = NULL ;
317
+ do {
318
+ prevString = *eventBuffer;
319
+ newEmptyString = strdup (" " );
320
+
321
+ // Compare pointers to strings to make sure we are still threadsafe!
322
+ if (OSAtomicCompareAndSwapPtr (prevString, newEmptyString, (void *)eventBuffer)) {
323
+ free (prevString);
324
+ return ;
325
+ }
326
+ } while (true );
259
327
}
260
328
261
329
#pragma mark - Batching
@@ -279,28 +347,26 @@ -(BOOL)timerIsRunning {
279
347
}
280
348
281
349
- (void )startTimer {
282
- // Reset timer, if it is already running
350
+
351
+ // Reset timer, if it is already running.
283
352
if ([self timerIsRunning ]) {
284
353
[self invalidateTimer ];
285
354
}
286
355
287
356
self.timerSource = dispatch_source_create (DISPATCH_SOURCE_TYPE_TIMER, 0 , 0 , self.dataItemsOperations );
288
357
dispatch_source_set_timer ((dispatch_source_t )self.timerSource , dispatch_walltime (NULL , NSEC_PER_SEC * self.batchInterval ), 1ull * NSEC_PER_SEC, 1ull * NSEC_PER_SEC);
289
-
290
358
__weak typeof (self) weakSelf = self;
291
359
dispatch_source_set_event_handler ((dispatch_source_t )self.timerSource , ^{
292
360
typeof (self) strongSelf = weakSelf;
293
-
294
- if (strongSelf) {
361
+ if (strongSelf) {
295
362
if (strongSelf.dataItemCount > 0 ) {
296
- [strongSelf persistDataItemQueue ];
363
+ [strongSelf persistDataItemQueue: &BITTelemetryEventBuffer ];
297
364
} else {
298
365
strongSelf.channelBlocked = NO ;
299
366
}
300
367
[strongSelf invalidateTimer ];
301
368
}
302
369
});
303
-
304
370
dispatch_resume ((dispatch_source_t )self.timerSource );
305
371
}
306
372
0 commit comments