Ken Thomases : loader: On Mac, reserve the process' s original thread for the frameworks.

Alexandre Julliard julliard at winehq.org
Tue Nov 16 12:45:44 CST 2010


Module: wine
Branch: master
Commit: 47dea9b6707a73a66bcc1b96bcf72ffe1168c0fa
URL:    http://source.winehq.org/git/wine.git/?a=commit;h=47dea9b6707a73a66bcc1b96bcf72ffe1168c0fa

Author: Ken Thomases <ken at codeweavers.com>
Date:   Tue Nov 16 01:41:14 2010 -0600

loader: On Mac, reserve the process's original thread for the frameworks.

The frameworks expect the original thread to run its run loop, so input
sources (like distributed notifications) get processed.  For example,
Core Audio on Snow Leopard doesn't track changes in the default ouput
device, such as when headphones are plugged in, without this.

---

 dlls/kernel32/process.c |   64 ++++++++++++++++++++++++-
 libs/wine/Makefile.in   |    2 +-
 libs/wine/loader.c      |  121 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 185 insertions(+), 2 deletions(-)

diff --git a/dlls/kernel32/process.c b/dlls/kernel32/process.c
index 258bb36..2b427b9 100644
--- a/dlls/kernel32/process.c
+++ b/dlls/kernel32/process.c
@@ -56,6 +56,9 @@ WINE_DECLARE_DEBUG_CHANNEL(file);
 WINE_DECLARE_DEBUG_CHANNEL(relay);
 
 #ifdef __APPLE__
+#include <CoreFoundation/CoreFoundation.h>
+#include <pthread.h>
+#include <unistd.h>
 extern char **__wine_get_main_environment(void);
 #else
 extern char **__wine_main_environ;
@@ -1632,6 +1635,54 @@ static const char *get_alternate_loader( char **ret_env )
     return loader;
 }
 
+#ifdef __APPLE__
+/***********************************************************************
+ *           terminate_main_thread
+ *
+ * On some versions of Mac OS X, the execve system call fails with
+ * ENOTSUP if the process has multiple threads.  Wine is always multi-
+ * threaded on Mac OS X because it specifically reserves the main thread
+ * for use by the system frameworks (see apple_main_thread() in
+ * libs/wine/loader.c).  So, when we need to exec without first forking,
+ * we need to terminate the main thread first.  We do this by installing
+ * a custom run loop source onto the main run loop and signaling it.
+ * The source's "perform" callback is pthread_exit and it will be
+ * executed on the main thread, terminating it.
+ *
+ * Returns TRUE if there's still hope the main thread has terminated or
+ * will soon.  Return FALSE if we've given up.
+ */
+static BOOL terminate_main_thread(void)
+{
+    static int delayms;
+
+    if (!delayms)
+    {
+        CFRunLoopSourceContext source_context = { 0 };
+        CFRunLoopSourceRef source;
+
+        source_context.perform = pthread_exit;
+        if (!(source = CFRunLoopSourceCreate( NULL, 0, &source_context )))
+            return FALSE;
+
+        CFRunLoopAddSource( CFRunLoopGetMain(), source, kCFRunLoopCommonModes );
+        CFRunLoopSourceSignal( source );
+        CFRunLoopWakeUp( CFRunLoopGetMain() );
+        CFRelease( source );
+
+        delayms = 20;
+    }
+
+    if (delayms > 1000)
+        return FALSE;
+
+    usleep(delayms * 1000);
+    delayms *= 2;
+
+    return TRUE;
+}
+#endif
+
 /***********************************************************************
  *           create_process
  *
@@ -1796,7 +1847,18 @@ static BOOL create_process( HANDLE hFile, LPCWSTR filename, LPWSTR cmd_line, LPW
         if (wineloader) putenv( wineloader );
         if (unixdir) chdir(unixdir);
 
-        if (argv) wine_exec_wine_binary( loader, argv, getenv("WINELOADER") );
+        if (argv)
+        {
+            do
+            {
+                wine_exec_wine_binary( loader, argv, getenv("WINELOADER") );
+            }
+#ifdef __APPLE__
+            while (errno == ENOTSUP && exec_only && terminate_main_thread());
+#else
+            while (0);
+#endif
+        }
         _exit(1);
     }
 
diff --git a/libs/wine/Makefile.in b/libs/wine/Makefile.in
index 359382f..fe33595 100644
--- a/libs/wine/Makefile.in
+++ b/libs/wine/Makefile.in
@@ -1,7 +1,7 @@
 DLLFLAGS  = @DLLFLAGS@
 MODULE    = libwine.$(LIBEXT)
 VERSCRIPT = $(srcdir)/wine.map
-EXTRALIBS = $(LIBPORT) @LIBDL@ @CRTLIBS@
+EXTRALIBS = $(LIBPORT) @LIBDL@ @CRTLIBS@ @COREFOUNDATIONLIB@
 DEFS      = -D__WINESRC__ -DWINE_UNICODE_API=""
 
 VERSION   = 1.0
diff --git a/libs/wine/loader.c b/libs/wine/loader.c
index 37c603e..6c2324d 100644
--- a/libs/wine/loader.c
+++ b/libs/wine/loader.c
@@ -48,6 +48,8 @@
 #ifdef __APPLE__
 #include <crt_externs.h>
 #define environ (*_NSGetEnviron())
+#include <CoreFoundation/CoreFoundation.h>
+#include <pthread.h>
 #else
 extern char **environ;
 #endif
@@ -668,6 +670,121 @@ static void set_max_limit( int limit )
 }
 
 
+#ifdef __APPLE__
+struct apple_stack_info
+{
+    void *stack;
+    size_t desired_size;
+};
+
+/***********************************************************************
+ *           apple_alloc_thread_stack
+ *
+ * Callback for wine_mmap_enum_reserved_areas to allocate space for
+ * the secondary thread's stack.
+ */
+static int apple_alloc_thread_stack( void *base, size_t size, void *arg )
+{
+    struct apple_stack_info *info = arg;
+
+    /* For mysterious reasons, putting the thread stack at the very top
+     * of the address space causes subsequent execs to fail, even on the
+     * child side of a fork.  Avoid the top 16MB. */
+    char * const limit = (char*)0xff000000;
+    if (base >= limit) return 0;
+    if (size > limit - (char*)base)
+        size = limit - (char*)base;
+    if (size < info->desired_size) return 0;
+    info->stack = wine_anon_mmap( (char *)base + size - info->desired_size,
+                                  info->desired_size, PROT_READ|PROT_WRITE, MAP_FIXED );
+    return (info->stack != (void *)-1);
+}
+
+/***********************************************************************
+ *           apple_create_wine_thread
+ *
+ * Spin off a secondary thread to complete Wine initialization, leaving
+ * the original thread for the Mac frameworks.
+ *
+ * Invoked as a CFRunLoopSource perform callback.
+ */
+static void apple_create_wine_thread( void *init_func )
+{
+    int success = 0;
+    pthread_t thread;
+    pthread_attr_t attr;
+
+    if (!pthread_attr_init( &attr ))
+    {
+        struct apple_stack_info info;
+
+        /* Try to put the new thread's stack in the reserved area.  If this
+         * fails, just let it go wherever.  It'll be a waste of space, but we
+         * can go on. */
+        if (!pthread_attr_getstacksize( &attr, &info.desired_size ) &&
+            wine_mmap_enum_reserved_areas( apple_alloc_thread_stack, &info, 1 ))
+        {
+            wine_mmap_remove_reserved_area( info.stack, info.desired_size, 0 );
+            pthread_attr_setstackaddr( &attr, (char*)info.stack + info.desired_size );
+        }
+
+        if (!pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ) &&
+            !pthread_create( &thread, &attr, init_func, NULL ))
+            success = 1;
+
+        pthread_attr_destroy( &attr );
+    }
+
+    /* Failure is indicated by returning from wine_init().  Stopping
+     * the run loop allows apple_main_thread() and thus wine_init() to
+     * return. */
+    if (!success)
+        CFRunLoopStop( CFRunLoopGetCurrent() );
+}
+
+
+/***********************************************************************
+ *           apple_main_thread
+ *
+ * Park the process's original thread in a Core Foundation run loop for
+ * use by the Mac frameworks, especially receiving and handling
+ * distributed notifications.  Spin off a new thread for the rest of the
+ * Wine initialization.
+ */
+static void apple_main_thread( void (*init_func)(void) )
+{
+    CFRunLoopSourceContext source_context = { 0 };
+    CFRunLoopSourceRef source;
+
+    /* Give ourselves the best chance of having the distributed notification
+     * center scheduled on this thread's run loop.  In theory, it's scheduled
+     * in the first thread to ask for it. */
+    CFNotificationCenterGetDistributedCenter();
+
+    /* We use this run loop source for two purposes.  First, a run loop exits
+     * if it has no more sources scheduled.  So, we need at least one source
+     * to keep the run loop running.  Second, although it's not critical, it's
+     * preferable for the Wine initialization to not proceed until we know
+     * the run loop is running.  So, we signal our source immediately after
+     * adding it and have its callback spin off the Wine thread. */
+    source_context.info = init_func;
+    source_context.perform = apple_create_wine_thread;
+    source = CFRunLoopSourceCreate( NULL, 0, &source_context );
+
+    if (source)
+    {
+        CFRunLoopAddSource( CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes );
+        CFRunLoopSourceSignal( source );
+        CFRelease( source );
+
+        CFRunLoopRun(); /* Should never return, except on error. */
+    }
+
+    /* If we get here (i.e. return), that indicates failure to our caller. */
+}
+#endif
+
+
 /***********************************************************************
  *           wine_init
  *
@@ -708,7 +825,11 @@ void wine_init( int argc, char *argv[], char *error, int error_size )
 
     if (!ntdll) return;
     if (!(init_func = wine_dlsym( ntdll, "__wine_process_init", error, error_size ))) return;
+#ifdef __APPLE__
+    apple_main_thread( init_func );
+#else
     init_func();
+#endif
 }
 
 




More information about the wine-cvs mailing list