Skip to content

Commit e0f6628

Browse files
authored
fix: event processor blocking transactions from being sent if autoAppStart is false (#2028)
* Add fix * Update CHANGELOG * Fix tests * Update native_app_start_integration.dart * Implement code review * Apply auto ttid only when autoAppStart is true * Revert * Revert
1 parent 9fe67d5 commit e0f6628

File tree

5 files changed

+144
-44
lines changed

5 files changed

+144
-44
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Fixes
6+
7+
- Event processor blocking transactions from being sent if `autoAppStart` is false ([#2028](https://github.com/getsentry/sentry-dart/pull/2028))
8+
59
### Features
610

711
- Adds app start spans to first transaction ([#2009](https://github.com/getsentry/sentry-dart/pull/2009))

flutter/lib/src/event_processor/native_app_start_event_processor.dart

+21-3
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,25 @@ class NativeAppStartEventProcessor implements EventProcessor {
2222

2323
@override
2424
Future<SentryEvent?> apply(SentryEvent event, Hint hint) async {
25-
if (_native.didAddAppStartMeasurement || event is! SentryTransaction) {
25+
final options = _hub.options;
26+
if (_native.didAddAppStartMeasurement ||
27+
event is! SentryTransaction ||
28+
options is! SentryFlutterOptions) {
2629
return event;
2730
}
2831

2932
final appStartInfo = await NativeAppStartIntegration.getAppStartInfo();
33+
34+
final appStartEnd = _native.appStartEnd;
35+
if (!options.autoAppStart) {
36+
if (appStartEnd != null) {
37+
appStartInfo?.end = appStartEnd;
38+
} else {
39+
// If autoAppStart is disabled and appStartEnd is not set, we can't add app starts
40+
return event;
41+
}
42+
}
43+
3044
final measurement = appStartInfo?.toMeasurement();
3145

3246
if (measurement != null) {
@@ -44,6 +58,10 @@ class NativeAppStartEventProcessor implements EventProcessor {
4458
Future<void> _attachAppStartSpans(
4559
AppStartInfo appStartInfo, SentryTracer transaction) async {
4660
final transactionTraceId = transaction.context.traceId;
61+
final appStartEnd = appStartInfo.end;
62+
if (appStartEnd == null) {
63+
return;
64+
}
4765

4866
final appStartSpan = await _createAndFinishSpan(
4967
tracer: transaction,
@@ -52,7 +70,7 @@ class NativeAppStartEventProcessor implements EventProcessor {
5270
parentSpanId: transaction.context.spanId,
5371
traceId: transactionTraceId,
5472
startTimestamp: appStartInfo.start,
55-
endTimestamp: appStartInfo.end);
73+
endTimestamp: appStartEnd);
5674

5775
final pluginRegistrationSpan = await _createAndFinishSpan(
5876
tracer: transaction,
@@ -79,7 +97,7 @@ class NativeAppStartEventProcessor implements EventProcessor {
7997
parentSpanId: appStartSpan.context.spanId,
8098
traceId: transactionTraceId,
8199
startTimestamp: appStartInfo.mainIsolateStart,
82-
endTimestamp: appStartInfo.end);
100+
endTimestamp: appStartEnd);
83101

84102
transaction.children.addAll([
85103
appStartSpan,

flutter/lib/src/integrations/native_app_start_integration.dart

+54-40
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ class NativeAppStartIntegration extends Integration<SentryFlutterOptions> {
1515
final SentryNative _native;
1616
final FrameCallbackHandler _frameCallbackHandler;
1717

18+
/// Timeout duration to wait for the app start info to be fetched.
19+
static const _timeoutDuration = Duration(seconds: 30);
20+
1821
/// We filter out App starts more than 60s
1922
static const _maxAppStartMillis = 60000;
2023

@@ -39,7 +42,8 @@ class NativeAppStartIntegration extends Integration<SentryFlutterOptions> {
3942
if (_appStartInfo != null) {
4043
return Future.value(_appStartInfo);
4144
}
42-
return _appStartCompleter.future;
45+
return _appStartCompleter.future
46+
.timeout(_timeoutDuration, onTimeout: () => null);
4347
}
4448

4549
@visibleForTesting
@@ -62,31 +66,31 @@ class NativeAppStartIntegration extends Integration<SentryFlutterOptions> {
6266
return;
6367
}
6468

65-
if (options.autoAppStart) {
66-
_frameCallbackHandler.addPostFrameCallback((timeStamp) async {
67-
if (_native.didFetchAppStart) {
68-
return;
69-
}
69+
if (_native.didFetchAppStart) {
70+
return;
71+
}
7072

73+
_frameCallbackHandler.addPostFrameCallback((timeStamp) async {
74+
final nativeAppStart = await _native.fetchNativeAppStart();
75+
if (nativeAppStart == null) {
76+
setAppStartInfo(null);
77+
return;
78+
}
79+
80+
final mainIsolateStartDateTime = SentryFlutter.mainIsolateStartTime;
81+
final appStartDateTime = DateTime.fromMillisecondsSinceEpoch(
82+
nativeAppStart.appStartTime.toInt());
83+
final pluginRegistrationDateTime = DateTime.fromMillisecondsSinceEpoch(
84+
nativeAppStart.pluginRegistrationTime);
85+
DateTime? appStartEndDateTime;
86+
87+
if (options.autoAppStart) {
7188
// We only assign the current time if it's not already set - this is useful in tests
7289
// ignore: invalid_use_of_internal_member
7390
_native.appStartEnd ??= options.clock();
74-
final appStartEndDateTime = _native.appStartEnd;
75-
final nativeAppStart = await _native.fetchNativeAppStart();
76-
final pluginRegistrationTime = nativeAppStart?.pluginRegistrationTime;
77-
final mainIsolateStartDateTime = SentryFlutter.mainIsolateStartTime;
78-
79-
if (nativeAppStart == null ||
80-
appStartEndDateTime == null ||
81-
pluginRegistrationTime == null) {
82-
return;
83-
}
91+
appStartEndDateTime = _native.appStartEnd;
8492

85-
final appStartDateTime = DateTime.fromMillisecondsSinceEpoch(
86-
nativeAppStart.appStartTime.toInt());
87-
final duration = appStartEndDateTime.difference(appStartDateTime);
88-
final pluginRegistrationDateTime =
89-
DateTime.fromMillisecondsSinceEpoch(pluginRegistrationTime);
93+
final duration = appStartEndDateTime?.difference(appStartDateTime);
9094

9195
// We filter out app start more than 60s.
9296
// This could be due to many different reasons.
@@ -96,23 +100,23 @@ class NativeAppStartIntegration extends Integration<SentryFlutterOptions> {
96100
// If the system forked the process earlier to accelerate the app start.
97101
// And some unknown reasons that could not be reproduced.
98102
// We've seen app starts with hours, days and even months.
99-
if (duration.inMilliseconds > _maxAppStartMillis) {
103+
if (duration != null && duration.inMilliseconds > _maxAppStartMillis) {
100104
setAppStartInfo(null);
101105
return;
102106
}
107+
}
103108

104-
final appStartInfo = AppStartInfo(
105-
nativeAppStart.isColdStart ? AppStartType.cold : AppStartType.warm,
106-
start: appStartDateTime,
107-
end: appStartEndDateTime,
108-
pluginRegistration: pluginRegistrationDateTime,
109-
mainIsolateStart: mainIsolateStartDateTime);
109+
final appStartInfo = AppStartInfo(
110+
nativeAppStart.isColdStart ? AppStartType.cold : AppStartType.warm,
111+
start: appStartDateTime,
112+
end: appStartEndDateTime,
113+
pluginRegistration: pluginRegistrationDateTime,
114+
mainIsolateStart: mainIsolateStartDateTime);
110115

111-
setAppStartInfo(appStartInfo);
112-
});
113-
}
116+
setAppStartInfo(appStartInfo);
117+
});
114118

115-
options.addEventProcessor(NativeAppStartEventProcessor(_native));
119+
options.addEventProcessor(NativeAppStartEventProcessor(_native, hub: hub));
116120

117121
options.sdk.addIntegration('nativeAppStartIntegration');
118122
}
@@ -121,21 +125,31 @@ class NativeAppStartIntegration extends Integration<SentryFlutterOptions> {
121125
enum AppStartType { cold, warm }
122126

123127
class AppStartInfo {
124-
AppStartInfo(this.type,
125-
{required this.start,
126-
required this.end,
127-
required this.pluginRegistration,
128-
required this.mainIsolateStart});
128+
AppStartInfo(
129+
this.type, {
130+
required this.start,
131+
required this.pluginRegistration,
132+
required this.mainIsolateStart,
133+
this.end,
134+
});
129135

130136
final AppStartType type;
131137
final DateTime start;
132-
final DateTime end;
138+
139+
// We allow the end to be null, since it might be set at a later time
140+
// with setAppStartEnd when autoAppStart is disabled
141+
DateTime? end;
142+
133143
final DateTime pluginRegistration;
134144
final DateTime mainIsolateStart;
135145

136-
Duration get duration => end.difference(start);
146+
Duration? get duration => end?.difference(start);
137147

138-
SentryMeasurement toMeasurement() {
148+
SentryMeasurement? toMeasurement() {
149+
final duration = this.duration;
150+
if (duration == null) {
151+
return null;
152+
}
139153
return type == AppStartType.cold
140154
? SentryMeasurement.coldAppStart(duration)
141155
: SentryMeasurement.warmAppStart(duration);

flutter/test/integrations/native_app_start_integration_test.dart

+64
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,70 @@ void main() {
114114
expect(appStartInfo?.start, DateTime.fromMillisecondsSinceEpoch(0));
115115
expect(appStartInfo?.end, DateTime.fromMillisecondsSinceEpoch(10));
116116
});
117+
118+
test(
119+
'autoAppStart is false and appStartEnd is not set does not add app start measurement',
120+
() async {
121+
fixture.options.autoAppStart = false;
122+
fixture.binding.nativeAppStart = NativeAppStart(
123+
appStartTime: 0, pluginRegistrationTime: 10, isColdStart: true);
124+
125+
fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options);
126+
127+
final tracer = fixture.createTracer();
128+
final transaction = SentryTransaction(tracer);
129+
130+
final processor = fixture.options.eventProcessors.first;
131+
final enriched =
132+
await processor.apply(transaction, Hint()) as SentryTransaction;
133+
134+
expect(enriched.measurements.isEmpty, true);
135+
expect(enriched.spans.isEmpty, true);
136+
});
137+
138+
test(
139+
'autoAppStart is false and appStartEnd is set adds app start measurement',
140+
() async {
141+
fixture.options.autoAppStart = false;
142+
fixture.binding.nativeAppStart = NativeAppStart(
143+
appStartTime: 0, pluginRegistrationTime: 10, isColdStart: true);
144+
SentryFlutter.native = fixture.native;
145+
146+
fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options);
147+
148+
SentryFlutter.setAppStartEnd(DateTime.fromMillisecondsSinceEpoch(10));
149+
150+
final tracer = fixture.createTracer();
151+
final transaction = SentryTransaction(tracer);
152+
153+
final processor = fixture.options.eventProcessors.first;
154+
final enriched =
155+
await processor.apply(transaction, Hint()) as SentryTransaction;
156+
157+
final measurement = enriched.measurements['app_start_cold']!;
158+
expect(measurement.value, 10);
159+
expect(measurement.unit, DurationSentryMeasurementUnit.milliSecond);
160+
161+
final appStartInfo = await NativeAppStartIntegration.getAppStartInfo();
162+
163+
final appStartSpan = enriched.spans.firstWhereOrNull((element) =>
164+
element.context.description == appStartInfo!.appStartTypeDescription);
165+
final pluginRegistrationSpan = enriched.spans.firstWhereOrNull(
166+
(element) =>
167+
element.context.description ==
168+
appStartInfo!.pluginRegistrationDescription);
169+
final mainIsolateSetupSpan = enriched.spans.firstWhereOrNull((element) =>
170+
element.context.description ==
171+
appStartInfo!.mainIsolateSetupDescription);
172+
final firstFrameRenderSpan = enriched.spans.firstWhereOrNull((element) =>
173+
element.context.description ==
174+
appStartInfo!.firstFrameRenderDescription);
175+
176+
expect(appStartSpan, isNotNull);
177+
expect(pluginRegistrationSpan, isNotNull);
178+
expect(mainIsolateSetupSpan, isNotNull);
179+
expect(firstFrameRenderSpan, isNotNull);
180+
});
117181
});
118182

119183
group('App start spans', () {

flutter/test/navigation/sentry_display_widget_test.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ void main() {
9494
expect(tracer.measurements, hasLength(1));
9595
final measurement = tracer.measurements['time_to_initial_display'];
9696
expect(measurement, isNotNull);
97-
expect(measurement?.value, appStartInfo.duration.inMilliseconds);
97+
expect(measurement?.value, appStartInfo.duration?.inMilliseconds);
9898
expect(measurement?.value, ttidSpanDuration.inMilliseconds);
9999
expect(measurement?.unit, DurationSentryMeasurementUnit.milliSecond);
100100
});

0 commit comments

Comments
 (0)