Skip to content

SIGSEGV in -[VSyncClient initWithTaskRunner:callback:] at launch on ProMotion — release App Store build, not debug (iOS 26.5.2, Flutter 3.41.4) #190030

Open

Description

@fallmarcus3-maker

Filing per the stale-bot's closing instruction on #183900 ("If you find this problem please file a new issue with the same description, what happens, logs and the output of flutter doctor -v").

The reason this is worth a fresh issue: #183900 was closed after the discussion converged on "debug cold start, which is not currently supported" (comment). This report is a --release build downloaded from TestFlight/App Store, on GA iOS. JIT/debug is not involved, so that explanation does not cover it. There is also a 2026-07-13 comment on the closed issue reproducing it on GA iOS 26.5.2 with Flutter 3.44.6, i.e. after the close.

Steps to Reproduce

I do not have a minimal reproduction — this is a production app, and I develop on Windows with iOS builds on CI, so I cannot bisect on-device. What I can state precisely is the configuration and the fault, below. The crash is 100% deterministic on the affected device: iOS relaunched the app automatically and it died identically 4 seconds later.

  1. Flutter 3.41.4 app, UIScene lifecycle, implicit engine (stock Main.storyboard with customClass="FlutterViewController" + FlutterImplicitEngineDelegate).
  2. flutter build ipa --release, upload to App Store Connect, install via TestFlight/App Store.
  3. Cold launch from the home screen, untethered, on a ProMotion (120 Hz) device.

Expected results

App launches.

Actual results

EXC_BAD_ACCESS (SIGSEGV) at 0x0 roughly 0.15 s after process launch, inside -[VSyncClient initWithTaskRunner:callback:].

Attaching the debugger masks it — it slows startup enough that the engine finishes attaching before viewDidLoad runs. This matches what others reported on #183900.

Analysis

I disassembled the instructionByteStream embedded in the .ips. At PC:

LDR X8, [X20]        <-- faults; X20 == 0 (threadState x[20] == 0x0)
LDR X8, [X8]         <-- first vtable slot
ADD X1, SP, #0x18    <-- arg1 = the std::function closure built immediately above
MOV X0, X20          <-- arg0 = this
BLR X8               <-- virtual call

and immediately before PC:

LDR X21, [X19, #0x28] ; MOV X0, X21 ; BL <objc_retain>
LDR X20, [X20]                          <-- deref the RefPtr, yields 0
ADRP/ADD X8, <vtable for std::__fl::__function::__func<-[VSyncClient initWithTaskRunner:callback:]::$_1, ...>>
STP X8, X21, [SP, #0x18]                <-- build the closure capturing the retained CADisplayLink

So this is task_runner->PostTask(closure) — vsync_waiter_ios.mm:86 — a virtual call through a null fml::TaskRunner. The register dump corroborates it independently: x8 is symbolized by the crash reporter as exactly that lambda's std::function vtable, far = 0x0, esr = 0x92000006 (data abort, byte read, translation fault).

The chain:

  • -[FlutterViewController createTouchRateCorrectionVSyncClientIfNeeded] (FlutterViewController.mm:1391-1392) passes self.engine.platformTaskRunner straight into -[VSyncClient initWithTaskRunner:callback:].
  • -[FlutterEngine platformTaskRunner] returns an empty fml::RefPtr when _shell is null (FlutterEngine.mm:440-445).
  • -[VSyncClient initWithTaskRunner:callback:] guards it only with FML_DCHECK(task_runner) (vsync_waiter_ios.mm:73), which is compiled out in release, then dereferences it at :86.

So in release there is no guard at all between "engine has no shell" and a null-pointer virtual call.

Why ProMotion only: FlutterViewController.mm:1380 early-returns when DisplayLinkManager.displayRefreshRate < 60.1. On a 60 Hz device or the Simulator this line is never reached, so the same underlying state stays invisible. Testing on non-ProMotion hardware proves nothing here.

What I could not determine is why the engine has no shell at viewDidLoad — and I want to be upfront that a strict reading of the 3.41.4 source seems to exclude every candidate state I could enumerate:

  • _engine being nil: awakeFromNib always calls sharedSetupWithProject: when _engine is nil, and that method's only early return needs [[FlutterEngine alloc] initWithName:...] to return nil, whose only nil path is the EnableTracingIfNecessary guard — and ptrace_check.h makes that a literal return true outside FLUTTER_RUNTIME_MODE_DEBUG.
  • A non-nil engine with a null shell: sharedSetupWithProject: sets _engineNeedsLaunch = YES at :331, so viewDidLoad would take the launchEngine branch first, and a null shell there faults at a non-zero offset inside Shell::RunEngine (which reads task_runners_.GetPlatformTaskRunner()), not at 0x0 inside VSyncClient.

One of those premises must be wrong on iOS 26.x. Two things I noticed that might be worth a look from someone who can run this on-device:

  1. -[FlutterEngine createShell:libraryURI:initialRoute:] is called at FlutterViewController.mm:302 with its BOOL return discarded, and _engineNeedsLaunch = YES is set unconditionally at :331 regardless. -[FlutterEngine run] by contrast gates the launch on shell-creation success. A silent createShell failure on the implicit path would therefore be invisible until something dereferences the shell.
  2. FlutterViewController's engineAllowHeadlessExecution has no implementation in the framework (declared FlutterViewController.h:255, used :290), so it auto-synthesizes to NO — meaning the implicit engine calls destroyContext / _shell.reset() when its controller deallocates (FlutterEngine.mm:553-563). That looks like a plausible mechanism for the scene-reconnect variant reported on [iOS 26 Beta] Crash (SIGSEGV) in -[VSyncClient initWithTaskRunner:callback:] on ProMotion devices using implicit engine #183900 (background 20+ min, return, iOS having discarded and recreated the scene): a live engine with a reset shell.

Configuration is stock

Worth stating explicitly, because the obvious first guess is a botched UIScene migration. I diffed our iOS config against packages/flutter_tools/templates/app/ios.tmpl/ in the 3.41.4 SDK and it matches:

  • Info.plist — the template itself ships both UISceneStoryboardFile=Main inside the scene config (Info.plist.tmpl:44-45) and top-level UIMainStoryboardFile=Main (:54-55), plus CADisableMinimumFrameDurationOnPhone (:5-6). Ours had all three.
  • AppDelegate.swift — identical to the template's (FlutterAppDelegate + FlutterImplicitEngineDelegate, registering in didInitializeImplicitFlutterEngine) apart from two push-notification lines.
  • Main.storyboard — unmodified template storyboard, single FlutterViewController initial VC.
  • Only deviation: we named FlutterSceneDelegate directly in Info.plist where the template uses an empty class SceneDelegate: FlutterSceneDelegate {} behind $(PRODUCT_MODULE_NAME).SceneDelegate. Behaviourally equivalent.

CADisableMinimumFrameDurationOnPhone is not the trigger, in case that comes up: it is read only by +[DisplayLinkManager maxRefreshRateEnabledOnIPhone] and consumed by -[VSyncClient setMaxRefreshRate:]. The branch deciding whether to build the VSyncClient at all reads DisplayLinkManager.displayRefreshRate, which falls through to UIScreen.mainScreen.maximumFramesPerSecond regardless of the plist. Removing it loses 120 Hz and still crashes.

Workaround

Abandoning the implicit engine fixes it — consistent with what others found on #183900. Create and run() an explicit FlutterEngine in application(_:didFinishLaunchingWithOptions:), register plugins against it there, and use a SceneDelegate: FlutterSceneDelegate that builds the window with FlutterViewController(engine:). -initWithEngine: sets _engineNeedsLaunch = NO and calls setViewController:, so viewDidLoad takes the engine.viewController == self branch against an already-shelled engine and the null runner becomes unreachable.

That is a bypass, not a fix. Regardless of the underlying cause, I'd argue vsync_waiter_ios.mm:86 should not be an unguarded deref in release — #184639 proposed exactly that and was closed unmerged. The counter-argument there was that silencing it would mask the real problem; the error screen suggested in that thread instead of a SIGSEGV would satisfy both, and would have made this far cheaper to diagnose than disassembling .ips byte streams.

Crash log

Thread 0 (crashed), com.apple.main-thread. Device identifiers redacted; otherwise verbatim.

Incident Identifier: <redacted>
Hardware Model:      iPhone18,2
Process:             Runner [13586]
Version:             2.10.0 (51)
Code Type:           ARM-64 (Native)
Role:                Foreground
OS Version:          iPhone OS 26.5.2 (23F84)

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
Exception Codes: 0x0000000000000001, 0x0000000000000000
VM Region Info: 0 is not in any region.  Bytes before following region: 4339662848
Termination Reason: SIGNAL 11 Segmentation fault: 11
Triggered by Thread:  0

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:  (symbol + symbolLocation, verbatim from the .ips `frames` array;
                    load addresses omitted rather than reconstructed)
0   Flutter            -[VSyncClient initWithTaskRunner:callback:] + 300
1   Flutter            -[FlutterViewController createTouchRateCorrectionVSyncClientIfNeeded] + 216
2   Flutter            -[FlutterViewController viewDidLoad] + 396
3   UIKitCore          -[UIViewController _sendViewDidLoadWithAppearanceProxyObjectTaggingEnabled] + 84
4   UIKitCore          -[UIViewController loadViewIfRequired] + 968
5   UIKitCore          -[UIViewController view] + 24
6   UIKitCore          -[UIWindow addRootViewControllerViewIfPossible] + 144
7   UIKitCore          -[UIWindow _updateLayerOrderingAndSetLayerHidden:actionBlock:] + 180
8   UIKitCore          -[UIWindow _setHidden:forced:] + 260
9   UIKitCore          -[UIWindow _mainQueue_makeKeyAndVisible] + 40
10  UIKitCore          -[UIWindowScene _performDeferredInitialWindowUpdateForConnection] + 216
11  UIKitCore          +[UIScene _sceneForFBSScene:create:withSession:connectionOptions:] + 1196
12  UIKitCore          -[UIApplication _connectUISceneFromFBSScene:transitionContext:] + 1200
13  UIKitCore          -[UIApplication workspace:didCreateScene:withTransitionContext:completion:] + 328
14  UIKitCore          -[UIApplicationSceneClientAgent scene:didInitializeWithEvent:completion:] + 304
15  FrontBoardServices __95-[FBSScene _callOutQueue_didCreateWithTransitionContext:alternativeCreationCallout:completion:]_block_invoke + 364
16  FrontBoardServices -[FBSScene _callOutQueue_maybeCoalesceClientSettingsUpdates:] + 132
17  FrontBoardServices -[FBSScene _callOutQueue_didCreateWithTransitionContext:alternativeCreationCallout:completion:] + 432
18  FrontBoardServices __93-[FBSWorkspaceScenesClient _callOutQueue_sendDidCreateForScene:transitionContext:completion:]_block_invoke.349 + 252
19  FrontBoardServices -[FBSWorkspace _calloutQueue_executeCalloutFromSource:withBlock:] + 176
20  FrontBoardServices -[FBSWorkspaceScenesClient _callOutQueue_sendDidCreateForScene:transitionContext:completion:] + 472
21  FrontBoardServices __92-[FBSWorkspaceScenesClient createSceneWithIdentity:parameters:transitionContext:completion:]_block_invoke_2 + 256
22  FrontBoardServices -[FBSWorkspace _calloutQueue_executeCalloutFromSource:withBlock:] + 176
23  libdispatch.dylib  _dispatch_client_callout + 16
24  libdispatch.dylib  _dispatch_block_invoke_direct + 284
25  BoardServices      __BSSERVICEMAINRUNLOOPQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 52
26  BoardServices      BSServiceMainRunLoopSourceHandler + 224
27  CoreFoundation     __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
28  CoreFoundation     __CFRunLoopDoSource0 + 172
29  CoreFoundation     __CFRunLoopDoSources0 + 332
30  CoreFoundation     __CFRunLoopRun + 820
31  CoreFoundation     _CFRunLoopRunSpecificWithOptions + 532
32  GraphicsServices   GSEventRunModal + 120
33  UIKitCore          -[UIApplication _run] + 796
34  UIKitCore          UIApplicationMain + 332
35  UIKitCore          <imageOffset 2721304>
36  Runner             static UIApplicationDelegate.main() + 128
37  Runner             static AppDelegate.$main() + 44
38  Runner             main + 28
39  dyld               start + 6928

Thread 0 crashed with ARM Thread State (64-bit):
    x20: 0x0000000000000000   <-- the null fml::TaskRunner
     x8: symbolized by the crash reporter as
         "vtable for std::__fl::__function::__func<-[VSyncClient initWithTaskRunner:callback:]::$_1,
          std::__fl::allocator<-[VSyncClient initWithTaskRunner:callback:]::$_1>, void ()>"
          (symbolLocation 16)
    esr: 0x92000006 (Data Abort) byte read Translation fault
    far: 0x0000000000000000
    pc:  matchesCrashFrame = 1

instructionByteStream.atPC (base64, as emitted by the crash reporter — this is what I disassembled):
    iAJA+QgBQPnhYwCR4AMUqgABP9bgG0D5HwAW64AAAFTgAAC0CAWAUg==
instructionByteStream.beforePC:
    dRZA+eADFapjECWUlAJA+WEQJZQI9wDQCMEikejXAan2YwCR9hsA+Q==

Binary Images (uuid + path; the two that matter):
    <e99862a0-6d8c-38ef-b0ff-083f80599e32> arm64  Runner
        .../Runner.app/Runner
    <4c4c441b-5555-3144-a1fa-318e70efe420> arm64  Flutter
        .../Runner.app/Frameworks/Flutter.framework/Flutter

bundleInfo: {"CFBundleShortVersionString":"2.10.0","CFBundleVersion":"51","DTAppStoreToolsBuild":"17F106"}
storeInfo: {"thirdParty":true, ...} — i.e. installed from the store, not a debug install.

flutter doctor -v

[√] Flutter (Channel stable, 3.41.4, on Microsoft Windows [Version 10.0.26200.8875], locale en-US) [579ms]
    • Flutter version 3.41.4 on channel stable at C:\...\flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision ff37bef603 (5 months ago), 2026-03-03 16:03:22 -0800
    • Engine revision e4b8dca3f1
    • Dart version 3.11.1
    • DevTools version 2.54.1
    • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop,
      enable-android, enable-ios, cli-animations, enable-native-assets, omit-legacy-version-file,
      enable-lldb-debugging, enable-uiscene-migration

[√] Windows Version (11 Home 64-bit, 25H2, 2009) [1,923ms]

[√] Android toolchain - develop for Android devices (Android SDK version 36.1.0) [4.6s]
    • Android SDK at C:\Users\...\Android\sdk
    • Platform android-36.1, build-tools 36.1.0
    • Java version OpenJDK Runtime Environment (build 21.0.9+-14649483-b1163.86)
    • All Android licenses accepted.

[√] Chrome - develop for the web [273ms]
[√] Visual Studio - develop Windows apps (Visual Studio Build Tools 2022 17.14.35 (June 2026)) [271ms]
[√] Connected device (3 available) [2.5s]
[√] Network resources [669ms]

• No issues found!

Note: I develop on Windows; the iOS builds are produced on macOS CI (Codemagic, Xcode 26, flutter build ipa --release), so flutter doctor shows no Xcode section. Deployment target iOS 15, use_frameworks! :linkage => :static, use_modular_headers!.

Related: #183900 (closed), #184639 (closed unmerged), #182108.

Activity

  1. added
    c: crashStack traces logged to the console
    platform-iosiOS applications specifically
    engineflutter/engine related. See also e: labels.
    fyi-engineFor the attention of Engine team
    team-iosOwned by iOS platform team
    on Jul 27, 2026
  2. gaaclarke commented on Jul 27, 2026

    @gaaclarke
    Member

    We've had problems with promotion in the past since it got added after-the-fact for some of this logic. Let us know if you need someone to bounce ideas off of. I don't have anything to add, someone will need to investigate it.

  3. added
    P2Important issues not at the top of the work list
    triaged-iosTriaged by iOS platform team
    on Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listc: crashStack traces logged to the consoleengineflutter/engine related. See also e: labels.platform-iosiOS applications specificallyteam-iosOwned by iOS platform teamtriaged-iosTriaged by iOS platform team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions