Skip to content

App hangs before main() on a physical device when linking MLX #485

Description

@deadlyserious

Environment

mlx-swift 0.31.6
Device iPhone 17 Pro Max (iPhone18,2), iOS 27.0 (24A437)
Xcode 27.0 (27A266a), Swift 6.4
Host macOS 27.0, Apple silicon
Config Debug, automatic signing, free developer profile

What happens

A SwiftUI app that links MLX never reaches main() on the device. The process
is created and stays alive, but no application code runs, and iOS's launch
watchdog eventually kills it. devicectl device process launch blocks
indefinitely rather than returning.

The same binary launches normally on the iOS Simulator, and the app launched
normally before MLX was added.

Evidence that app code never runs

The first statement in App.init() appends a line to a file in the app's
Documents directory. (A file rather than print, because an iOS app's stdout
goes nowhere without a debugger attached, and the trace has to survive a launch
that never completes.)

After repeated launch attempts:

  • Documents/ was never written — so App.init() never ran.
  • Library/Caches/<bundle-id>/com.apple.metal/{libraries,functions}.data was
    written during each attempt, so Metal initialisation did run.

Since Cmlx's C++ global constructors reference Metal (they are what makes
_MTLIOErrorDomain / _MTLTensorDomain undefined when linking for the
simulator — #341), those initialisers run at dyld load time, before main().
That is consistent with what the filesystem shows.

Relaunching with an already-warm Metal shader cache hung identically, so this is
not first-run shader compilation being slow.

Minimal reproduction

A SwiftUI app whose only dependency is MLX, which touches MLX only inside
.task — long after launch — so a hang before that point cannot be attributed
to application code calling into MLX.

import SwiftUI
import MLX

@main
struct ReproApp: App {
    init() { Self.trace("app init reached") }

    var body: some Scene {
        WindowGroup { ContentView() }
    }

    static func trace(_ message: String) {
        let line = "\(Date().timeIntervalSince1970) \(message)\n"
        guard let documents = FileManager.default.urls(
            for: .documentDirectory, in: .userDomainMask
        ).first else { return }
        let url = documents.appendingPathComponent("trace.log")
        if let handle = try? FileHandle(forWritingTo: url) {
            handle.seekToEndOfFile()
            handle.write(Data(line.utf8))
            try? handle.close()
        } else {
            try? Data(line.utf8).write(to: url)
        }
    }
}

struct ContentView: View {
    @State private var result = "body not evaluated yet"

    var body: some View {
        VStack(spacing: 16) {
            Text("MLX launch repro").font(.headline)
            Text(result).font(.footnote).monospaced()
        }
        .task {
            ReproApp.trace("view appeared")
            let sum = (MLXArray([1, 2, 3]) + MLXArray([4, 5, 6])).sum().item(Int.self)
            result = "MLX computed \(sum)"
            ReproApp.trace("mlx computed \(sum)")
        }
    }
}

Expected: trace.log contains app init reached, then view appeared.

What I have not been able to isolate

I want to be straight about the limits of this report.

The app where I observed the hang links MLX through
mlalma/kokoro-ios and
mlalma/MisakiSwift, so I have not
proved MLX alone is sufficient to reproduce it
— the trigger could be another
initialiser in that chain that reaches into MLX. The repro above is written to
answer exactly that question, but I could not install it: a free developer
profile cannot mint a provisioning profile for a new bundle id from the command
line, and the device is already at the three-app limit.

If someone can run the repro on a device, that should settle it quickly. I am
happy to gather anything else useful.

Notes

  • Also reproduced with the app's own Documents write as the very first line of
    init(), ruling out SwiftData, audio session setup and other app-level work.
  • Memory is not a plausible cause: the hang happens before any allocation, and
    running-on-ios.md discusses jetsam, which terminates rather than hangs.

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions