Skip to content

Commit ed68562

Browse files
authored
fix: clean up profilers for discarded transactions (#3154)
1 parent 5b14b06 commit ed68562

File tree

12 files changed

+246
-61
lines changed

12 files changed

+246
-61
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Fixes
6+
7+
- Reclaim memory used by profiler when transactions are discarded (#3154)
8+
39
## 8.9.2
410

511
### Improvements

Sentry.xcodeproj/project.pbxproj

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1827,13 +1827,6 @@
18271827
/* End PBXFrameworksBuildPhase section */
18281828

18291829
/* Begin PBXGroup section */
1830-
035E73C627D5661A005EEB11 /* Profiling */ = {
1831-
isa = PBXGroup;
1832-
children = (
1833-
);
1834-
path = Profiling;
1835-
sourceTree = "<group>";
1836-
};
18371830
0A9BF4E028A114690068D266 /* ViewHierarchy */ = {
18381831
isa = PBXGroup;
18391832
children = (
@@ -2255,7 +2248,6 @@
22552248
7BD7299B24654CD500EA3610 /* Helper */,
22562249
7B944FA924697E9700A10721 /* Integrations */,
22572250
7BBD18AF24517E5D00427C76 /* Networking */,
2258-
035E73C627D5661A005EEB11 /* Profiling */,
22592251
7B3D0474249A3D5800E106B6 /* Protocol */,
22602252
63FE71D220DA66C500CDBAE8 /* SentryCrash */,
22612253
7B944FAC2469B41600A10721 /* State */,

SentryTestUtils/ClearTestState.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class TestCleanup: NSObject {
4040

4141
#if os(iOS) || os(macOS) || targetEnvironment(macCatalyst)
4242
SentryProfiler.getCurrent().stop(for: .normal)
43-
SentryTracer.resetConcurrencyTracking()
43+
SentryProfiler.resetConcurrencyTracking()
4444
#endif // os(iOS) || os(macOS) || targetEnvironment(macCatalyst)
4545

4646
#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)

SentryTestUtils/SentryTestUtils-ObjC-BridgingHeader.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88
# import "SentryUIViewControllerPerformanceTracker.h"
99
#endif // SENTRY_HAS_UIKIT
1010

11+
#import "SentryProfilingConditionals.h"
12+
13+
#if SENTRY_TARGET_PROFILING_SUPPORTED
14+
# import "SentryProfiler+Test.h"
15+
#endif // SENTRY_TARGET_PROFILING_SUPPORTED
16+
1117
#import "PrivateSentrySDKOnly.h"
1218
#import "SentryAppState.h"
1319
#import "SentryClient+Private.h"
@@ -26,7 +32,6 @@
2632
#import "SentryNSTimerFactory.h"
2733
#import "SentryNetworkTracker.h"
2834
#import "SentryPerformanceTracker+Testing.h"
29-
#import "SentryProfiler+Test.h"
3035
#import "SentryRandom.h"
3136
#import "SentrySDK+Private.h"
3237
#import "SentrySDK+Tests.h"

Sources/Sentry/Profiling/SentryProfiledTracerConcurrency.mm

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,42 @@
1616
# endif // SENTRY_HAS_UIKIT
1717

1818
/**
19-
* a mapping of profilers to the tracers that started them that are still in-flight and will need to
20-
* query them for their profiling data when they finish. this helps resolve the incongruity between
21-
* the different timeout durations between tracers (500s) and profilers (30s), where a transaction
22-
* may start a profiler that then times out, and then a new transaction starts a new profiler, and
23-
* we must keep the aborted one around until its associated transaction finishes.
19+
* a mapping of profilers to the number of tracers that started them that are still in-flight and
20+
* will need to query them for their profiling data when they finish. this helps resolve the
21+
* incongruity between the different timeout durations between tracers (500s) and profilers (30s),
22+
* where a transaction may start a profiler that then times out, and then a new transaction starts a
23+
* new profiler, and we must keep the aborted one around until its associated transaction finishes.
2424
*/
2525
static NSMutableDictionary</* SentryProfiler.profileId */ NSString *,
26-
NSMutableSet<SentryTracer *> *> *_gProfilersToTracers;
26+
/* number of in-flight tracers */ NSNumber *> *_gProfilersToTracers;
2727

2828
/** provided for fast access to a profiler given a tracer */
2929
static NSMutableDictionary</* SentryTracer.tracerId */ NSString *, SentryProfiler *>
3030
*_gTracersToProfilers;
3131

32+
namespace {
33+
34+
/**
35+
* Remove a profiler from tracking given the id of the tracer it's associated with.
36+
* @warning Must be called from a synchronized context.
37+
*/
38+
void
39+
_unsafe_cleanUpProfiler(SentryProfiler *profiler, NSString *tracerKey)
40+
{
41+
const auto profilerKey = profiler.profileId.sentryIdString;
42+
43+
[_gTracersToProfilers removeObjectForKey:tracerKey];
44+
_gProfilersToTracers[profilerKey] = @(_gProfilersToTracers[profilerKey].unsignedIntValue - 1);
45+
if ([_gProfilersToTracers[profilerKey] unsignedIntValue] == 0) {
46+
[_gProfilersToTracers removeObjectForKey:profilerKey];
47+
if ([profiler isRunning]) {
48+
[profiler stopForReason:SentryProfilerTruncationReasonNormal];
49+
}
50+
}
51+
}
52+
53+
} // namespace
54+
3255
std::mutex _gStateLock;
3356

3457
void
@@ -48,22 +71,39 @@
4871

4972
if (_gProfilersToTracers == nil) {
5073
_gProfilersToTracers = [NSMutableDictionary</* SentryProfiler.profileId */ NSString *,
51-
NSMutableSet<SentryTracer *> *> dictionaryWithObject:[NSMutableSet setWithObject:tracer]
52-
forKey:profilerKey];
74+
/* number of in-flight tracers */ NSNumber *>
75+
dictionary];
5376
_gTracersToProfilers =
5477
[NSMutableDictionary</* SentryTracer.tracerId */ NSString *, SentryProfiler *>
55-
dictionaryWithObject:profiler
56-
forKey:tracerKey];
57-
return;
78+
dictionary];
5879
}
5980

60-
if (_gProfilersToTracers[profilerKey] == nil) {
61-
_gProfilersToTracers[profilerKey] = [NSMutableSet setWithObject:tracer];
62-
} else {
63-
[_gProfilersToTracers[profilerKey] addObject:tracer];
81+
_gProfilersToTracers[profilerKey] = @(_gProfilersToTracers[profilerKey].unsignedIntValue + 1);
82+
_gTracersToProfilers[tracerKey] = profiler;
83+
}
84+
85+
void
86+
discardProfilerForTracer(SentryTracer *tracer)
87+
{
88+
std::lock_guard<std::mutex> l(_gStateLock);
89+
90+
SENTRY_CASSERT(_gTracersToProfilers != nil && _gProfilersToTracers != nil,
91+
@"Structures should have already been initialized by the time they are being queried");
92+
93+
const auto tracerKey = tracer.traceId.sentryIdString;
94+
const auto profiler = _gTracersToProfilers[tracerKey];
95+
96+
if (profiler == nil) {
97+
return;
6498
}
6599

66-
_gTracersToProfilers[tracerKey] = profiler;
100+
_unsafe_cleanUpProfiler(profiler, tracerKey);
101+
102+
# if SENTRY_HAS_UIKIT
103+
if (_gProfilersToTracers.count == 0) {
104+
[SentryDependencyContainer.sharedInstance.framesTracker resetProfilingTimestamps];
105+
}
106+
# endif // SENTRY_HAS_UIKIT
67107
}
68108

69109
SentryProfiler *_Nullable profilerForFinishedTracer(SentryTracer *tracer)
@@ -81,16 +121,7 @@
81121
return nil;
82122
}
83123

84-
const auto profilerKey = profiler.profileId.sentryIdString;
85-
86-
[_gTracersToProfilers removeObjectForKey:tracerKey];
87-
[_gProfilersToTracers[profilerKey] removeObject:tracer];
88-
if ([_gProfilersToTracers[profilerKey] count] == 0) {
89-
[_gProfilersToTracers removeObjectForKey:profilerKey];
90-
if ([profiler isRunning]) {
91-
[profiler stopForReason:SentryProfilerTruncationReasonNormal];
92-
}
93-
}
124+
_unsafe_cleanUpProfiler(profiler, tracerKey);
94125

95126
# if SENTRY_HAS_UIKIT
96127
profiler._screenFrameData =
@@ -111,6 +142,13 @@
111142
[_gTracersToProfilers removeAllObjects];
112143
[_gProfilersToTracers removeAllObjects];
113144
}
145+
146+
NSUInteger
147+
currentProfiledTracers()
148+
{
149+
std::lock_guard<std::mutex> l(_gStateLock);
150+
return [_gTracersToProfilers count];
151+
}
114152
# endif // defined(TEST) || defined(TESTCI)
115153

116154
#endif // SENTRY_TARGET_PROFILING_SUPPORTED

Sources/Sentry/SentryProfiler.mm

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,19 @@ + (SentryProfiler *)getCurrentProfiler
515515
{
516516
return _gCurrentProfiler;
517517
}
518+
519+
// this just calls through to SentryProfiledTracerConcurrency.resetConcurrencyTracking(). we have to
520+
// do this through SentryTracer because SentryProfiledTracerConcurrency cannot be included in test
521+
// targets via ObjC bridging headers because it contains C++.
522+
+ (void)resetConcurrencyTracking
523+
{
524+
resetConcurrencyTracking();
525+
}
526+
527+
+ (NSUInteger)currentProfiledTracers
528+
{
529+
return currentProfiledTracers();
530+
}
518531
# endif // defined(TEST) || defined(TESTCI)
519532

520533
@end

Sources/Sentry/SentryTracer.m

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,15 @@ - (instancetype)initWithTransactionContext:(SentryTransactionContext *)transacti
172172
return self;
173173
}
174174

175+
- (void)dealloc
176+
{
177+
#if SENTRY_TARGET_PROFILING_SUPPORTED
178+
if (self.isProfiling) {
179+
discardProfilerForTracer(self);
180+
}
181+
#endif // SENTRY_TARGET_PROFILING_SUPPORTED
182+
}
183+
175184
- (nullable SentryTracer *)tracer
176185
{
177186
return self;
@@ -831,16 +840,6 @@ - (NSDate *)originalStartTimestamp
831840
return _startTimeChanged ? _originalStartTimestamp : self.startTimestamp;
832841
}
833842

834-
#if SENTRY_TARGET_PROFILING_SUPPORTED && (defined(TEST) || defined(TESTCI))
835-
// this just calls through to SentryProfiledTracerConcurrency.resetConcurrencyTracking(). we have to
836-
// do this through SentryTracer because SentryProfiledTracerConcurrency cannot be included in test
837-
// targets via ObjC bridging headers because it contains C++.
838-
+ (void)resetConcurrencyTracking
839-
{
840-
resetConcurrencyTracking();
841-
}
842-
#endif // SENTRY_TARGET_PROFILING_SUPPORTED && (defined(TEST) || defined(TESTCI))
843-
844843
@end
845844

846845
NS_ASSUME_NONNULL_END

Sources/Sentry/include/SentryProfiledTracerConcurrency.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ SENTRY_EXTERN_C_BEGIN
1717
*/
1818
void trackProfilerForTracer(SentryProfiler *profiler, SentryTracer *tracer);
1919

20+
/**
21+
* For transactions that will be discarded, clean up the bookkeeping state associated with them to
22+
* reclaim the memory they're using.
23+
*/
24+
void discardProfilerForTracer(SentryTracer *tracer);
25+
2026
/**
2127
* Return the profiler instance associated with the tracer. If it was the last tracer for the
2228
* associated profiler, stop that profiler. Copy any recorded @c SentryScreenFrames data into the
@@ -27,6 +33,7 @@ SentryProfiler *_Nullable profilerForFinishedTracer(SentryTracer *tracer);
2733

2834
# if defined(TEST) || defined(TESTCI)
2935
void resetConcurrencyTracking(void);
36+
NSUInteger currentProfiledTracers(void);
3037
# endif // defined(TEST) || defined(TESTCI)
3138

3239
SENTRY_EXTERN_C_END

0 commit comments

Comments
 (0)