From 7f22e0ee9f92e3e76a6a846d72b4ee39d5db0a63 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Tue, 2 Jan 2024 16:57:24 +0100 Subject: [PATCH 01/13] Add isolate debug name to sync file span --- file/lib/src/sentry_file.dart | 6 +++++- file/test/sentry_file_test.dart | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/file/lib/src/sentry_file.dart b/file/lib/src/sentry_file.dart index de0004c938..e15edebe5d 100644 --- a/file/lib/src/sentry_file.dart +++ b/file/lib/src/sentry_file.dart @@ -206,6 +206,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:isolate'; import 'dart:typed_data'; import 'package:meta/meta.dart'; import 'package:sentry/sentry.dart'; @@ -489,7 +490,10 @@ class SentryFile implements File { span?.origin = SentryTraceOrigins.autoFile; span?.setData('file.async', false); - + final isolateName = Isolate.current.debugName; + if (isolateName != null && isolateName.isNotEmpty) { + span?.setData('file.isolate', isolateName); + } final Map breadcrumbData = {}; breadcrumbData['file.async'] = false; diff --git a/file/test/sentry_file_test.dart b/file/test/sentry_file_test.dart index 3f7135de54..276fcd6150 100644 --- a/file/test/sentry_file_test.dart +++ b/file/test/sentry_file_test.dart @@ -34,6 +34,9 @@ void main() { .endsWith('test_resources/testfile.txt'), true); expect(span.origin, SentryTraceOrigins.autoFile); + if (!async) { + expect(span.data['file.isolate'], 'main'); + } } void _asserBreadcrumb(bool async) { @@ -121,6 +124,9 @@ void main() { .endsWith('test_resources/testfile_create.txt'), true); expect(span.origin, SentryTraceOrigins.autoFile); + if (!async) { + expect(span.data['file.isolate'], 'main'); + } } void _assertBreadcrumb(bool async, {int? size = 0}) { @@ -206,6 +212,9 @@ void main() { .endsWith('test_resources/testfile_delete.txt'), true); expect(span.origin, SentryTraceOrigins.autoFile); + if (!async) { + expect(span.data['file.isolate'], 'main'); + } } void _assertBreadcrumb(bool async, {int? size = 0}) { @@ -347,6 +356,9 @@ void main() { .endsWith('test_resources/$fileName'), true); expect(span.origin, SentryTraceOrigins.autoFile); + if (!async) { + expect(span.data['file.isolate'], 'main'); + } } void _assertBreadcrumb(String fileName, bool async, {int? size = 0}) { @@ -497,6 +509,9 @@ void main() { (span.data['file.path'] as String).endsWith('test_resources/$name'), true); expect(span.origin, SentryTraceOrigins.autoFile); + if (!async) { + expect(span.data['file.isolate'], 'main'); + } } void _assertBreadcrumb(bool async, String name) { From e38f8e437b7608202624e87653fa5132dabf0418 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Tue, 2 Jan 2024 17:00:03 +0100 Subject: [PATCH 02/13] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71483dab8e..17e26930db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Add `ConnectivityIntegration` for web ([#1765](https://github.com/getsentry/sentry-dart/pull/1765)) - We only get the info if online/offline on web platform. The added breadcrumb is set to either `wifi` or `none`. - APM for isar ([#1726](https://github.com/getsentry/sentry-dart/pull/1726)) +- Add isolate name to sync file spans ([#1801](https://github.com/getsentry/sentry-dart/pull/1801)) ## 7.14.0 From 3202d56e9ccef83d299345a7b06227bdbbef0d27 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Mon, 8 Jan 2024 13:45:11 +0100 Subject: [PATCH 03/13] check against Isolate debug name --- file/test/sentry_file_test.dart | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/file/test/sentry_file_test.dart b/file/test/sentry_file_test.dart index 276fcd6150..c41b940992 100644 --- a/file/test/sentry_file_test.dart +++ b/file/test/sentry_file_test.dart @@ -3,6 +3,7 @@ @TestOn('vm') import 'dart:io'; +import 'dart:isolate'; import 'package:sentry/sentry.dart'; import 'package:sentry_file/sentry_file.dart'; @@ -35,7 +36,7 @@ void main() { true); expect(span.origin, SentryTraceOrigins.autoFile); if (!async) { - expect(span.data['file.isolate'], 'main'); + expect(span.data['file.isolate'], Isolate.current.debugName); } } @@ -125,7 +126,7 @@ void main() { true); expect(span.origin, SentryTraceOrigins.autoFile); if (!async) { - expect(span.data['file.isolate'], 'main'); + expect(span.data['file.isolate'], Isolate.current.debugName); } } @@ -213,7 +214,7 @@ void main() { true); expect(span.origin, SentryTraceOrigins.autoFile); if (!async) { - expect(span.data['file.isolate'], 'main'); + expect(span.data['file.isolate'], Isolate.current.debugName); } } @@ -357,7 +358,7 @@ void main() { true); expect(span.origin, SentryTraceOrigins.autoFile); if (!async) { - expect(span.data['file.isolate'], 'main'); + expect(span.data['file.isolate'], Isolate.current.debugName); } } @@ -510,7 +511,7 @@ void main() { true); expect(span.origin, SentryTraceOrigins.autoFile); if (!async) { - expect(span.data['file.isolate'], 'main'); + expect(span.data['file.isolate'], Isolate.current.debugName); } } From 53df9a1b21d41e0de1a168b19a924db29ebd9c78 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Mon, 8 Jan 2024 13:45:57 +0100 Subject: [PATCH 04/13] update chengelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9773eb777e..b4d6c2f62c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,9 @@ - Add `ConnectivityIntegration` for web ([#1765](https://github.com/getsentry/sentry-dart/pull/1765)) - We only get the info if online/offline on web platform. The added breadcrumb is set to either `wifi` or `none`. - APM for isar ([#1726](https://github.com/getsentry/sentry-dart/pull/1726)) -- Add isolate name to sync file spans ([#1801](https://github.com/getsentry/sentry-dart/pull/1801)) - Add isar breadcrumbs ([#1800](https://github.com/getsentry/sentry-dart/pull/1800)) - Starting with Flutter 3.16, Sentry adds the [`appFlavor`](https://api.flutter.dev/flutter/services/appFlavor-constant.html) to the `flutter_context` ([#1799](https://github.com/getsentry/sentry-dart/pull/1799)) +- Add isolate name to sync file spans ([#1801](https://github.com/getsentry/sentry-dart/pull/1801)) ### Dependencies From 5ee098334b55e4183e523d9b8b8b2bac0d43f3e4 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Tue, 16 Jan 2024 18:09:25 +0100 Subject: [PATCH 05/13] inject isolate check, use blocked_main_thread --- file/lib/src/sentry_file.dart | 17 +- file/test/sentry_file_test.dart | 305 ++++++++++++++++++++++++++++++-- 2 files changed, 300 insertions(+), 22 deletions(-) diff --git a/file/lib/src/sentry_file.dart b/file/lib/src/sentry_file.dart index e15edebe5d..8c8d3c146a 100644 --- a/file/lib/src/sentry_file.dart +++ b/file/lib/src/sentry_file.dart @@ -214,6 +214,7 @@ import 'package:sentry/sentry.dart'; import 'version.dart'; typedef Callback = FutureOr Function(); +typedef IsMainIsolateCallback = bool Function(); /// The Sentry wrapper for the File IO implementation that creates a span /// out of the active transaction in the scope and a breadcrumb, which gets @@ -239,13 +240,19 @@ class SentryFile implements File { SentryFile( this._file, { @internal Hub? hub, - }) : _hub = hub ?? HubAdapter() { + @internal IsMainIsolateCallback? isMainIsolateCallback, + }) : _hub = hub ?? HubAdapter(), + _isMainIsolateCallback = isMainIsolateCallback ?? + (() { + return Isolate.current.debugName == 'main'; + }) { _hub.options.sdk.addIntegration('SentryFileTracing'); _hub.options.sdk.addPackage(packageName, sdkVersion); } final File _file; final Hub _hub; + final IsMainIsolateCallback _isMainIsolateCallback; @override Future copy(String newPath) { @@ -490,10 +497,10 @@ class SentryFile implements File { span?.origin = SentryTraceOrigins.autoFile; span?.setData('file.async', false); - final isolateName = Isolate.current.debugName; - if (isolateName != null && isolateName.isNotEmpty) { - span?.setData('file.isolate', isolateName); - } + + final isMainIsolate = _isMainIsolateCallback(); + span?.setData('blocked_main_thread', isMainIsolate); + final Map breadcrumbData = {}; breadcrumbData['file.async'] = false; diff --git a/file/test/sentry_file_test.dart b/file/test/sentry_file_test.dart index c41b940992..d6c5d9e110 100644 --- a/file/test/sentry_file_test.dart +++ b/file/test/sentry_file_test.dart @@ -3,7 +3,6 @@ @TestOn('vm') import 'dart:io'; -import 'dart:isolate'; import 'package:sentry/sentry.dart'; import 'package:sentry_file/sentry_file.dart'; @@ -35,9 +34,6 @@ void main() { .endsWith('test_resources/testfile.txt'), true); expect(span.origin, SentryTraceOrigins.autoFile); - if (!async) { - expect(span.data['file.isolate'], Isolate.current.debugName); - } } void _asserBreadcrumb(bool async) { @@ -125,9 +121,6 @@ void main() { .endsWith('test_resources/testfile_create.txt'), true); expect(span.origin, SentryTraceOrigins.autoFile); - if (!async) { - expect(span.data['file.isolate'], Isolate.current.debugName); - } } void _assertBreadcrumb(bool async, {int? size = 0}) { @@ -213,9 +206,6 @@ void main() { .endsWith('test_resources/testfile_delete.txt'), true); expect(span.origin, SentryTraceOrigins.autoFile); - if (!async) { - expect(span.data['file.isolate'], Isolate.current.debugName); - } } void _assertBreadcrumb(bool async, {int? size = 0}) { @@ -357,9 +347,6 @@ void main() { .endsWith('test_resources/$fileName'), true); expect(span.origin, SentryTraceOrigins.autoFile); - if (!async) { - expect(span.data['file.isolate'], Isolate.current.debugName); - } } void _assertBreadcrumb(String fileName, bool async, {int? size = 0}) { @@ -510,9 +497,6 @@ void main() { (span.data['file.path'] as String).endsWith('test_resources/$name'), true); expect(span.origin, SentryTraceOrigins.autoFile); - if (!async) { - expect(span.data['file.isolate'], Isolate.current.debugName); - } } void _assertBreadcrumb(bool async, String name) { @@ -673,6 +657,286 @@ void main() { ); }); }); + + group('$SentryFile span sets `blocked_main_thread`', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + tearDown(() {}); + + void _assertSpan(bool isMainIsolate) { + final call = fixture.client.captureTransactionCalls.first; + final span = call.transaction.spans.first; + expect(span.data['blocked_main_thread'], isMainIsolate); + } + + test('to `true` when calling `copySync` on main', () async { + final file = File('test_resources/testfile.txt'); + + final sut = fixture.getSut( + file, + sendDefaultPii: true, + tracesSampleRate: 1.0, + isMainIsolate: true, + ); + + final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true); + sut.copySync('test_resources/testfile_copy.txt'); + await tr.finish(); + + _assertSpan(true); + }); + + test('to `false` when not calling `copySync` on main', () async { + final file = File('test_resources/testfile.txt'); + + final sut = fixture.getSut( + file, + sendDefaultPii: true, + tracesSampleRate: 1.0, + isMainIsolate: false, + ); + + final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true); + final newFile = sut.copySync('test_resources/testfile_copy.txt'); + await tr.finish(); + + _assertSpan(false); + + newFile.deleteSync(); + }); + + test('to `true` when calling `createSync` on main', () async { + final file = File('test_resources/testfile.txt'); + + final sut = fixture.getSut( + file, + sendDefaultPii: true, + tracesSampleRate: 1.0, + isMainIsolate: true, + ); + + final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true); + sut.createSync(); + await tr.finish(); + + _assertSpan(true); + }); + + test('to `false` when not calling `createSync` on main', () async { + final file = File('test_resources/testfile.txt'); + + final sut = fixture.getSut( + file, + sendDefaultPii: true, + tracesSampleRate: 1.0, + isMainIsolate: false, + ); + + final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true); + sut.createSync(); + await tr.finish(); + + _assertSpan(false); + }); + + // deleteSync + + test('to `true` when calling `deleteSync` on main', () async { + final file = File('test_resources/testfile_delete.txt'); + file.createSync(); + expect(file.existsSync(), true); + + final sut = fixture.getSut( + file, + sendDefaultPii: true, + tracesSampleRate: 1.0, + isMainIsolate: true, + ); + + final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true); + sut.deleteSync(); + await tr.finish(); + + _assertSpan(true); + }); + + test('to `false` when not calling `deleteSync` on main', () async { + final file = File('test_resources/testfile_delete.txt'); + file.createSync(); + expect(file.existsSync(), true); + + final sut = fixture.getSut( + file, + sendDefaultPii: true, + tracesSampleRate: 1.0, + isMainIsolate: false, + ); + + final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true); + sut.deleteSync(); + await tr.finish(); + + _assertSpan(false); + }); + + // readAsBytesSync + + test('to `true` when calling `readAsBytesSync` on main', () async { + final file = File('test_resources/sentry.png'); + + final sut = fixture.getSut( + file, + sendDefaultPii: true, + tracesSampleRate: 1.0, + isMainIsolate: true, + ); + + final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true); + sut.readAsBytesSync(); + await tr.finish(); + + _assertSpan(true); + }); + + test('to `false` when not calling `readAsBytesSync` on main', () async { + final file = File('test_resources/testfile.txt'); + + final sut = fixture.getSut( + file, + sendDefaultPii: true, + tracesSampleRate: 1.0, + isMainIsolate: false, + ); + + final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true); + sut.readAsBytesSync(); + await tr.finish(); + + _assertSpan(false); + }); + + // readAsLinesSync + + test('to `true` when calling `readAsLinesSync` on main', () async { + final file = File('test_resources/testfile.txt'); + + final sut = fixture.getSut( + file, + sendDefaultPii: true, + tracesSampleRate: 1.0, + isMainIsolate: true, + ); + + final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true); + + sut.readAsLinesSync(); + + await tr.finish(); + + _assertSpan(true); + }); + + test('to `false` when not calling `readAsLinesSync` on main', () async { + final file = File('test_resources/testfile.txt'); + + final sut = fixture.getSut( + file, + sendDefaultPii: true, + tracesSampleRate: 1.0, + isMainIsolate: false, + ); + + final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true); + sut.readAsLinesSync(); + await tr.finish(); + + _assertSpan(false); + }); + + // readAsStringSync + + test('to `true` when calling `readAsStringSync` on main', () async { + final file = File('test_resources/testfile.txt'); + + final sut = fixture.getSut( + file, + sendDefaultPii: true, + tracesSampleRate: 1.0, + isMainIsolate: true, + ); + + final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true); + + sut.readAsStringSync(); + + await tr.finish(); + + _assertSpan(true); + }); + + test('to `false` when not calling `readAsStringSync` on main', () async { + final file = File('test_resources/testfile.txt'); + + final sut = fixture.getSut( + file, + sendDefaultPii: true, + tracesSampleRate: 1.0, + isMainIsolate: false, + ); + + final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true); + sut.readAsStringSync(); + await tr.finish(); + + _assertSpan(false); + }); + + // renameSync + + test('to `true` when calling `renameSync` on main', () async { + final file = File('test_resources/old_name.txt'); + file.createSync(); + + final sut = fixture.getSut( + file, + sendDefaultPii: true, + tracesSampleRate: 1.0, + isMainIsolate: true, + ); + + final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true); + final newFile = sut.renameSync('test_resources/testfile_copy.txt'); + await tr.finish(); + + _assertSpan(true); + + newFile.deleteSync(); + }); + + test('to `false` when not calling `renameSync` on main', () async { + final file = File('test_resources/old_name.txt'); + file.createSync(); + + final sut = fixture.getSut( + file, + sendDefaultPii: true, + tracesSampleRate: 1.0, + isMainIsolate: false, + ); + + final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true); + final newFile = sut.renameSync('test_resources/testfile_copy.txt'); + await tr.finish(); + + _assertSpan(false); + + newFile.deleteSync(); + }); + }); } class Fixture { @@ -683,6 +947,7 @@ class Fixture { SentryFile getSut( File file, { bool sendDefaultPii = false, + bool isMainIsolate = false, double? tracesSampleRate, }) { options.sendDefaultPii = sendDefaultPii; @@ -690,6 +955,12 @@ class Fixture { hub = Hub(options); hub.bindClient(client); - return SentryFile(file, hub: hub); + return SentryFile( + file, + hub: hub, + isMainIsolateCallback: () { + return isMainIsolate; + }, + ); } } From 39cf7f7a275bc03c209d1e2b76ad668572b458d1 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Tue, 16 Jan 2024 18:21:58 +0100 Subject: [PATCH 06/13] move callback to options --- dart/lib/src/sentry_options.dart | 9 +++++++++ file/lib/src/sentry_file.dart | 12 ++---------- file/test/sentry_file_test.dart | 11 ++++------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart index 918a5d5758..7d26e00541 100644 --- a/dart/lib/src/sentry_options.dart +++ b/dart/lib/src/sentry_options.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:developer'; +import 'dart:isolate'; import 'package:meta/meta.dart'; import 'package:http/http.dart'; @@ -446,6 +447,11 @@ class SentryOptions { late SentryStackTraceFactory stackTraceFactory = SentryStackTraceFactory(this); + @internal + IsMainIsolateCallback isMainIsolate = () { + return Isolate.current.debugName == 'main'; + }; + void _debugLogger( SentryLevel level, String message, { @@ -527,3 +533,6 @@ void dartLogger( stackTrace: stackTrace, ); } + +/// A callback used to check if we are on the main isolate. +typedef IsMainIsolateCallback = bool Function(); \ No newline at end of file diff --git a/file/lib/src/sentry_file.dart b/file/lib/src/sentry_file.dart index 8c8d3c146a..b02cd777f0 100644 --- a/file/lib/src/sentry_file.dart +++ b/file/lib/src/sentry_file.dart @@ -206,7 +206,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:isolate'; import 'dart:typed_data'; import 'package:meta/meta.dart'; import 'package:sentry/sentry.dart'; @@ -214,7 +213,6 @@ import 'package:sentry/sentry.dart'; import 'version.dart'; typedef Callback = FutureOr Function(); -typedef IsMainIsolateCallback = bool Function(); /// The Sentry wrapper for the File IO implementation that creates a span /// out of the active transaction in the scope and a breadcrumb, which gets @@ -240,19 +238,13 @@ class SentryFile implements File { SentryFile( this._file, { @internal Hub? hub, - @internal IsMainIsolateCallback? isMainIsolateCallback, - }) : _hub = hub ?? HubAdapter(), - _isMainIsolateCallback = isMainIsolateCallback ?? - (() { - return Isolate.current.debugName == 'main'; - }) { + }) : _hub = hub ?? HubAdapter() { _hub.options.sdk.addIntegration('SentryFileTracing'); _hub.options.sdk.addPackage(packageName, sdkVersion); } final File _file; final Hub _hub; - final IsMainIsolateCallback _isMainIsolateCallback; @override Future copy(String newPath) { @@ -498,7 +490,7 @@ class SentryFile implements File { span?.origin = SentryTraceOrigins.autoFile; span?.setData('file.async', false); - final isMainIsolate = _isMainIsolateCallback(); + final isMainIsolate = _hub.options.isMainIsolate(); span?.setData('blocked_main_thread', isMainIsolate); final Map breadcrumbData = {}; diff --git a/file/test/sentry_file_test.dart b/file/test/sentry_file_test.dart index d6c5d9e110..a4413732d6 100644 --- a/file/test/sentry_file_test.dart +++ b/file/test/sentry_file_test.dart @@ -952,15 +952,12 @@ class Fixture { }) { options.sendDefaultPii = sendDefaultPii; options.tracesSampleRate = tracesSampleRate; + options.isMainIsolate = () { + return isMainIsolate; + }; hub = Hub(options); hub.bindClient(client); - return SentryFile( - file, - hub: hub, - isMainIsolateCallback: () { - return isMainIsolate; - }, - ); + return SentryFile(file, hub: hub); } } From 33d40b171cc6dfd777c23f0f60e4bf2db341bcd9 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Tue, 16 Jan 2024 18:26:22 +0100 Subject: [PATCH 07/13] Use `ServicesBinding.rootIsolateToken` to check for main isolate in flutter --- flutter/lib/src/sentry_flutter.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 62a9043bc9..f07a32d9ab 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -121,6 +121,11 @@ mixin SentryFlutter { options.addEventProcessor(PlatformExceptionEventProcessor()); _setSdk(options); + + // ignore: invalid_use_of_internal_member + options.isMainIsolate = () { + return ServicesBinding.rootIsolateToken != null; + }; } /// Install default integrations From e6c860a2ca207fa508aadf6265e92e3508dde28d Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Tue, 16 Jan 2024 18:28:26 +0100 Subject: [PATCH 08/13] Update changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db63729c29..212fbe16b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,8 @@ - APM for isar ([#1726](https://github.com/getsentry/sentry-dart/pull/1726)) - Add isar breadcrumbs ([#1800](https://github.com/getsentry/sentry-dart/pull/1800)) - Starting with Flutter 3.16, Sentry adds the [`appFlavor`](https://api.flutter.dev/flutter/services/appFlavor-constant.html) to the `flutter_context` ([#1799](https://github.com/getsentry/sentry-dart/pull/1799)) -- Add isolate name to sync file spans ([#1801](https://github.com/getsentry/sentry-dart/pull/1801)) - Add beforeScreenshotCallback to SentryFlutterOptions ([#1805](https://github.com/getsentry/sentry-dart/pull/1805)) +- Add `blocked_main_thread` to sync file spans ([#1801](https://github.com/getsentry/sentry-dart/pull/1801)) ### Dependencies From 7e4b32bc1362615c9b88760b6bfd76fea55b7f81 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Tue, 16 Jan 2024 18:30:58 +0100 Subject: [PATCH 09/13] add newline --- dart/lib/src/sentry_options.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart index 7d26e00541..7ac94babf7 100644 --- a/dart/lib/src/sentry_options.dart +++ b/dart/lib/src/sentry_options.dart @@ -535,4 +535,4 @@ void dartLogger( } /// A callback used to check if we are on the main isolate. -typedef IsMainIsolateCallback = bool Function(); \ No newline at end of file +typedef IsMainIsolateCallback = bool Function(); From e36a6188d876215e34fbec2d58223ee58bb8a49b Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Wed, 17 Jan 2024 10:39:38 +0100 Subject: [PATCH 10/13] fix changelog --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95fbc51327..23336dd76d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,9 @@ - Add isar breadcrumbs ([#1800](https://github.com/getsentry/sentry-dart/pull/1800)) - Starting with Flutter 3.16, Sentry adds the [`appFlavor`](https://api.flutter.dev/flutter/services/appFlavor-constant.html) to the `flutter_context` ([#1799](https://github.com/getsentry/sentry-dart/pull/1799)) - Add beforeScreenshotCallback to SentryFlutterOptions ([#1805](https://github.com/getsentry/sentry-dart/pull/1805)) +- Add support for `readTransaction` in `sqflite` ([#1819](https://github.com/getsentry/sentry-dart/pull/1819)) - Add `blocked_main_thread` to sync file spans ([#1801](https://github.com/getsentry/sentry-dart/pull/1801)) -- Add support for `readTransaction` in `sqflite` ([#1819](https://github.com/getsentry/sentry-dart/pull/1819)) - ### Dependencies - Bump Android SDK from v7.0.0 to v7.1.0 ([#1788](https://github.com/getsentry/sentry-dart/pull/1788)) From fb7e78727aa9eea05b5e4e3cac1b9002ec117bf4 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Mon, 22 Jan 2024 12:48:34 +0100 Subject: [PATCH 11/13] Remove `ServicesBinding.rootIsolateToken` check in flutter options --- flutter/lib/src/sentry_flutter.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index f07a32d9ab..62a9043bc9 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -121,11 +121,6 @@ mixin SentryFlutter { options.addEventProcessor(PlatformExceptionEventProcessor()); _setSdk(options); - - // ignore: invalid_use_of_internal_member - options.isMainIsolate = () { - return ServicesBinding.rootIsolateToken != null; - }; } /// Install default integrations From 906e92a16be9a4b89280bab164c0df61f081c81a Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Tue, 23 Jan 2024 15:23:50 +0100 Subject: [PATCH 12/13] fix changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d1184f777..46f27f34fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Method `hint.addAll(Map keysAndValues)` now takes `Map` instead of `Map` - Method `set(String key, dynamic value)` now takes value of `dynamic` instead of `Object` - Method `hint.get(String key)` now returns `dynamic` instead of `Object?` +- Add `blocked_main_thread` to sync file spans ([#1801](https://github.com/getsentry/sentry-dart/pull/1801)) ## 7.15.0 @@ -24,7 +25,6 @@ - Starting with Flutter 3.16, Sentry adds the [`appFlavor`](https://api.flutter.dev/flutter/services/appFlavor-constant.html) to the `flutter_context` ([#1799](https://github.com/getsentry/sentry-dart/pull/1799)) - Add beforeScreenshotCallback to SentryFlutterOptions ([#1805](https://github.com/getsentry/sentry-dart/pull/1805)) - Add support for `readTransaction` in `sqflite` ([#1819](https://github.com/getsentry/sentry-dart/pull/1819)) -- Add `blocked_main_thread` to sync file spans ([#1801](https://github.com/getsentry/sentry-dart/pull/1801)) ### Dependencies From 03663b0e6ff4ada136836b6bcb6b41d8a2f0f23a Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Mon, 29 Jan 2024 14:01:57 +0100 Subject: [PATCH 13/13] add integration test in sample app --- .../integration_test/integration_test.dart | 65 ++++++++++++++++++- flutter/example/lib/main.dart | 14 +++- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/flutter/example/integration_test/integration_test.dart b/flutter/example/integration_test/integration_test.dart index c4c71edb41..e26c35b771 100644 --- a/flutter/example/integration_test/integration_test.dart +++ b/flutter/example/integration_test/integration_test.dart @@ -2,9 +2,13 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:sentry_file/sentry_file.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_flutter_example/main.dart'; import 'package:http/http.dart'; @@ -29,7 +33,7 @@ void main() { await tester.pumpWidget(SentryScreenshotWidget( child: DefaultAssetBundle( bundle: SentryAssetBundle(enableStructuredDataTracing: true), - child: const MyApp(), + child: const MyApp(withNavigatorObserver: false), ))); }, dsn ?? fakeDsn, @@ -141,6 +145,65 @@ void main() { await transaction.finish(); }); + testWidgets( + 'sync call in sentry file sets blocked_main_thread to true on main isolate', + (tester) async { + await setupSentryAndApp(tester); + final documentsDir = await getApplicationDocumentsDirectory(); + final file = File('${documentsDir.path}/testfile.txt').sentryTrace(); + + // Start a transaction if there's no active transaction + Sentry.startTransaction( + 'File', + 'file', + bindToScope: true, + ); + + file.createSync(); + + var span = Sentry.getSpan() as dynamic; + var fileSpan = span.children.first; + expect(fileSpan?.data['blocked_main_thread'], true); + }); + + testWidgets( + 'sync call in sentry file sets blocked_main_thread to false on background isolate', + (tester) async { + // Only setup app without sentry. + await tester.pumpWidget(SentryScreenshotWidget( + child: DefaultAssetBundle( + bundle: SentryAssetBundle(enableStructuredDataTracing: true), + child: const MyApp(withNavigatorObserver: false), + ))); + + final documentsDir = await getApplicationDocumentsDirectory(); + final file = File('${documentsDir.path}/testfile.txt').sentryTrace(); + + final blockedMainThread = await compute((path) async { + // Isolates do not share state. Init Sentry within isolate. + await Sentry.init((options) { + options.dsn = 'https://abc@def.ingest.sentry.io/1234567'; + options.tracesSampleRate = 1.0; + }); + + // Start a transaction if there's no active transaction + Sentry.startTransaction( + 'File', + 'file', + bindToScope: true, + ); + + final sentryFile = file.sentryTrace(); + sentryFile.createSync(); + + var span = Sentry.getSpan() as dynamic; + var fileSpan = span.children.first; + return fileSpan?.data['blocked_main_thread']; + }, file); + + expect(blockedMainThread, false); + }); + // group('e2e', () { // var output = find.byKey(const Key('output')); // late Fixture fixture; diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index 4cb5b97942..6135fe8777 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -93,13 +93,23 @@ Future setupSentry(AppRunner appRunner, String dsn, } class MyApp extends StatefulWidget { - const MyApp({Key? key}) : super(key: key); + const MyApp({Key? key, this.withNavigatorObserver = true}) : super(key: key); + + final bool withNavigatorObserver; @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State { + late bool withNavigatorObserver; + + @override + void initState() { + super.initState(); + withNavigatorObserver = widget.withNavigatorObserver; + } + @override Widget build(BuildContext context) { return feedback.BetterFeedback( @@ -109,7 +119,7 @@ class _MyAppState extends State { builder: (context) => MaterialApp( navigatorKey: navigatorKey, navigatorObservers: [ - SentryNavigatorObserver(), + if (withNavigatorObserver) SentryNavigatorObserver() ], theme: Provider.of(context).theme, home: const MainScaffold(),