Skip to content

Commit 26530fe

Browse files
authored
Send envelopes that cannot be cached to disk (#4294)
1 parent 90e653b commit 26530fe

13 files changed

+230
-57
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Features
66

77
- Added `thermal_state` to device context (#4305)
8+
- Send envelopes that cannot be cached to disk (#4294)
89

910
### Refactoring
1011

Sentry.xcodeproj/project.pbxproj

+4
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,7 @@
769769
8EE017A126704CD500470616 /* SentryUIViewControllerPerformanceTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EA1ED0E2669152F00E62B98 /* SentryUIViewControllerPerformanceTrackerTests.swift */; };
770770
8F0D6AA22B04115A00D048B1 /* SentryInstallation+Test.h in Sources */ = {isa = PBXBuildFile; fileRef = 8F0D6AA12B040A0100D048B1 /* SentryInstallation+Test.h */; };
771771
8F73BC312B02B87E00C3CEF4 /* SentryInstallationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F73BC302B02B87E00C3CEF4 /* SentryInstallationTests.swift */; };
772+
92136D672C9D7660002A9FB8 /* SentryNSURLRequestBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92136D662C9D765D002A9FB8 /* SentryNSURLRequestBuilderTests.swift */; };
772773
92672BB629C9A2A9006B021C /* SentryBreadcrumb+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 92672BB529C9A2A9006B021C /* SentryBreadcrumb+Private.h */; };
773774
9286059529A5096600F96038 /* SentryGeo.h in Headers */ = {isa = PBXBuildFile; fileRef = 9286059429A5096600F96038 /* SentryGeo.h */; settings = {ATTRIBUTES = (Public, ); }; };
774775
9286059729A5098900F96038 /* SentryGeo.m in Sources */ = {isa = PBXBuildFile; fileRef = 9286059629A5098900F96038 /* SentryGeo.m */; };
@@ -1836,6 +1837,7 @@
18361837
8F0D6AA12B040A0100D048B1 /* SentryInstallation+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryInstallation+Test.h"; sourceTree = "<group>"; };
18371838
8F73BC302B02B87E00C3CEF4 /* SentryInstallationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryInstallationTests.swift; sourceTree = "<group>"; };
18381839
8FF94DF22B06A24C00BCD650 /* SentryCrash+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryCrash+Test.h"; sourceTree = "<group>"; };
1840+
92136D662C9D765D002A9FB8 /* SentryNSURLRequestBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryNSURLRequestBuilderTests.swift; sourceTree = "<group>"; };
18391841
92672BB529C9A2A9006B021C /* SentryBreadcrumb+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SentryBreadcrumb+Private.h"; path = "include/HybridPublic/SentryBreadcrumb+Private.h"; sourceTree = "<group>"; };
18401842
9286059429A5096600F96038 /* SentryGeo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryGeo.h; path = Public/SentryGeo.h; sourceTree = "<group>"; };
18411843
9286059629A5098900F96038 /* SentryGeo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryGeo.m; sourceTree = "<group>"; };
@@ -3037,6 +3039,7 @@
30373039
7BBD18AF24517E5D00427C76 /* Networking */ = {
30383040
isa = PBXGroup;
30393041
children = (
3042+
92136D662C9D765D002A9FB8 /* SentryNSURLRequestBuilderTests.swift */,
30403043
7BBD18B124517FE800427C76 /* RateLimits */,
30413044
7BC8523A2458849E005A70F0 /* SentryDataCategoryMapperTests.swift */,
30423045
7B58816627FC5D790098B121 /* SentryDiscardReasonMapperTests.swift */,
@@ -4983,6 +4986,7 @@
49834986
D86130122BB563FD004C0F5E /* SentrySessionReplayIntegrationTests.swift in Sources */,
49844987
7BFC16BA2524D4AF00FF6266 /* SentryMessage+Equality.m in Sources */,
49854988
7B4260342630315C00B36EDD /* SampleError.swift in Sources */,
4989+
92136D672C9D7660002A9FB8 /* SentryNSURLRequestBuilderTests.swift in Sources */,
49864990
D855B3E827D652AF00BCED76 /* SentryCoreDataTrackingIntegrationTest.swift in Sources */,
49874991
D855AD62286ED6A4002573E1 /* SentryCrashTests.m in Sources */,
49884992
D8AFC0012BD252B900118BE1 /* SentryOnDemandReplayTests.swift in Sources */,

SentryTestUtils/TestClient.swift

+13
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,23 @@ public class TestFileManager: SentryFileManager {
144144
var storeTimestampLastInForegroundInvocations: Int = 0
145145
var deleteTimestampLastInForegroundInvocations: Int = 0
146146

147+
public var storeEnvelopeInvocations = Invocations<SentryEnvelope>()
148+
public var storeEnvelopePath: String?
149+
public var storeEnvelopePathNil: Bool = false
150+
147151
public init(options: Options) throws {
148152
try super.init(options: options, dispatchQueueWrapper: TestSentryDispatchQueueWrapper())
149153
}
150154

155+
public override func store(_ envelope: SentryEnvelope) -> String? {
156+
storeEnvelopeInvocations.record(envelope)
157+
if storeEnvelopePathNil {
158+
return nil
159+
} else {
160+
return storeEnvelopePath ?? super.store(envelope)
161+
}
162+
}
163+
151164
public var deleteOldEnvelopeItemsInvocations = Invocations<Void>()
152165
public override func deleteOldEnvelopeItems() {
153166
deleteOldEnvelopeItemsInvocations.record(Void())

Sources/Sentry/SentryFileManager.m

+1
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ - (nullable NSString *)storeEnvelope:(SentryEnvelope *)envelope
319319

320320
if (![self writeData:envelopeData toPath:path]) {
321321
SENTRY_LOG_WARN(@"Failed to store envelope.");
322+
return nil;
322323
}
323324

324325
[self handleEnvelopesLimit];

Sources/Sentry/SentryHttpTransport.m

+46-21
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ @interface SentryHttpTransport ()
5353
@property (nonatomic, strong)
5454
NSMutableDictionary<NSString *, SentryDiscardedEvent *> *discardedEvents;
5555

56+
@property (nonatomic, strong) NSMutableArray<SentryEnvelope *> *notStoredEnvelopes;
57+
5658
/**
5759
* Synching with a dispatch queue to have concurrent reads and writes as barrier blocks is roughly
5860
* 30% slower than using atomic here.
@@ -87,6 +89,7 @@ - (id)initWithOptions:(SentryOptions *)options
8789
_isSending = NO;
8890
_isFlushing = NO;
8991
self.discardedEvents = [NSMutableDictionary new];
92+
self.notStoredEnvelopes = [NSMutableArray new];
9093
[self.envelopeRateLimit setDelegate:self];
9194
[self.fileManager setDelegate:self];
9295

@@ -132,7 +135,13 @@ - (void)sendEnvelope:(SentryEnvelope *)envelope
132135
// thread, which could be the main thread.
133136
__weak SentryHttpTransport *weakSelf = self;
134137
[self.dispatchQueue dispatchAsyncWithBlock:^{
135-
[weakSelf.fileManager storeEnvelope:envelopeToStore];
138+
NSString *path = [weakSelf.fileManager storeEnvelope:envelopeToStore];
139+
if (path == nil) {
140+
SENTRY_LOG_DEBUG(@"Could not store envelope. Schedule for sending.");
141+
@synchronized(weakSelf.notStoredEnvelopes) {
142+
[weakSelf.notStoredEnvelopes addObject:envelopeToStore];
143+
}
144+
}
136145
[weakSelf sendAllCachedEnvelopes];
137146
}];
138147
}
@@ -280,24 +289,38 @@ - (void)sendAllCachedEnvelopes
280289
self.isSending = YES;
281290
}
282291

283-
SentryFileContents *envelopeFileContents = [self.fileManager getOldestEnvelope];
284-
if (nil == envelopeFileContents) {
285-
SENTRY_LOG_DEBUG(@"No envelopes left to send.");
286-
[self finishedSending];
287-
return;
292+
SentryEnvelope *envelope;
293+
NSString *envelopeFilePath;
294+
295+
@synchronized(self.notStoredEnvelopes) {
296+
if (self.notStoredEnvelopes.count > 0) {
297+
envelope = self.notStoredEnvelopes[0];
298+
[self.notStoredEnvelopes removeObjectAtIndex:0];
299+
}
288300
}
289301

290-
SentryEnvelope *envelope = [SentrySerialization envelopeWithData:envelopeFileContents.contents];
291-
if (nil == envelope) {
292-
SENTRY_LOG_DEBUG(@"Envelope contained no deserializable data.");
293-
[self deleteEnvelopeAndSendNext:envelopeFileContents.path];
294-
return;
302+
if (envelope == nil) {
303+
SentryFileContents *envelopeFileContents = [self.fileManager getOldestEnvelope];
304+
if (nil == envelopeFileContents) {
305+
SENTRY_LOG_DEBUG(@"No envelopes left to send.");
306+
[self finishedSending];
307+
return;
308+
}
309+
310+
envelopeFilePath = envelopeFileContents.path;
311+
312+
envelope = [SentrySerialization envelopeWithData:envelopeFileContents.contents];
313+
if (nil == envelope) {
314+
SENTRY_LOG_DEBUG(@"Envelope contained no deserializable data.");
315+
[self deleteEnvelopeAndSendNext:envelopeFilePath];
316+
return;
317+
}
295318
}
296319

297320
SentryEnvelope *rateLimitedEnvelope = [self.envelopeRateLimit removeRateLimitedItems:envelope];
298321
if (rateLimitedEnvelope.items.count == 0) {
299322
SENTRY_LOG_DEBUG(@"Envelope had no rate-limited items, nothing to send.");
300-
[self deleteEnvelopeAndSendNext:envelopeFileContents.path];
323+
[self deleteEnvelopeAndSendNext:envelopeFilePath];
301324
return;
302325
}
303326

@@ -309,22 +332,24 @@ - (void)sendAllCachedEnvelopes
309332
dsn:self.options.parsedDsn
310333
didFailWithError:&requestError];
311334

312-
if (nil != requestError) {
313-
SENTRY_LOG_DEBUG(@"Failed to build request: %@.", requestError);
335+
if (nil == request || nil != requestError) {
336+
if (nil != requestError) {
337+
SENTRY_LOG_DEBUG(@"Failed to build request: %@.", requestError);
338+
}
314339
[self recordLostEventFor:rateLimitedEnvelope.items];
315-
[self deleteEnvelopeAndSendNext:envelopeFileContents.path];
340+
[self deleteEnvelopeAndSendNext:envelopeFilePath];
316341
return;
317342
} else {
318-
[self sendEnvelope:rateLimitedEnvelope
319-
envelopePath:envelopeFileContents.path
320-
request:request];
343+
[self sendEnvelope:rateLimitedEnvelope envelopePath:envelopeFilePath request:request];
321344
}
322345
}
323346

324-
- (void)deleteEnvelopeAndSendNext:(NSString *)envelopePath
347+
- (void)deleteEnvelopeAndSendNext:(nullable NSString *)envelopePath
325348
{
326349
SENTRY_LOG_DEBUG(@"Deleting envelope and sending next.");
327-
[self.fileManager removeFileAtPath:envelopePath];
350+
if (envelopePath != nil) {
351+
[self.fileManager removeFileAtPath:envelopePath];
352+
}
328353
@synchronized(self) {
329354
self.isSending = NO;
330355
}
@@ -340,7 +365,7 @@ - (void)deleteEnvelopeAndSendNext:(NSString *)envelopePath
340365
}
341366

342367
- (void)sendEnvelope:(SentryEnvelope *)envelope
343-
envelopePath:(NSString *)envelopePath
368+
envelopePath:(NSString *_Nullable)envelopePath
344369
request:(NSURLRequest *)request
345370
{
346371
__weak SentryHttpTransport *weakSelf = self;

Sources/Sentry/SentryNSURLRequestBuilder.m

+24-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#import "SentryNSURLRequestBuilder.h"
22
#import "SentryDsn.h"
3+
#import "SentryLog.h"
34
#import "SentryNSURLRequest.h"
45
#import "SentrySerialization.h"
56
#import <Foundation/Foundation.h>
@@ -8,25 +9,33 @@
89

910
@implementation SentryNSURLRequestBuilder
1011

11-
- (NSURLRequest *)createEnvelopeRequest:(SentryEnvelope *)envelope
12-
dsn:(SentryDsn *)dsn
13-
didFailWithError:(NSError *_Nullable *_Nullable)error
12+
- (nullable NSURLRequest *)createEnvelopeRequest:(SentryEnvelope *)envelope
13+
dsn:(SentryDsn *)dsn
14+
didFailWithError:(NSError *_Nullable *_Nullable)error
1415
{
15-
return [[SentryNSURLRequest alloc]
16-
initEnvelopeRequestWithDsn:dsn
17-
andData:[SentrySerialization dataWithEnvelope:envelope]
18-
didFailWithError:error];
16+
NSData *data = [SentrySerialization dataWithEnvelope:envelope];
17+
if (nil == data) {
18+
SENTRY_LOG_ERROR(@"Envelope cannot be converted to data");
19+
return nil;
20+
}
21+
return [[SentryNSURLRequest alloc] initEnvelopeRequestWithDsn:dsn
22+
andData:data
23+
didFailWithError:error];
1924
}
2025

21-
- (NSURLRequest *)createEnvelopeRequest:(SentryEnvelope *)envelope
22-
url:(NSURL *)url
23-
didFailWithError:(NSError *_Nullable *_Nullable)error
26+
- (nullable NSURLRequest *)createEnvelopeRequest:(SentryEnvelope *)envelope
27+
url:(NSURL *)url
28+
didFailWithError:(NSError *_Nullable *_Nullable)error
2429
{
25-
return [[SentryNSURLRequest alloc]
26-
initEnvelopeRequestWithURL:url
27-
andData:[SentrySerialization dataWithEnvelope:envelope]
28-
authHeader:nil
29-
didFailWithError:error];
30+
NSData *data = [SentrySerialization dataWithEnvelope:envelope];
31+
if (nil == data) {
32+
SENTRY_LOG_ERROR(@"Envelope cannot be converted to data");
33+
return nil;
34+
}
35+
return [[SentryNSURLRequest alloc] initEnvelopeRequestWithURL:url
36+
andData:data
37+
authHeader:nil
38+
didFailWithError:error];
3039
}
3140

3241
@end

Sources/Sentry/SentrySpotlightTransport.m

+4-2
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,10 @@ - (void)sendEnvelope:(SentryEnvelope *)envelope
7070
url:self.apiURL
7171
didFailWithError:&requestError];
7272

73-
if (requestError) {
74-
SENTRY_LOG_ERROR(@"Unable to build envelope request with error %@", requestError);
73+
if (nil == request || nil != requestError) {
74+
if (nil != requestError) {
75+
SENTRY_LOG_ERROR(@"Unable to build envelope request with error %@", requestError);
76+
}
7577
return;
7678
}
7779

Sources/Sentry/include/SentryNSURLRequestBuilder.h

+6-6
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ NS_ASSUME_NONNULL_BEGIN
99
*/
1010
@interface SentryNSURLRequestBuilder : NSObject
1111

12-
- (NSURLRequest *)createEnvelopeRequest:(SentryEnvelope *)envelope
13-
dsn:(SentryDsn *)dsn
14-
didFailWithError:(NSError *_Nullable *_Nullable)error;
12+
- (nullable NSURLRequest *)createEnvelopeRequest:(SentryEnvelope *)envelope
13+
dsn:(SentryDsn *)dsn
14+
didFailWithError:(NSError *_Nullable *_Nullable)error;
1515

16-
- (NSURLRequest *)createEnvelopeRequest:(SentryEnvelope *)envelope
17-
url:(NSURL *)url
18-
didFailWithError:(NSError *_Nullable *_Nullable)error;
16+
- (nullable NSURLRequest *)createEnvelopeRequest:(SentryEnvelope *)envelope
17+
url:(NSURL *)url
18+
didFailWithError:(NSError *_Nullable *_Nullable)error;
1919

2020
@end
2121

Tests/SentryTests/Networking/SentryHttpTransportTests.swift

+28-7
Original file line numberDiff line numberDiff line change
@@ -125,19 +125,19 @@ class SentryHttpTransportTests: XCTestCase {
125125
return transactionEnvelope
126126
}
127127

128-
func getSut(dispatchQueueWrapper: SentryDispatchQueueWrapper? = nil) -> SentryHttpTransport {
129-
130-
let dispatchQueue = dispatchQueueWrapper ?? self.dispatchQueueWrapper
131-
128+
func getSut(
129+
fileManager: SentryFileManager? = nil,
130+
dispatchQueueWrapper: SentryDispatchQueueWrapper? = nil
131+
) -> SentryHttpTransport {
132132
return SentryHttpTransport(
133133
options: options,
134134
cachedEnvelopeSendDelay: 0.0,
135-
fileManager: fileManager,
135+
fileManager: fileManager ?? self.fileManager,
136136
requestManager: requestManager,
137137
requestBuilder: requestBuilder,
138138
rateLimits: rateLimits,
139139
envelopeRateLimit: EnvelopeRateLimit(rateLimits: rateLimits),
140-
dispatchQueueWrapper: dispatchQueue
140+
dispatchQueueWrapper: dispatchQueueWrapper ?? self.dispatchQueueWrapper
141141
)
142142
}
143143
}
@@ -470,7 +470,7 @@ class SentryHttpTransportTests: XCTestCase {
470470
}
471471

472472
func testAllCachedEnvelopesCantDeserializeEnvelope() throws {
473-
let path = try XCTUnwrap( fixture.fileManager.store(TestConstants.envelope))
473+
let path = try XCTUnwrap(fixture.fileManager.store(TestConstants.envelope))
474474
let faultyEnvelope = Data([0x70, 0xa3, 0x10, 0x45])
475475
try faultyEnvelope.write(to: URL(fileURLWithPath: path))
476476

@@ -479,6 +479,17 @@ class SentryHttpTransportTests: XCTestCase {
479479
assertRequestsSent(requestCount: 1)
480480
assertEnvelopesStored(envelopeCount: 0)
481481
}
482+
483+
func testFailureToStoreEvenlopeEventStillSendsRequest() throws {
484+
let fileManger = try TestFileManager(options: fixture.options)
485+
fileManger.storeEnvelopePathNil = true // Failure to store envelope returns nil path
486+
let sut = fixture.getSut(fileManager: fileManger)
487+
488+
sut.send(envelope: fixture.eventEnvelope)
489+
490+
XCTAssertEqual(fileManger.storeEnvelopeInvocations.count, 1)
491+
assertRequestsSent(requestCount: 1)
492+
}
482493

483494
func testSendCachedEnvelopesFirst() throws {
484495
givenNoInternetConnection()
@@ -651,6 +662,16 @@ class SentryHttpTransportTests: XCTestCase {
651662
assertRequestsSent(requestCount: 1)
652663
}
653664

665+
func testBuildingRequestFailsReturningNil_DeletesEnvelopeAndSendsNext() {
666+
givenNoInternetConnection()
667+
sendEvent()
668+
669+
fixture.requestBuilder.shouldFailReturningNil = true
670+
sendEvent()
671+
assertEnvelopesStored(envelopeCount: 0)
672+
assertRequestsSent(requestCount: 1)
673+
}
674+
654675
func testDeallocated_CachedEnvelopesNotAllSent() throws {
655676
givenNoInternetConnection()
656677
givenCachedEvents(amount: 10)

0 commit comments

Comments
 (0)