Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

Expand Down Expand Up @@ -37,6 +38,45 @@ public class MacMainThreadScheduler
// Configurable sleep time in milliseconds in the message loop.
private const int WorkerSleepInMilliseconds = 10;

/// <summary>
/// P/Invoke bindings for macOS CoreFoundation CFRunLoop.
/// Isolated in a nested class so the DllImport is only JIT-resolved on macOS.
/// Processing the CFRunLoop is required to prevent deadlocks when the native
/// MSAL broker runtime uses dispatch_sync to the main GCD queue (e.g. during
/// FallbackToNativeMsal on devices without an SSO extension).
/// </summary>
private static class CoreFoundationInterop
{
private const string CoreFoundationLib =
"/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
private const uint KCFStringEncodingUTF8 = 0x08000100;

[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);
Comment on lines +54 to +61

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 +59 to +66

/// <summary>
/// Process the macOS CFRunLoop for up to the given duration, servicing all
/// pending sources (including GCD dispatch_sync calls targeting the main queue)
/// until the timeout elapses. This prevents the deadlock that occurs when native
/// broker code falls back to MSAL native on non-Intune macOS devices.
/// </summary>
public static void ProcessRunLoop(double seconds)
{
CFRunLoopRunInMode(GetDefaultMode(), seconds, false);
}
Comment thread
embetten marked this conversation as resolved.
}

// Singleton mode
private static readonly Lazy<MacMainThreadScheduler> _instance = new Lazy<MacMainThreadScheduler>(() => new MacMainThreadScheduler());

Expand Down Expand Up @@ -132,6 +172,7 @@ public void StartMessageLoop()

_isRunning = true;
_workerFinished = false;
bool isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
try
{
while (!_workerFinished)
Expand All @@ -152,7 +193,18 @@ public void StartMessageLoop()
}
}
// Sleep for a short interval to avoid busy-waiting and reduce CPU usage while waiting for new actions in the queue.
Thread.Sleep(WorkerSleepInMilliseconds);
// On macOS, we must process the CFRunLoop instead of sleeping. The native MSAL broker
// runtime may use dispatch_sync to the main GCD queue (e.g. during FallbackToNativeMsal
// on devices without an SSO extension). Without processing the run loop, dispatch_sync
// blocks forever because the main thread never services the GCD queue → deadlock.
if (isMacOS)
{
CoreFoundationInterop.ProcessRunLoop(WorkerSleepInMilliseconds / 1000.0);
}
else
{
Thread.Sleep(WorkerSleepInMilliseconds);
}
}
}
finally
Expand Down
Loading