]> git.sesse.net Git - vlc/blobdiff - src/win32/thread.c
Win32: implement thread return value
[vlc] / src / win32 / thread.c
index c41fbc443a0df628dbd008fc9b5f1ddaa4a25c5f..365025674f25072070903c743357ef25f37b9820 100644 (file)
 # include <mmsystem.h>
 #endif
 
-static vlc_threadvar_t cancel_key;
+static vlc_threadvar_t thread_key;
 
 /**
- * Per-thread cancellation data
+ * Per-thread data
  */
-typedef struct vlc_cancel_t
+struct vlc_thread
 {
-    vlc_cleanup_t *cleaners;
+    HANDLE         id;
 #ifdef UNDER_CE
     HANDLE         cancel_event;
 #endif
+
+    bool           detached;
     bool           killable;
     bool           killed;
-} vlc_cancel_t;
+    vlc_cleanup_t *cleaners;
 
-#ifndef UNDER_CE
-# define VLC_CANCEL_INIT { NULL, true, false }
-#else
-# define VLC_CANCEL_INIT { NULL, NULL, true, false }
-#endif
+    void        *(*entry) (void *);
+    void          *data;
+};
 
 #ifdef UNDER_CE
 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy);
@@ -67,20 +67,20 @@ static void CALLBACK vlc_cancel_self (ULONG_PTR dummy);
 static DWORD vlc_cancelable_wait (DWORD count, const HANDLE *handles,
                                   DWORD delay)
 {
-    vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
-    if (nfo == NULL)
+    struct vlc_thread *th = vlc_threadvar_get (thread_key);
+    if (th == NULL)
     {
         /* Main thread - cannot be cancelled anyway */
         return WaitForMultipleObjects (count, handles, FALSE, delay);
     }
     HANDLE new_handles[count + 1];
     memcpy(new_handles, handles, count * sizeof(HANDLE));
-    new_handles[count] = nfo->cancel_event;
+    new_handles[count] = th->cancel_event;
     DWORD result = WaitForMultipleObjects (count + 1, new_handles, FALSE,
                                            delay);
     if (result == WAIT_OBJECT_0 + count)
     {
-        vlc_cancel_self (NULL);
+        vlc_cancel_self ((uintptr_t)th);
         return WAIT_IO_COMPLETION;
     }
     else
@@ -153,11 +153,11 @@ BOOL WINAPI DllMain (HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved)
         case DLL_PROCESS_ATTACH:
             vlc_mutex_init (&super_mutex);
             vlc_cond_init (&super_variable);
-            vlc_threadvar_create (&cancel_key, free);
+            vlc_threadvar_create (&thread_key, NULL);
             break;
 
         case DLL_PROCESS_DETACH:
-            vlc_threadvar_delete( &cancel_key );
+            vlc_threadvar_delete (&thread_key);
             vlc_cond_destroy (&super_variable);
             vlc_mutex_destroy (&super_mutex);
             break;
@@ -458,40 +458,83 @@ void vlc_rwlock_unlock (vlc_rwlock_t *lock)
 }
 
 /*** Thread-specific variables (TLS) ***/
+struct vlc_threadvar
+{
+    DWORD                 id;
+    void                (*destroy) (void *);
+    struct vlc_threadvar *prev;
+    struct vlc_threadvar *next;
+} *vlc_threadvar_last = NULL;
+
 int vlc_threadvar_create (vlc_threadvar_t *p_tls, void (*destr) (void *))
 {
-#warning FIXME: use destr() callback and stop leaking!
-    VLC_UNUSED( destr );
+    struct vlc_threadvar *var = malloc (sizeof (*var));
+    if (unlikely(var == NULL))
+        return errno;
 
-    *p_tls = TlsAlloc();
-    return (*p_tls == TLS_OUT_OF_INDEXES) ? EAGAIN : 0;
+    var->id = TlsAlloc();
+    if (var->id == TLS_OUT_OF_INDEXES)
+    {
+        free (var);
+        return EAGAIN;
+    }
+    var->destroy = destr;
+    var->next = NULL;
+    *p_tls = var;
+
+    vlc_mutex_lock (&super_mutex);
+    var->prev = vlc_threadvar_last;
+    vlc_threadvar_last = var;
+    vlc_mutex_unlock (&super_mutex);
+    return 0;
 }
 
 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
 {
-    TlsFree (*p_tls);
+    struct vlc_threadvar *var = *p_tls;
+
+    vlc_mutex_lock (&super_mutex);
+    if (var->prev != NULL)
+        var->prev->next = var->next;
+    else
+        vlc_threadvar_last = var->next;
+    if (var->next != NULL)
+        var->next->prev = var->prev;
+    vlc_mutex_unlock (&super_mutex);
+
+    TlsFree (var->id);
+    free (var);
 }
 
-/**
- * Sets a thread-local variable.
- * @param key thread-local variable key (created with vlc_threadvar_create())
- * @param value new value for the variable for the calling thread
- * @return 0 on success, a system error code otherwise.
- */
 int vlc_threadvar_set (vlc_threadvar_t key, void *value)
 {
-    return TlsSetValue (key, value) ? ENOMEM : 0;
+    return TlsSetValue (key->id, value) ? ENOMEM : 0;
 }
 
-/**
- * Gets the value of a thread-local variable for the calling thread.
- * This function cannot fail.
- * @return the value associated with the given variable for the calling
- * or NULL if there is no value.
- */
 void *vlc_threadvar_get (vlc_threadvar_t key)
 {
-    return TlsGetValue (key);
+    return TlsGetValue (key->id);
+}
+
+static void vlc_threadvar_cleanup (void)
+{
+    vlc_threadvar_t key;
+
+retry:
+    /* TODO: use RW lock or something similar */
+    vlc_mutex_lock (&super_mutex);
+    for (key = vlc_threadvar_last; key != NULL; key = key->prev)
+    {
+        void *value = vlc_threadvar_get (key);
+        if (value != NULL && key->destroy != NULL)
+        {
+            vlc_mutex_unlock (&super_mutex);
+            vlc_threadvar_set (key, NULL);
+            key->destroy (value);
+            goto retry;
+        }
+    }
+    vlc_mutex_unlock (&super_mutex);
 }
 
 
@@ -501,195 +544,164 @@ void vlc_threads_setup (libvlc_int_t *p_libvlc)
     (void) p_libvlc;
 }
 
-struct vlc_entry_data
-{
-    void * (*func) (void *);
-    void *  data;
-#ifdef UNDER_CE
-    HANDLE  cancel_event;
-#endif
-};
-
 static unsigned __stdcall vlc_entry (void *p)
 {
-    vlc_cancel_t cancel_data = VLC_CANCEL_INIT;
-    struct vlc_entry_data data;
-
-    memcpy (&data, p, sizeof (data));
-    free (p);
+    struct vlc_thread *th = p;
 
-#ifdef UNDER_CE
-    cancel_data.cancel_event = data.cancel_event;
-#endif
-
-    vlc_threadvar_set (cancel_key, &cancel_data);
-    data.func (data.data);
+    vlc_threadvar_set (thread_key, th);
+    th->killable = true;
+    th->data = th->entry (th->data);
+    vlc_threadvar_cleanup ();
+    if (th->detached)
+        free (th);
     return 0;
 }
 
 int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
                int priority)
 {
-    int err = ENOMEM;
-    HANDLE hThread;
-
-    struct vlc_entry_data *entry_data = malloc (sizeof (*entry_data));
-    if (entry_data == NULL)
+    struct vlc_thread *th = malloc (sizeof (*th));
+    if (unlikely(th == NULL))
         return ENOMEM;
-    entry_data->func = entry;
-    entry_data->data = data;
+    th->entry = entry;
+    th->data = data;
+    th->detached = p_handle == NULL;
+    th->killable = false; /* not until vlc_entry() ! */
+    th->killed = false;
+    th->cleaners = NULL;
 
+    HANDLE hThread;
 #ifndef UNDER_CE
     /* When using the MSVCRT C library you have to use the _beginthreadex
      * function instead of CreateThread, otherwise you'll end up with
      * memory leaks and the signal functions not working (see Microsoft
      * Knowledge Base, article 104641) */
     hThread = (HANDLE)(uintptr_t)
-        _beginthreadex (NULL, 0, vlc_entry, entry_data, CREATE_SUSPENDED, NULL);
-    if (! hThread)
+        _beginthreadex (NULL, 0, vlc_entry, th, CREATE_SUSPENDED, NULL);
+    if (hThread == NULL)
     {
-        err = errno;
-        goto error;
-    }
-
-    /* Thread closes the handle when exiting, duplicate it here
-     * to be on the safe side when joining. */
-    if (!DuplicateHandle (GetCurrentProcess (), hThread,
-                          GetCurrentProcess (), p_handle, 0, FALSE,
-                          DUPLICATE_SAME_ACCESS))
-    {
-        CloseHandle (hThread);
-        goto error;
+        int err = errno;
+        free (th);
+        return err;
     }
 
 #else
-    vlc_thread_t th = malloc (sizeof (*th));
-    if (th == NULL)
-        goto error;
+    /* FIXME: cancel_event is useless and leaked in detached threads */
     th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL);
     if (th->cancel_event == NULL)
     {
         free (th);
-        goto error;
+        return ENOMEM;
     }
-    entry_data->cancel_event = th->cancel_event;
 
     /* Not sure if CREATE_SUSPENDED + ResumeThread() is any useful on WinCE.
      * Thread handles act up, too. */
-    th->handle = CreateThread (NULL, 128*1024, vlc_entry, entry_data,
-                               CREATE_SUSPENDED, NULL);
-    if (th->handle == NULL)
+    hThread = CreateThread (NULL, 128*1024, vlc_entry, th,
+                            CREATE_SUSPENDED, NULL);
+    if (hThread == NULL)
     {
         CloseHandle (th->cancel_event);
         free (th);
-        goto error;
+        return ENOMEM;
     }
 
-    *p_handle = th;
-    hThread = th->handle;
-
 #endif
+    th->id = hThread;
 
     ResumeThread (hThread);
     if (priority)
         SetThreadPriority (hThread, priority);
 
-    return 0;
+    if (p_handle != NULL)
+        *p_handle = th;
+    else
+        CloseHandle (hThread);
 
-error:
-    free (entry_data);
-    return err;
+    return 0;
 }
 
-void vlc_join (vlc_thread_t handle, void **result)
+void vlc_join (vlc_thread_t th, void **result)
 {
-#ifdef UNDER_CE
-# define handle handle->handle
-#endif
     do
         vlc_testcancel ();
-    while (WaitForSingleObjectEx (handle, INFINITE, TRUE)
+    while (WaitForSingleObjectEx (th->id, INFINITE, TRUE)
                                                         == WAIT_IO_COMPLETION);
 
-    CloseHandle (handle);
-    assert (result == NULL); /* <- FIXME if ever needed */
+    CloseHandle (th->id);
 #ifdef UNDER_CE
-# undef handle
-    CloseHandle (handle->cancel_event);
-    free (handle);
+    CloseHandle (th->cancel_event);
 #endif
+    if (result != NULL)
+        *result = th->data;
+    free (th);
 }
 
-void vlc_detach (vlc_thread_t handle)
+int vlc_clone_detach (void *(*entry) (void *), void *data, int priority)
 {
-#ifndef UNDER_CE
-    CloseHandle (handle);
-#else
-    /* FIXME: handle->cancel_event leak */
-    CloseHandle (handle->handle);
-    free (handle);
-#endif
+    return vlc_clone (NULL, entry, data, priority);
 }
 
 /*** Thread cancellation ***/
 
 /* APC procedure for thread cancellation */
-static void CALLBACK vlc_cancel_self (ULONG_PTR dummy)
+static void CALLBACK vlc_cancel_self (ULONG_PTR self)
 {
-    vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
+    struct vlc_thread *th = (void *)self;
 
-    if (likely(nfo != NULL))
-        nfo->killed = true;
-
-    (void)dummy;
+    if (likely(th != NULL))
+        th->killed = true;
 }
 
-void vlc_cancel (vlc_thread_t thread_id)
+void vlc_cancel (vlc_thread_t th)
 {
 #ifndef UNDER_CE
-    QueueUserAPC (vlc_cancel_self, thread_id, 0);
+    QueueUserAPC (vlc_cancel_self, th->id, (uintptr_t)th);
 #else
-    SetEvent (thread_id->cancel_event);
+    SetEvent (th->cancel_event);
 #endif
 }
 
 int vlc_savecancel (void)
 {
-    int state;
-
-    vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
-    if (nfo == NULL)
+    struct vlc_thread *th = vlc_threadvar_get (thread_key);
+    if (th == NULL)
         return false; /* Main thread - cannot be cancelled anyway */
 
-    state = nfo->killable;
-    nfo->killable = false;
+    int state = th->killable;
+    th->killable = false;
     return state;
 }
 
 void vlc_restorecancel (int state)
 {
-    vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
+    struct vlc_thread *th = vlc_threadvar_get (thread_key);
     assert (state == false || state == true);
 
-    if (nfo == NULL)
+    if (th == NULL)
         return; /* Main thread - cannot be cancelled anyway */
 
-    assert (!nfo->killable);
-    nfo->killable = state != 0;
+    assert (!th->killable);
+    th->killable = state != 0;
 }
 
 void vlc_testcancel (void)
 {
-    vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
-    if (nfo == NULL)
+    struct vlc_thread *th = vlc_threadvar_get (thread_key);
+    if (th == NULL)
         return; /* Main thread - cannot be cancelled anyway */
 
-    if (nfo->killable && nfo->killed)
+    if (th->killable && th->killed)
     {
-        for (vlc_cleanup_t *p = nfo->cleaners; p != NULL; p = p->next)
+        /* Detached threads cannot be cancelled */
+        assert (!th->detached);
+
+        th->data = NULL; /* TODO: special value? */
+
+        for (vlc_cleanup_t *p = th->cleaners; p != NULL; p = p->next)
              p->proc (p->data);
+        vlc_threadvar_cleanup ();
 #ifndef UNDER_CE
-        _endthread ();
+        _endthreadex(0);
 #else
         ExitThread(0);
 #endif
@@ -702,8 +714,8 @@ void vlc_control_cancel (int cmd, ...)
      * need to lock anything. */
     va_list ap;
 
-    vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
-    if (nfo == NULL)
+    struct vlc_thread *th = vlc_threadvar_get (thread_key);
+    if (th == NULL)
         return; /* Main thread - cannot be cancelled anyway */
 
     va_start (ap, cmd);
@@ -714,14 +726,14 @@ void vlc_control_cancel (int cmd, ...)
             /* cleaner is a pointer to the caller stack, no need to allocate
              * and copy anything. As a nice side effect, this cannot fail. */
             vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *);
-            cleaner->next = nfo->cleaners;
-            nfo->cleaners = cleaner;
+            cleaner->next = th->cleaners;
+            th->cleaners = cleaner;
             break;
         }
 
         case VLC_CLEANUP_POP:
         {
-            nfo->cleaners = nfo->cleaners->next;
+            th->cleaners = th->cleaners->next;
             break;
         }
     }