Skip to content

[Bug]: iOS - Permission.location.status / .isGranted can hang the main thread for 2+ seconds #1557

Open

Description

@pfraunlobEurofunk

Please check the following before submitting a new issue.

Please select affected platform(s)

  • Android
  • iOS
  • Windows

Steps to reproduce

We collect production crash/ANR data via Sentry (App Hang detection, watchdog
threshold 2000ms). We see repeated "App Hanging" events on iOS whose main
thread stack trace is:

App Hanging: App hanging for at least 2000 ms.

  Runner              0x100794114  +[LocationPermissionStrategy permissionStatus:] (LocationPermissionStrategy.m:166)
  Runner              0x100792ec0  +[PermissionManager checkPermissionStatus:result:] (PermissionManager.m:23)
  Runner              0x100792a70  -[PermissionHandlerPlugin handleMethodCall:result:] (PermissionHandlerPlugin.m:30)
  Flutter             0x101a7dce4  __45-[FlutterMethodChannel setMethodCallHandler:]_block_invoke (FlutterChannels.mm:315)
  Flutter             0x1015dbdf0  flutter::PlatformMessageHandlerIos::HandlePlatformMessage (platform_message_handler_ios.mm:70)

This happens most frequently right at applicationDidBecomeActive /
AppLifecycleState.resumed, when app code calls Permission.location.status
or Permission.location.isGranted to detect whether the user changed the
location permission in Settings while the app was backgrounded.

Root cause: LocationPermissionStrategy.checkPermissionStatus: calls
+[CLLocationManager authorizationStatus] synchronously on whatever thread
invoked the method channel (the main/platform thread by default), with no
dispatch_async off-thread hop.

This is the same class of problem as #1002, which was fixed for
checkServiceStatus (CLLocationManager.locationServicesEnabled) in #1329 /
permission_handler_apple 9.4.5:

- (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        BOOL isEnabled = [CLLocationManager locationServicesEnabled];
        dispatch_async(dispatch_get_main_queue(), ^(void) {
            completionHandler(isEnabled ? ServiceStatusEnabled : ServiceStatusDisabled);
        });
    });
}

However, checkPermissionStatus: (used by .status / .isGranted) was never given the same treatment and is still synchronous today in the latest published version (9.6.1):

- (PermissionStatus)checkPermissionStatus:(PermissionGroup)permission {
    return [LocationPermissionStrategy permissionStatus:permission];
}

+ (PermissionStatus)permissionStatus:(PermissionGroup)permission {
    CLAuthorizationStatus authorizationStatus = [CLLocationManager authorizationStatus]; // synchronous, can block
    ...
}

Since PermissionManager.checkPermissionStatus:result: calls this synchronously and immediately passes the result to FlutterResult, the main thread is blocked for however long authorizationStatus takes internally (observed 2000ms+ on affected devices/iOS versions in production).

Possible fix

Mirroring the checkServiceStatus fix, checkPermissionStatus could dispatch the location-specific call off the main thread and hop back before invoking the result callback, e.g. (scoped to location permission groups only, to avoid touching the other ~10 permission strategies that don't have this issue):

+ (void)checkPermissionStatus:(enum PermissionGroup)permission result:(FlutterResult)result {
    id<PermissionStrategy> permissionStrategy = [PermissionManager permissionStrategy:permission];

    if (permission == PermissionGroupLocation || permission == PermissionGroupLocationAlways ||
        permission == PermissionGroupLocationWhenInUse) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            PermissionStatus status = [permissionStrategy checkPermissionStatus:permission];
            dispatch_async(dispatch_get_main_queue(), ^{ result(@(status)); });
        });
        return;
    }

    PermissionStatus status = [permissionStrategy checkPermissionStatus:permission];
    result(@(status));
}

Expected results

Permission.location.status / .isGranted should never be able to block the calling (main) thread for multiple seconds, consistent with how serviceStatus already behaves after #1329.

Actual results

Main thread hangs 2000ms+ inside [CLLocationManager authorizationStatus], reported by Sentry's App Hang detector on iOS, primarily right after the app becomes active again.

Code sample

Code sample
[Paste your code here]

Screenshots or video

Screenshots or video demonstration

[Upload media here]

Version

permission_handler: 13.0.1 / permission_handler_apple: 9.6.1

Flutter Doctor output

Doctor output
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.44.8, on macOS 26.5.2 25F84 darwin-arm64, locale de-AT)
[✓] Android toolchain - develop for Android devices (Android SDK version 37.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 26.6)
[✓] Chrome - develop for the web
[✓] Connected device (2 available)
[✓] Network resources

• No issues found!

Activity

  1. yuriibezhevets commented on Sep 25, 2026

    @yuriibezhevets

    We are seeing the same underlying issue in production through Permission.locationWhenInUse.request().
    Here is the relevant main-thread stack trace captured by Sentry:

    libsystem_kernel    0x234af9cd4  mach_msg2_trap
     libsystem_kernel    0x234afd2f4  mach_msg2_internal
     libsystem_kernel    0x234afd210  mach_msg_overwrite
     libsystem_kernel    0x234afd058  mach_msg
     libdispatch         0x1c2c52e90  _dispatch_mach_send_and_wait_for_reply
     libdispatch         0x1c2c53230  dispatch_mach_send_with_result_and_wait_for_reply
     libxpc              0x19e3cde00  xpc_connection_send_message_with_reply_sync
     Foundation          0x1877a89ec  __NSXPCCONNECTION_IS_WAITING_FOR_A_SYNCHRONOUS_REPLY__
     Foundation          0x1881f1250  -[NSXPCConnection _sendInvocation:orArguments:count:methodSignature:selector:withProxy:]
     Foundation          0x1881f0910  -[NSXPCConnection _sendSelector:withProxy:arg1:arg2:arg3:]
     Foundation          0x1877aacdc  _NSXPCDistantObjectSimpleMessageSend3
     CoreLocation        0x1a2a868f0  CLGetUncertaintyScaleFactors
     CoreLocation        0x1a294fca8  CLClientCreateWithBundleIdentifierAndPathWithWebsiteOnSilo
     CoreLocation        0x1a298118c  CLClientStopVehicleHeadingUpdates
     Runner              0x10117aa68  +[LocationPermissionStrategy permissionStatus:] (LocationPermissionStrategy.m:166)
     Runner              0x10117a5d8  -[LocationPermissionStrategy requestPermission:completionHandler:errorHandler:] (LocationPermissionStrategy.m:49)
     Runner              0x101179b0c  -[PermissionManager requestPermissions:completion:errorHandler:] (PermissionManager.m:60)
     Runner              0x101179518  -[PermissionHandlerPlugin handleMethodCall:result:] (PermissionHandlerPlugin.m:43)
     Flutter             0x105d15ce4  __45-[FlutterMethodChannel setMethodCallHandler:]_block_invoke (FlutterChannels.mm:315)
     Flutter             0x105873df0  flutter::PlatformMessageHandlerIos::HandlePlatformMessage (platform_message_handler_ios.mm:70)
    

    This confirms that requestPermission also performs the synchronous CLLocationManager.authorizationStatus lookup on the main
    thread. When Core Location takes multiple seconds to respond, the UI remains blocked for the entire duration.

    The proposed change to PermissionManager.checkPermissionStatus appears to cover .status and .isGranted, but may not cover this path because LocationPermissionStrategy.requestPermission performs its own status check.

    Could the fix also move the authorization status lookup inside requestPermission off the main thread?

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