Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zone missmatch on flutter web #1943

Open
kay4ik opened this issue Mar 20, 2024 · 25 comments · May be fixed by #2088
Open

Zone missmatch on flutter web #1943

kay4ik opened this issue Mar 20, 2024 · 25 comments · May be fixed by #2088
Assignees

Comments

@kay4ik
Copy link

kay4ik commented Mar 20, 2024

Platform

Flutter Web

Obfuscation

Disabled

Debug Info

Disabled

Doctor

[√] Flutter (Channel stable, 3.19.3, on Microsoft Windows [Version 10.0.19045.4046], locale de-DE)
• Flutter version 3.19.3 on channel stable at C:\Users\R081094\dev\flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision ba39319843 (13 days ago), 2024-03-07 15:22:21 -0600
• Engine revision 2e4ba9c6fb
• Dart version 3.3.1
• DevTools version 2.31.1

[√] Windows Version (Installed version of Windows is version 10 or higher)

[√] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
• Android SDK at C:\Users\R081094\AppData\Local\Android\Sdk
• Platform android-34, build-tools 34.0.0
• ANDROID_HOME = C:\Users\R081094\AppData\Local\Android\Sdk
• Java binary at: C:\Program Files\Android\Android Studio\jbr\bin\java
• Java version OpenJDK Runtime Environment (build 17.0.9+0--11185874)
• All Android licenses accepted.

[√] Chrome - develop for the web
• Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe

[√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.9.3)
• Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Community
• Visual Studio Community 2022 version 17.9.34701.34
• Windows 10 SDK version 10.0.20348.0

[√] Android Studio (version 2023.2)
• Android Studio at C:\Program Files\Android\Android Studio
• Flutter plugin can be installed from:
https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 17.0.9+0--11185874)

[√] VS Code (version 1.87.2)
• VS Code at C:\Users\R081094\AppData\Local\Programs\Microsoft VS Code
• Flutter extension version 3.84.0

[√] Connected device (3 available)
• Windows (desktop) • windows • windows-x64 • Microsoft Windows [Version 10.0.19045.4046]
• Chrome (web) • chrome • web-javascript • Google Chrome 122.0.6261.129
• Edge (web) • edge • web-javascript • Microsoft Edge 122.0.2365.92

[√] Network resources
• All expected network resources are available.

• No issues found!

Version

7.18.0

Steps to Reproduce

  1. create a Flutter project using all available platforms (iOS, MacOS, Windows, Android) and Web
  2. implement a Future<void> init() method wich is called in main() and initializes some stuff asynch.
  3. Your main() should look like this:
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await init(); // initializes the stuff asynch

  await SentryFlutter.init(
    (options) => options
      ..dsn = 'dsn'
      ..debug = kDebugMode
      ..attachViewHierarchy = true
      ..sampleRate = settings.general.acceptAnalysis == true ? 1 : 0, // Enables or disables error sending according to user settings

    appRunner: () => runApp(const ProviderScope(child: SchunkControlCenterApp())), // Riverpod should not care here
  );
}
  1. run your app in debug mode

Expected Result

I expected that the app starts normally without throwing an exception.

Actual Result

Following Exception is thrown:

The following assertion was thrown during runApp:
Zone mismatch.
The Flutter bindings were initialized in a different zone than is now being used. This will likely
cause confusion and bugs as any zone-specific configuration will inconsistently use the
configuration of the original binding initialization zone or this zone based on hard-to-predict
factors such as which zone was active when a particular callback was set.
It is important to use the same zone when calling `ensureInitialized` on the binding as when calling
`runApp` later.
To make this warning fatal, set BindingBase.debugZoneErrorsAreFatal to true before the bindings are
initialized (i.e. as the first statement in `void main() { }`).

When the exception was thrown, this was the stack:
dart-sdk/lib/_internal/js_dev_runtime/patch/core_patch.dart 945:28  get current
packages/flutter/src/foundation/binding.dart 495:29                 <fn>
packages/flutter/src/foundation/binding.dart 499:14                 debugCheckZone
packages/flutter/src/widgets/binding.dart 1212:17                   runApp
packages/schunk_cc/main.dart 34:22                                  <fn>
packages/sentry/src/sentry.dart 136:26                              <fn>
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 45:50  <fn>
dart-sdk/lib/async/zone.dart 1407:47                                _rootRunUnary
dart-sdk/lib/async/zone.dart 1308:19                                runUnary
dart-sdk/lib/async/future_impl.dart 162:18                          handleValue
dart-sdk/lib/async/future_impl.dart 838:44                          handleValueCallback
dart-sdk/lib/async/future_impl.dart 867:13                          _propagateToListeners
dart-sdk/lib/async/future_impl.dart 643:5                           [_completeWithValue]
dart-sdk/lib/async/future_impl.dart 713:7                           <fn>
dart-sdk/lib/async/zone.dart 1399:13                                _rootRun
dart-sdk/lib/async/zone.dart 1301:19                                run
dart-sdk/lib/async/zone.dart 1209:7                                 runGuarded
dart-sdk/lib/async/zone.dart 1249:23                                callback
dart-sdk/lib/async/schedule_microtask.dart 40:11                    _microtaskLoop
dart-sdk/lib/async/schedule_microtask.dart 49:5                     _startMicrotaskLoop
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 181:7  <fn>

Are you willing to submit a PR?

No

@kay4ik
Copy link
Author

kay4ik commented Mar 20, 2024

I also tried to wrap everythin inside main() with a runZoneGuarded() but that didn't work...

I think the problen is that The appRunner is initialized with a runZoneGuarded automatically on Web. There is no option to disable this ro run my own guarded zone on the web.

I cannot init my data inside the app runner because i need to give the user the option to diable Sentry (mandatory from my companys legal team). If there is a workaround by disabling Sentry programatically on another way, i'd be happy to use this.

@r-dev-limited
Copy link

Same here, all combinations result in that mismatch

@stefanosiano
Copy link
Member

stefanosiano commented Mar 22, 2024

hi @kay4ik and @r-dev-limited
You can find the reason in the docs.

The SDK already runs your init callback on an error handler, such as runZonedGuarded on Flutter versions prior to 3.3, or PlatformDispatcher.onError on Flutter versions 3.3 and higher, so that errors are automatically captured.

This means that WidgetsFlutterBinding.ensureInitialized() should be called within the runZonedGuarded
Please, let me know if this works for you.

void main() async {

  await runZonedGuarded(() async {
    await init(); // initializes the stuff asynch

    await SentryFlutter.init(
        (options) => options
          ..dsn = 'dsn'
          ..debug = kDebugMode
          ..attachViewHierarchy = true
          ..sampleRate = settings.general.acceptAnalysis == true ? 1 : 0, // Enables or disables error sending according to user settings
        appRunner: () async {
      WidgetsFlutterBinding.ensureInitialized();
      runApp(const ProviderScope(child: SchunkControlCenterApp())), // Riverpod should not care here
    });
  }, (exception, stackTrace) async {
    await Sentry.captureException(exception, stackTrace: stackTrace);
  });
}

@stefanosiano
Copy link
Member

As a workaround, you could use the beforeSend callback to drop all data sent to Sentry based on a flag reflecting users consent.
Something like

await SentryFlutter.init(
          (options) => options
        ..beforeSend = (event, {hint}) => userConsent ? event : null,
        ...

@kay4ik
Copy link
Author

kay4ik commented Mar 22, 2024

@stefanosiano

This means that WidgetsFlutterBinding.ensureInitialized() should be called within the runZonedGuarded
Please, let me know if this works for you.

No this is not working. The Zone missmatch occurs either way.

Thank you for the workaround idea! I will try it :)

@kay4ik
Copy link
Author

kay4ik commented Mar 26, 2024

So the workaround is working fine but i think it should be possible to initialize the app before sentry.

@stefanosiano
Copy link
Member

Let's add the workaround to the troubleshooting section of the docs.
Let's also see how we can initialize the app before Sentry

@flodaniel
Copy link

I run into the same issue but for me it is not user consent that I run before await SentryFlutter.init but some other async initialization logic that determines the Sentry dsn property. Can I ignore the crash or is there another workaround available?

@buenaflor
Copy link
Contributor

buenaflor commented May 27, 2024

@flodaniel

do errors etc.. still log to sentry despite the zone mismatch error?

@stefanosiano
Copy link
Member

The problem is that we always run runZonedGuarded on SentryFlutter.init (on web).
The way to fix the issues with runZonedGuarded would be to automatically check if we are inside a guarded zone during SentryFlutter.init, and skip the zone creation. Let's see if we can do it.
Otherwise we may add a flag - something like inGuardedZone or avoidZoneCreation - to pass to SentryFlutter.init to disable the zone creation

@buenaflor
Copy link
Contributor

Should be possible by checking if Zone.current == Zone.root then we at least know that no custom Zone has been created

@ueman
Copy link
Collaborator

ueman commented Jun 6, 2024

FWIW, Zone creation is skipped when no appRunner callback is passed.

So, as of now, you should not use the appRunner callback when creating the zone yourself. If you do not create the zone yourself, you should use the appRunner callback.

(On io platform zones and the appRunner callback are not needed)

@buenaflor
Copy link
Contributor

@ueman oh yeah, was there a specific reason why it was implement this way? (creating zone if appRunner callback is used otherwise skip)

@ueman
Copy link
Collaborator

ueman commented Jun 7, 2024

No clue, since that's code from before I started contributing.

But it's somewhat logical to do it that way since Sentry can only create a zone for you if Sentry is executing the appRunner callback. If the callback is not used, creating a zone doesn't actually do anything.

You need to be aware of this even if there would be an option for creating a zone. So adding an option would probably lead to just as much confusion, since you're still required to use the appRunner callback correctly.

@stefanosiano
Copy link
Member

Totally agree. I was confused, too 😅
we should add it to the troubleshoot in our docs, since the only place we wrote it is the github repo, I think
And then link it to other places like https://docs.sentry.io/platforms/flutter/usage/#capturing-errors

@buenaflor
Copy link
Contributor

Thoughts on skipping zone creation on web if the current zone != root zone in .init? (see #2088)

Or shall we rather keep it as it is and tell users to just not use the appRunner callback in case they want to have a guarded zone themselves?

@stefanosiano
Copy link
Member

i think it still makes sense, to make the SDK simpler to use

@stefanosiano
Copy link
Member

Thinking more about it, if we don't create a zone, the user will be responsible to manually add Sentry.captureException in his own zone creation, right?
So, the difference with now would be that there is no zone mismatch error anymore, but the user needs to be aware he has to add the captureException call.
Not a real problem, but we should make it crystal clear in the docs.
What do you think?
Is it better to:

  1. have an error that makes you more "aware" of the problem
  2. handle it gracefully with the users potentially not seeing their errors on sentry?

In both cases we should improve docs.

@buenaflor
Copy link
Contributor

Thinking more about it, if we don't create a zone, the user will be responsible to manually add Sentry.captureException in his own zone creation, right?

it's possible to decorate the user's onError callback with ours on top of it - so it would be possible to keep the default behaviour (reporting the error to Sentry) on top of the user's custom onError. Just need to add proper docs in that case that the user shouldn't report that exception again otherwise it would be sent twice

@buenaflor buenaflor self-assigned this Jun 20, 2024
@ekuleshov
Copy link

Another problem with internal Sentry's call to WidgetsFlutterBinding.ensureInitialized() is that it is called after the (options) { ... } callback is invoked, so you can't use any code in that callback that requires bindings to be initialized. For example, can't use the PackageInfo.fromPlatform() in order to set options.release.

On a side note. When code in the (options) { ... } callback throws any exception - nothing is shown on the console. Even when you have debug build running in the IDE. It makes it hard to catch these errors.

@stefanosiano
Copy link
Member

@ekuleshov
You can run your own runZonedGuarded and call WidgetsFlutterBinding.ensureInitialized()
The only thing is that when you init Sentry you don't have to pass the appRunner argument, instead run your app directly.
Something like this

void main() async {
  await runZonedGuarded(() async {
    WidgetsFlutterBinding.ensureInitialized();

    await SentryFlutter.init(
        (options) => options
          ..dsn = 'dsn'
          ...,
    );
    runApp(MyApp()),
  }, (exception, stackTrace) async {
    Sentry.captureException(exception, stackTrace: stackTrace);
  });
}

We are going to make it work even when passing the appRunner parameter and without the need to call Sentry.captureException(exception, stackTrace: stackTrace); in newer versions

@ziqq
Copy link

ziqq commented Jul 24, 2024

@stefanosiano, thx its work for me.

@ewann
Copy link

ewann commented Nov 8, 2024

As a (potential) customer, I'd encourage someone to consider the DX while this issue is prioritised. In spite of the onboarding flow I did find my way here today. On a day with worse google-fu, I may have just binned this off and used something/body else ;)

That said, it's certainly a positive to find #1943 (comment) and unblock my eval :)

@kahest
Copy link
Member

kahest commented Nov 8, 2024

@ewann that's fair feedback, thanks 👍

@buenaflor
Copy link
Contributor

@ewann thx for the feedback, I agree this needs to addressed.

I'll have a look here and see how we can improve the dx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: No status
Status: No status
Status: Backlog
Development

Successfully merging a pull request may close this issue.