Skip to content

Fix macOS broker deadlock by processing CFRunLoop in MacMainThreadScheduler - #5966

Open
embetten wants to merge 4 commits into
AzureAD:mainfrom
embetten:users/embetten/macos-broker-deadlock-fix
Open

Fix macOS broker deadlock by processing CFRunLoop in MacMainThreadScheduler#5966
embetten wants to merge 4 commits into
AzureAD:mainfrom
embetten:users/embetten/macos-broker-deadlock-fix

Conversation

@embetten

Copy link
Copy Markdown

On macOS, MacMainThreadScheduler.StartMessageLoop() uses Thread.Sleep to wait between processing queued actions. However, the native MSAL broker runtime may use dispatch_sync to the main GCD queue (e.g. during FallbackToNativeMsal on devices without an SSO extension). Because Thread.Sleep does not service the GCD queue, dispatch_sync blocks forever waiting for the main thread — resulting in a deadlock.

This PR replaces Thread.Sleep with CFRunLoopRunInMode on macOS, which processes pending GCD dispatch calls while waiting. On non-macOS platforms, the existing Thread.Sleep behavior is preserved.

Fixes #
#5940

Changes proposed in this request

  • Add CoreFoundationInterop nested class with P/Invoke bindings to macOS CoreFoundation (CFRunLoopRunInMode, CFStringCreateWithCString)
  • In StartMessageLoop, call CoreFoundationInterop.ProcessRunLoop() instead of Thread.Sleep when running on macOS
  • P/Invoke bindings are isolated in a nested class so the DllImport is only JIT-resolved on macOS

Testing

  • Manually tested on a macOS ARM64 device using Azure Artifacts credential providers with the MSAL broker. Confirmed the deadlock no longer occurs during FallbackToNativeMsal on a non-Intune macOS device without the SSO extension.
  • Confirmed broker still works with SSO enabled on a macOS ARM64.
  • Manually tested WAM on a windows machine.

Performance impact
CFRunLoopRunInMode with the same sleep interval behaves equivalently to Thread.Sleep for timing purposes, while additionally servicing the run loop. No measurable performance difference expected.

Documentation
I am not aware of any related documentation

…eduler

On macOS, the native MSAL broker runtime may use dispatch_sync to the main GCD queue (e.g. during FallbackToNativeMsal on devices without an SSO extension). The existing Thread.Sleep in the message loop prevents the main thread from servicing the GCD queue, causing a deadlock.

Replace Thread.Sleep with CFRunLoopRunInMode on macOS so dispatch_sync calls targeting the main queue can execute while the scheduler waits for new work.
Copilot AI review requested due to automatic review settings April 30, 2026 23:08
@embetten
embetten requested a review from a team as a code owner April 30, 2026 23:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Fixes a macOS deadlock in MacMainThreadScheduler.StartMessageLoop() by pumping the CoreFoundation run loop (servicing GCD main-queue dispatch_sync) instead of using Thread.Sleep when running on macOS.

Changes:

  • Added macOS CoreFoundation P/Invoke bindings in a nested CoreFoundationInterop class.
  • Replaced Thread.Sleep with CFRunLoopRunInMode on macOS while preserving Thread.Sleep elsewhere.

Comment on lines +63 to +73
private static IntPtr s_defaultMode;

private static IntPtr GetDefaultMode()
{
if (s_defaultMode == IntPtr.Zero)
{
s_defaultMode = CFStringCreateWithCString(
IntPtr.Zero, "kCFRunLoopDefaultMode", KCFStringEncodingUTF8);
}

return s_defaultMode;
Comment thread src/client/Microsoft.Identity.Client/Utils/MacMainThreadScheduler.cs Outdated
@embetten

Copy link
Copy Markdown
Author

Copilot apply changes based on the comments in this thread

@gladjohn

Copy link
Copy Markdown
Contributor

Ashok Kumar Ramakrishnan (@ashok672) fengga can you review this please

Copilot AI review requested due to automatic review settings May 5, 2026 16:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.

Comment on lines +59 to +66
[DllImport(CoreFoundationLib)]
private static extern IntPtr CFStringCreateWithCString(
IntPtr alloc, string cStr, uint encoding);

private static readonly Lazy<IntPtr> s_defaultMode = new Lazy<IntPtr>(() =>
CFStringCreateWithCString(IntPtr.Zero, "kCFRunLoopDefaultMode", KCFStringEncodingUTF8));

private static IntPtr GetDefaultMode() => s_defaultMode.Value;
Comment on lines +54 to +61
[DllImport(CoreFoundationLib)]
private static extern int CFRunLoopRunInMode(
IntPtr mode, double seconds,
[MarshalAs(UnmanagedType.I1)] bool returnAfterSourceHandled);

[DllImport(CoreFoundationLib)]
private static extern IntPtr CFStringCreateWithCString(
IntPtr alloc, string cStr, uint encoding);

@fengga fengga left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, but I'd say Thread.Sleep() in this class is by design, and cannot replaced by RunloopRun() here - RunloopRun is called inside MSALRuntime. Let's fix the issues from other angles

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants