[PATCH v4 0/1] MR9579: Draft: ntdll: On macOS, transform the main thread into a Wine thread (running a CFRunLoop) on launch.
Currently, the process's main thread runs `main()` -> `__wine_main()` -> `apple_main_thread()` -> `CFRunLoopRun()`. A run loop source is added that creates the Wine main thread and runs `start_main_thread()`. With this MR, the Wine main thread calls `transform_mac_main_thread()` in `server_init_process()`. That essentially calls `NtCreateThreadEx()`, but instead of creating a new pthread it adds a source to the main thread run loop to run `start_thread()`. This transforms the process main thread into a Wine thread, after that it calls the PE `__wine_mac_run_cfrunloop()` in ntdll. That does a run_mac_cfrunloop Unix call, which just runs `CFRunLoopRun()`. The process main thread is now a Wine thread, but is otherwise still a normal Mac application main thread (including the ability for winemac to transform into a Cocoa application). In theory it might be possible to avoid the initial `CFRunLoopRun()` and have `apple_main_thread()` just spawn the Wine main thread and wait to run `start_thread()`, but the extra `CFRunLoopRun()` on the stack doesn't hurt anything. An exception/crash on the process main thread will be caught like a normal syscall fault, but `__wine_mac_run_cfrunloop()` prints an error and terminates the process. I think this is an improvement over the current behavior, which is a segfault in the signal handler when it tries to access the non-existent TEB that results in the thread hanging and eating 100% CPU. (Arguably it would be even better to unregister the signal handler and re-throw the signal so the user gets a normal macOS crash report with a helpful backtrace, maybe this could be done later.) Besides exception handling, having the main thread be a Wine thread will also allow the full use of Wine logging functions (!9503), and should allow winemac to be simplified (where many AppKit functions must be called from the main thread, and events are delivered to the main thread). -- v4: ntdll: Convert the macOS main thread into a Wine system thread on launch. https://gitlab.winehq.org/wine/wine/-/merge_requests/9579
From: Brendan Shanks <bshanks@codeweavers.com> Based on a patch from Rémi Bernon. --- dlls/ntdll/unix/server.c | 1 + dlls/ntdll/unix/thread.c | 38 ++++++++++++++++++++++++++++++++++ dlls/ntdll/unix/unix_private.h | 3 +++ 3 files changed, 42 insertions(+) diff --git a/dlls/ntdll/unix/server.c b/dlls/ntdll/unix/server.c index 1629ee63e63..f084bfb2d7e 100644 --- a/dlls/ntdll/unix/server.c +++ b/dlls/ntdll/unix/server.c @@ -1754,6 +1754,7 @@ void server_init_process_done(void) #ifdef __APPLE__ send_server_task_port(); + convert_mac_main_thread(); #endif /* Install signal handlers; this cannot be done earlier, since we cannot diff --git a/dlls/ntdll/unix/thread.c b/dlls/ntdll/unix/thread.c index 55e52af402a..a1279ff28c5 100644 --- a/dlls/ntdll/unix/thread.c +++ b/dlls/ntdll/unix/thread.c @@ -63,6 +63,7 @@ #endif #ifdef __APPLE__ +#include <CoreFoundation/CoreFoundation.h> #include <mach/mach.h> #endif #ifdef __FreeBSD__ @@ -1329,6 +1330,24 @@ static NTSTATUS spawn_thread( struct thread_data *data ) pthread_attr_t attr; NTSTATUS status = STATUS_SUCCESS; +#ifdef __APPLE__ + if (data->start == CFRunLoopRun) + { + CFRunLoopSourceContext context = { .perform = (void (*)(void *))server_init_thread, .info = data }; + CFRunLoopSourceRef source; + + if (!(source = CFRunLoopSourceCreate( NULL, 0, &context ))) return STATUS_NO_MEMORY; + + InterlockedIncrement( &nb_threads ); + CFRunLoopAddSource( CFRunLoopGetMain(), source, kCFRunLoopCommonModes ); + CFRunLoopSourceSignal( source ); + CFRunLoopWakeUp( CFRunLoopGetMain() ); + CFRelease( source ); + + return STATUS_SUCCESS; + } +#endif + pthread_sigmask( SIG_BLOCK, &server_block_set, &sigset ); pthread_attr_init( &attr ); pthread_attr_setstack( &attr, get_kernel_stack( data ), kernel_stack_size ); @@ -1581,6 +1600,25 @@ void wait_suspend( CONTEXT *context ) } +#ifdef __APPLE__ +/********************************************************************** + * convert_mac_main_thread + * + * Convert the process main thread into a Wine system thread + */ +void convert_mac_main_thread( void ) +{ + NTSTATUS status; + HANDLE thread; + + if ((status = PsCreateSystemThread(&thread, THREAD_ALL_ACCESS, NULL, 0, NULL, (void *)CFRunLoopRun, NULL))) + ERR("Failed to spawn main thread, status %#x\n", status); + + NtClose(thread); +} +#endif + + /********************************************************************** * send_debug_event * diff --git a/dlls/ntdll/unix/unix_private.h b/dlls/ntdll/unix/unix_private.h index c8ca97d21a0..9bd3ec06edf 100644 --- a/dlls/ntdll/unix/unix_private.h +++ b/dlls/ntdll/unix/unix_private.h @@ -285,6 +285,9 @@ extern void DECLSPEC_NORETURN abort_thread( int status ); extern void DECLSPEC_NORETURN abort_process( int status ); extern void DECLSPEC_NORETURN exit_process( int status ); extern void wait_suspend( CONTEXT *context ); +#ifdef __APPLE__ +extern void convert_mac_main_thread( void ); +#endif extern NTSTATUS send_debug_event( struct thread_data *data, EXCEPTION_RECORD *rec, CONTEXT *context, BOOL first_chance, BOOL exception ); extern NTSTATUS set_thread_context( HANDLE handle, const void *context, BOOL *self, USHORT machine ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9579
I updated this with a new version based on Rémi's !11333 that converts the macOS main thread to a Wine system thread at launch. Marking as draft for the moment so I can test out logging and crashes. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9579#note_149634
That nested run loop doesn't seem very elegant. I was thinking of doing something like this instead: [mac-thread.diff](/uploads/adde2b0bcc1032998c41f2ce4e8a11e5/mac-thread.diff) -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9579#note_149660
On Tue Aug 25 00:06:30 2026 +0000, Alexandre Julliard wrote:
That nested run loop doesn't seem very elegant. I was thinking of doing something like this instead: [mac-thread.diff](/uploads/adde2b0bcc1032998c41f2ce4e8a11e5/mac-thread.diff) Thanks, that's a big improvement, I've pushed a new version adopting that. Crashes and logging are also working.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9579#note_149791
participants (3)
-
Alexandre Julliard (@julliard) -
Brendan Shanks -
Brendan Shanks (@bshanks)