Skip to content

Commit bc9c95b

Browse files
Release 6.28.0 - GDT cherry pick of #6010 and #6032 (#6035)
* [GDTCORUploadCoordinator startTimer]: don't restart timer if already started (#6032) * [GDTCORUploadCoordinator startTimer]: don't restart timer if already started * style * `GDTCORUploadCoordinator.timer` - nullable * GDTCORUploadCoordinator test utils thread safety. * GDTCORFlatFileStorage: keep not expired events when expired batch removed (#6010) * GDTCORFlatFileStorage: expiration clean up tests * GDTCORFlatFileStorage: keep not expired events when expired batch removed * Cleanup * Cleanup * Comments * Comments * Remove additional delays in tests * style * Changelog * Changelog: remove 7.0.1
1 parent b6e6540 commit bc9c95b

File tree

5 files changed

+204
-138
lines changed

5 files changed

+204
-138
lines changed

GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m

Lines changed: 78 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h"
2828
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadCoordinator.h"
2929

30+
NS_ASSUME_NONNULL_BEGIN
31+
3032
/** A library data key this class uses to track batchIDs. */
3133
static NSString *const gBatchIDCounterKey = @"GDTCORFlatFileStorageBatchIDCounter";
3234

@@ -225,52 +227,7 @@ - (void)removeBatchWithID:(nonnull NSNumber *)batchID
225227
deleteEvents:(BOOL)deleteEvents
226228
onComplete:(void (^_Nullable)(void))onComplete {
227229
dispatch_async(_storageQueue, ^{
228-
NSError *error;
229-
NSArray<NSString *> *batchDirPaths = [self batchDirPathsForBatchID:batchID error:&error];
230-
231-
if (batchDirPaths == nil) {
232-
if (onComplete) {
233-
onComplete();
234-
}
235-
return;
236-
}
237-
238-
NSFileManager *fileManager = [NSFileManager defaultManager];
239-
240-
void (^removeBatchDir)(NSString *batchDirPath) = ^(NSString *batchDirPath) {
241-
NSError *error;
242-
if ([fileManager removeItemAtPath:batchDirPath error:&error]) {
243-
GDTCORLogDebug(@"Batch removed at path: %@", batchDirPath);
244-
} else {
245-
GDTCORLogDebug(@"Failed to remove batch at path: %@", batchDirPath);
246-
}
247-
};
248-
249-
for (NSString *batchDirPath in batchDirPaths) {
250-
if (deleteEvents) {
251-
removeBatchDir(batchDirPath);
252-
} else {
253-
NSString *batchDirName = [batchDirPath lastPathComponent];
254-
NSDictionary<NSString *, id> *components = [self batchComponentsFromFilename:batchDirName];
255-
NSNumber *target = components[kGDTCORBatchComponentsTargetKey];
256-
NSString *destinationPath = [[GDTCORFlatFileStorage eventDataStoragePath]
257-
stringByAppendingPathComponent:target.stringValue];
258-
259-
// `- [NSFileManager moveItemAtPath:toPath:error:] method fails if an item by the
260-
// destination path already exists (which usually is the case for the current method). Move
261-
// the events one by one instead.
262-
if ([self moveContentsOfDirectoryAtPath:batchDirPath to:destinationPath error:&error]) {
263-
GDTCORLogDebug(@"Batched events at path: %@ moved back to the storage: %@", batchDirPath,
264-
destinationPath);
265-
} else {
266-
GDTCORLogDebug(@"Error encountered whilst moving events back: %@", error);
267-
}
268-
269-
// Even if not all events where moved back to the storage, there is not much can be done at
270-
// this point, so cleanup batch directory now to avoid clattering.
271-
removeBatchDir(batchDirPath);
272-
}
273-
}
230+
[self syncThreadUnsafeRemoveBatchWithID:batchID deleteEvents:deleteEvents];
274231

275232
if (onComplete) {
276233
onComplete();
@@ -384,45 +341,48 @@ - (void)hasEventsForTarget:(GDTCORTarget)target onComplete:(void (^)(BOOL hasEve
384341

385342
- (void)checkForExpirations {
386343
dispatch_async(_storageQueue, ^{
387-
NSMutableSet<NSString *> *pathsToDelete = [[NSMutableSet alloc] init];
388344
GDTCORLogDebug(@"%@", @"Checking for expired events and batches");
389345
NSTimeInterval now = [NSDate date].timeIntervalSince1970;
390346
NSFileManager *fileManager = [NSFileManager defaultManager];
391-
NSString *eventDataPath = [GDTCORFlatFileStorage eventDataStoragePath];
392-
NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtPath:eventDataPath];
393-
NSString *path;
394-
while ((path = [enumerator nextObject])) {
395-
NSString *fileName = [path lastPathComponent];
396-
NSDictionary<NSString *, id> *eventComponents = [self eventComponentsFromFilename:fileName];
397-
NSDate *expirationDate = eventComponents[kGDTCOREventComponentsExpirationKey];
398-
if (expirationDate != nil && expirationDate.timeIntervalSince1970 < now) {
399-
[pathsToDelete addObject:[eventDataPath stringByAppendingPathComponent:path]];
400-
}
401-
}
402347

403-
// TODO: Events from expired batches with not expired events must be moved back to queue to
404-
// avoid data loss.
405348
// TODO: Storage may not have enough context to remove batches because a batch may be being
406349
// uploaded but the storage has not context of it.
350+
351+
// Find expired batches and move their events back to the main storage.
352+
// If a batch contains expired events they are expected to be removed further in the method
353+
// together with other expired events in the main storage.
407354
NSString *batchDataPath = [GDTCORFlatFileStorage batchDataStoragePath];
408355
NSArray<NSString *> *batchDataPaths = [fileManager contentsOfDirectoryAtPath:batchDataPath
409356
error:nil];
410357
for (NSString *path in batchDataPaths) {
411358
NSString *fileName = [path lastPathComponent];
412359
NSDictionary<NSString *, id> *batchComponents = [self batchComponentsFromFilename:fileName];
413360
NSDate *expirationDate = batchComponents[kGDTCORBatchComponentsExpirationKey];
414-
if (expirationDate != nil && expirationDate.timeIntervalSince1970 < now) {
415-
[pathsToDelete addObject:[batchDataPath stringByAppendingPathComponent:path]];
361+
NSNumber *batchID = batchComponents[kGDTCORBatchComponentsBatchIDKey];
362+
if (expirationDate != nil && expirationDate.timeIntervalSince1970 < now && batchID != nil) {
363+
NSNumber *batchID = batchComponents[kGDTCORBatchComponentsBatchIDKey];
364+
// Move all events from the expired batch back to the main storage.
365+
[self syncThreadUnsafeRemoveBatchWithID:batchID deleteEvents:NO];
416366
}
417367
}
418368

419-
for (NSString *path in pathsToDelete) {
420-
NSError *error;
421-
[fileManager removeItemAtPath:path error:&error];
422-
if (error != nil) {
423-
GDTCORLogDebug(@"There was an error deleting an expired item: %@", error);
424-
} else {
425-
GDTCORLogDebug(@"Item deleted because it expired: %@", path);
369+
// Find expired events and remove them from the storage.
370+
NSString *eventDataPath = [GDTCORFlatFileStorage eventDataStoragePath];
371+
NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtPath:eventDataPath];
372+
NSString *path;
373+
while ((path = [enumerator nextObject])) {
374+
NSString *fileName = [path lastPathComponent];
375+
NSDictionary<NSString *, id> *eventComponents = [self eventComponentsFromFilename:fileName];
376+
NSDate *expirationDate = eventComponents[kGDTCOREventComponentsExpirationKey];
377+
if (expirationDate != nil && expirationDate.timeIntervalSince1970 < now) {
378+
NSString *pathToDelete = [eventDataPath stringByAppendingPathComponent:path];
379+
NSError *error;
380+
[fileManager removeItemAtPath:pathToDelete error:&error];
381+
if (error != nil) {
382+
GDTCORLogDebug(@"There was an error deleting an expired item: %@", error);
383+
} else {
384+
GDTCORLogDebug(@"Item deleted because it expired: %@", pathToDelete);
385+
}
426386
}
427387
}
428388
});
@@ -539,6 +499,53 @@ - (BOOL)moveContentsOfDirectoryAtPath:(NSString *)sourcePath
539499
}
540500
}
541501

502+
- (void)syncThreadUnsafeRemoveBatchWithID:(nonnull NSNumber *)batchID
503+
deleteEvents:(BOOL)deleteEvents {
504+
NSError *error;
505+
NSArray<NSString *> *batchDirPaths = [self batchDirPathsForBatchID:batchID error:&error];
506+
507+
if (batchDirPaths == nil) {
508+
return;
509+
}
510+
511+
NSFileManager *fileManager = [NSFileManager defaultManager];
512+
513+
void (^removeBatchDir)(NSString *batchDirPath) = ^(NSString *batchDirPath) {
514+
NSError *error;
515+
if ([fileManager removeItemAtPath:batchDirPath error:&error]) {
516+
GDTCORLogDebug(@"Batch removed at path: %@", batchDirPath);
517+
} else {
518+
GDTCORLogDebug(@"Failed to remove batch at path: %@", batchDirPath);
519+
}
520+
};
521+
522+
for (NSString *batchDirPath in batchDirPaths) {
523+
if (deleteEvents) {
524+
removeBatchDir(batchDirPath);
525+
} else {
526+
NSString *batchDirName = [batchDirPath lastPathComponent];
527+
NSDictionary<NSString *, id> *components = [self batchComponentsFromFilename:batchDirName];
528+
NSNumber *target = components[kGDTCORBatchComponentsTargetKey];
529+
NSString *destinationPath = [[GDTCORFlatFileStorage eventDataStoragePath]
530+
stringByAppendingPathComponent:target.stringValue];
531+
532+
// `- [NSFileManager moveItemAtPath:toPath:error:]` method fails if an item by the
533+
// destination path already exists (which usually is the case for the current method). Move
534+
// the events one by one instead.
535+
if ([self moveContentsOfDirectoryAtPath:batchDirPath to:destinationPath error:&error]) {
536+
GDTCORLogDebug(@"Batched events at path: %@ moved back to the storage: %@", batchDirPath,
537+
destinationPath);
538+
} else {
539+
GDTCORLogDebug(@"Error encountered whilst moving events back: %@", error);
540+
}
541+
542+
// Even if not all events where moved back to the storage, there is not much can be done at
543+
// this point, so cleanup batch directory now to avoid clattering.
544+
removeBatchDir(batchDirPath);
545+
}
546+
}
547+
}
548+
542549
#pragma mark - Private helper methods
543550

544551
+ (NSString *)eventDataStoragePath {
@@ -766,3 +773,5 @@ - (void)appWillTerminate:(GDTCORApplication *)application {
766773
}
767774

768775
@end
776+
777+
NS_ASSUME_NONNULL_END

GoogleDataTransport/GDTCORLibrary/GDTCORUploadCoordinator.m

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,16 @@ - (void)forceUploadForTarget:(GDTCORTarget)target {
6363
*/
6464
- (void)startTimer {
6565
dispatch_async(_coordinationQueue, ^{
66+
if (self->_timer) {
67+
// The timer has been already started.
68+
return;
69+
}
70+
6671
self->_timer =
6772
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self->_coordinationQueue);
6873
dispatch_source_set_timer(self->_timer, DISPATCH_TIME_NOW, self->_timerInterval,
6974
self->_timerLeeway);
75+
7076
dispatch_source_set_event_handler(self->_timer, ^{
7177
if (![[GDTCORApplication sharedApplication] isRunningInBackground]) {
7278
GDTCORUploadConditions conditions = [self uploadConditions];
@@ -83,6 +89,7 @@ - (void)startTimer {
8389
- (void)stopTimer {
8490
if (_timer) {
8591
dispatch_source_cancel(_timer);
92+
_timer = nil;
8693
}
8794
}
8895

GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadCoordinator.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ NS_ASSUME_NONNULL_BEGIN
3939
@property(nonatomic, readonly) dispatch_queue_t coordinationQueue;
4040

4141
/** A timer that will causes regular checks for events to upload. */
42-
@property(nonatomic, readonly) dispatch_source_t timer;
42+
@property(nonatomic, readonly, nullable) dispatch_source_t timer;
4343

4444
/** The interval the timer will fire. */
4545
@property(nonatomic, readonly) uint64_t timerInterval;

GoogleDataTransport/GDTCORTests/Common/Categories/GDTCORUploadCoordinator+Testing.m

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,20 @@ - (void)reset {
3131

3232
- (void)setTimerInterval:(uint64_t)timerInterval {
3333
[self setValue:@(timerInterval) forKey:@"_timerInterval"];
34-
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, timerInterval, self.timerLeeway);
34+
35+
dispatch_source_t timer = self.timer;
36+
if (timer) {
37+
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, timerInterval, self.timerLeeway);
38+
}
3539
}
3640

3741
- (void)setTimerLeeway:(uint64_t)timerLeeway {
3842
[self setValue:@(timerLeeway) forKey:@"_timerLeeway"];
39-
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, self.timerInterval, timerLeeway);
43+
44+
dispatch_source_t timer = self.timer;
45+
if (timer) {
46+
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, self.timerInterval, timerLeeway);
47+
}
4048
}
4149

4250
@end

0 commit comments

Comments
 (0)