Skip to content

Commit 372d7e5

Browse files
authored
Limit the number of unsent reports on disk to prevent disk filling (#7619)
1 parent bda1a99 commit 372d7e5

File tree

6 files changed

+322
-26
lines changed

6 files changed

+322
-26
lines changed

Crashlytics/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Unreleased
2-
- [added] Added a new API checkAndUpdateUnsentReportsWithCompletion for updating the crash report from the previous run of the app if, for example, the developer wants to implement a feedback dialog to ask end-users for more information. Unsent Crashlytics Reports have familiar methods like setting custom keys and logs.
2+
- [added] Added a new API checkAndUpdateUnsentReportsWithCompletion for updating the crash report from the previous run of the app if, for example, the developer wants to implement a feedback dialog to ask end-users for more information. Unsent Crashlytics Reports have familiar methods like setting custom keys and logs (#7503).
3+
- [changed] Added a limit to the number of unsent reports on disk to prevent disk filling up when automatic data collection is off. Developers can ensure this limit is never reached by calling send/deleteUnsentReports every run (#7619).
34

45
# v7.7.0
56
- [added] Added a new API to allow for bulk logging of custom keys and values (#7302).

Crashlytics/Crashlytics/Controllers/FIRCLSExistingReportManager.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ NS_ASSUME_NONNULL_BEGIN
2121
@class FIRCLSDataCollectionToken;
2222
@class FIRCrashlyticsReport;
2323

24+
FOUNDATION_EXPORT NSUInteger const FIRCLSMaxUnsentReports;
25+
2426
@interface FIRCLSExistingReportManager : NSObject
2527

2628
/**
@@ -60,6 +62,10 @@ NS_ASSUME_NONNULL_BEGIN
6062
* new report for this run of the app has been created. Any
6163
* reports in ExistingReportManager will be uploaded or deleted
6264
* and we don't want to do that for the current run of the app.
65+
*
66+
* If there are over MAX_UNSENT_REPORTS valid reports, this will delete them.
67+
*
68+
* This methods is slow and should be called only once.
6369
*/
6470
- (void)collectExistingReports;
6571

Crashlytics/Crashlytics/Controllers/FIRCLSExistingReportManager.m

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,29 @@
1717
#import "Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h"
1818
#import "Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.h"
1919
#import "Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionToken.h"
20+
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
2021
#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
2122
#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
2223
#import "Crashlytics/Crashlytics/Private/FIRCrashlyticsReport_Private.h"
2324
#import "Crashlytics/Crashlytics/Public/FirebaseCrashlytics/FIRCrashlyticsReport.h"
2425

26+
// This value should stay in sync with the Android SDK
27+
NSUInteger const FIRCLSMaxUnsentReports = 4;
28+
2529
@interface FIRCLSExistingReportManager ()
2630

2731
@property(nonatomic, strong) FIRCLSFileManager *fileManager;
28-
@property(nonatomic, strong) NSOperationQueue *operationQueue;
2932
@property(nonatomic, strong) FIRCLSReportUploader *reportUploader;
33+
@property(nonatomic, strong) NSOperationQueue *operationQueue;
3034

3135
// This list of active reports excludes the brand new active report that will be created this run of
3236
// the app.
3337
@property(nonatomic, strong) NSArray *existingUnemptyActiveReportPaths;
3438
@property(nonatomic, strong) NSArray *processingReportPaths;
3539
@property(nonatomic, strong) NSArray *preparedReportPaths;
3640

41+
@property(nonatomic, strong) FIRCLSInternalReport *newestInternalReport;
42+
3743
@end
3844

3945
@implementation FIRCLSExistingReportManager
@@ -52,15 +58,16 @@ - (instancetype)initWithManagerData:(FIRCLSManagerData *)managerData
5258
return self;
5359
}
5460

55-
NSInteger compareOlder(FIRCLSInternalReport *reportA,
61+
NSInteger compareNewer(FIRCLSInternalReport *reportA,
5662
FIRCLSInternalReport *reportB,
5763
void *context) {
58-
return [reportA.dateCreated compare:reportB.dateCreated];
64+
// Compare naturally sorts with oldest first, so swap A and B
65+
return [reportB.dateCreated compare:reportA.dateCreated];
5966
}
6067

6168
- (void)collectExistingReports {
6269
self.existingUnemptyActiveReportPaths =
63-
[self getUnemptyExistingActiveReportsAndDeleteEmpty:self.fileManager.activePathContents];
70+
[self getUnsentActiveReportsAndDeleteEmptyOrOld:self.fileManager.activePathContents];
6471
self.processingReportPaths = self.fileManager.processingPathContents;
6572
self.preparedReportPaths = self.fileManager.preparedPathContents;
6673
}
@@ -70,43 +77,71 @@ - (FIRCrashlyticsReport *)newestUnsentReport {
7077
return nil;
7178
}
7279

73-
NSMutableArray<NSString *> *allReportPaths =
74-
[NSMutableArray arrayWithArray:self.existingUnemptyActiveReportPaths];
80+
return [[FIRCrashlyticsReport alloc] initWithInternalReport:self.newestInternalReport];
81+
}
82+
83+
- (NSUInteger)unsentReportsCount {
84+
// There are nuances about why we only count active reports.
85+
// See the header comment for more information.
86+
return self.existingUnemptyActiveReportPaths.count;
87+
}
7588

89+
/*
90+
* This has the side effect of deleting any reports over the max, starting with oldest reports.
91+
*/
92+
- (NSArray<NSString *> *)getUnsentActiveReportsAndDeleteEmptyOrOld:(NSArray *)reportPaths {
7693
NSMutableArray<FIRCLSInternalReport *> *validReports = [NSMutableArray array];
77-
for (NSString *path in allReportPaths) {
94+
for (NSString *path in reportPaths) {
7895
FIRCLSInternalReport *_Nullable report = [FIRCLSInternalReport reportWithPath:path];
7996
if (!report) {
8097
continue;
8198
}
99+
100+
// Delete reports without any crashes or non-fatals
101+
if (![report hasAnyEvents]) {
102+
[self.operationQueue addOperationWithBlock:^{
103+
[self.fileManager removeItemAtPath:path];
104+
}];
105+
continue;
106+
}
107+
82108
[validReports addObject:report];
83109
}
84110

85-
[validReports sortUsingFunction:compareOlder context:nil];
111+
if (validReports.count == 0) {
112+
return @[];
113+
}
86114

87-
FIRCLSInternalReport *_Nullable internalReport = [validReports lastObject];
88-
return [[FIRCrashlyticsReport alloc] initWithInternalReport:internalReport];
89-
}
115+
// Sort with the newest at the end
116+
[validReports sortUsingFunction:compareNewer context:nil];
90117

91-
- (NSUInteger)unsentReportsCount {
92-
// There are nuances about why we only count active reports.
93-
// See the header comment for more information.
94-
return self.existingUnemptyActiveReportPaths.count;
95-
}
118+
// Set our report for updating in checkAndUpdateUnsentReports
119+
self.newestInternalReport = [validReports firstObject];
96120

97-
- (NSArray *)getUnemptyExistingActiveReportsAndDeleteEmpty:(NSArray *)reportPaths {
98-
NSMutableArray *unemptyReports = [NSMutableArray array];
99-
for (NSString *path in reportPaths) {
100-
FIRCLSInternalReport *report = [FIRCLSInternalReport reportWithPath:path];
101-
if ([report hasAnyEvents]) {
102-
[unemptyReports addObject:path];
103-
} else {
121+
// Delete any reports above the limit, starting with the oldest
122+
// which should be at the start of the array.
123+
if (validReports.count > FIRCLSMaxUnsentReports) {
124+
NSUInteger deletingCount = validReports.count - FIRCLSMaxUnsentReports;
125+
FIRCLSInfoLog(@"Deleting %lu unsent reports over the limit of %lu to prevent disk space from "
126+
@"filling up. To prevent this make sure to call send/deleteUnsentReports.",
127+
deletingCount, FIRCLSMaxUnsentReports);
128+
}
129+
130+
// Not that validReports is sorted, delete any reports at indices > MAX_UNSENT_REPORTS, and
131+
// collect the rest of the reports to return.
132+
NSMutableArray<NSString *> *validReportPaths = [NSMutableArray array];
133+
for (int i = 0; i < validReports.count; i++) {
134+
if (i >= FIRCLSMaxUnsentReports) {
104135
[self.operationQueue addOperationWithBlock:^{
136+
NSString *path = [[validReports objectAtIndex:i] path];
105137
[self.fileManager removeItemAtPath:path];
106138
}];
139+
} else {
140+
[validReportPaths addObject:[[validReports objectAtIndex:i] path]];
107141
}
108142
}
109-
return unemptyReports;
143+
144+
return validReportPaths;
110145
}
111146

112147
- (void)sendUnsentReportsWithToken:(FIRCLSDataCollectionToken *)dataCollectionToken
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef FIRCLSExistingReportManager_Private_h
16+
#define FIRCLSExistingReportManager_Private_h
17+
18+
#import "Crashlytics/Crashlytics/Controllers/FIRCLSExistingReportManager.h"
19+
20+
/**
21+
* Visible for testing
22+
*/
23+
@interface FIRCLSExistingReportManager (Private)
24+
25+
@property(nonatomic, strong) NSOperationQueue *operationQueue;
26+
27+
@property(nonatomic, strong) NSArray *existingUnemptyActiveReportPaths;
28+
@property(nonatomic, strong) NSArray *processingReportPaths;
29+
@property(nonatomic, strong) NSArray *preparedReportPaths;
30+
31+
@end
32+
33+
#endif /* FIRCLSExistingReportManager_Private_h */

0 commit comments

Comments
 (0)