]> git.sesse.net Git - vlc/blobdiff - src/misc/threads.c
Support for cancellation cleanup functions
[vlc] / src / misc / threads.c
index af65ddb4a0c0868b977dd3344619f9e1c246f634..33a76e4759deb1dfa4e3482d62e1c5d6ec17332d 100644 (file)
@@ -31,6 +31,7 @@
 #include <vlc_common.h>
 
 #include "libvlc.h"
+#include <stdarg.h>
 #include <assert.h>
 #ifdef HAVE_UNISTD_H
 # include <unistd.h>
@@ -143,6 +144,8 @@ void vlc_pthread_fatal (const char *action, int error,
     (void)action; (void)error; (void)file; (void)line;
     abort();
 }
+
+static vlc_threadvar_t cancel_key;
 #endif
 
 /*****************************************************************************
@@ -177,6 +180,9 @@ int vlc_threads_init( void )
         vlc_threadvar_create( &thread_object_key, NULL );
 #endif
         vlc_threadvar_create( &msg_context_global_key, msg_StackDestroy );
+#ifndef LIBVLC_USE_PTHREAD
+        vlc_threadvar_create( &cancel_key, free );
+#endif
     }
     i_initializations++;
 
@@ -206,6 +212,9 @@ void vlc_threads_end( void )
     if( i_initializations == 1 )
     {
         vlc_object_release( p_root );
+#ifndef LIBVLC_USE_PTHREAD
+        vlc_threadvar_delete( &cancel_key );
+#endif
         vlc_threadvar_delete( &msg_context_global_key );
 #ifndef NDEBUG
         vlc_threadvar_delete( &thread_object_key );
@@ -559,6 +568,15 @@ int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
     return ret;
 }
 
+#if defined (WIN32)
+/* APC procedure for thread cancellation */
+static void CALLBACK vlc_cancel_self (ULONG_PTR dummy)
+{
+    (void)dummy;
+    vlc_control_cancel (VLC_DO_CANCEL);
+}
+#endif
+
 /**
  * Marks a thread as cancelled. Next time the target thread reaches a
  * cancellation point (while not having disabled cancellation), it will
@@ -570,11 +588,16 @@ void vlc_cancel (vlc_thread_t thread_id)
 {
 #if defined (LIBVLC_USE_PTHREAD)
     pthread_cancel (thread_id);
+#elif defined (WIN32)
+    QueueUserAPC (vlc_cancel_self, thread_id->handle, 0);
 #endif
 }
 
 /**
  * Waits for a thread to complete (if needed), and destroys it.
+ * This is a cancellation point; in case of cancellation, the join does _not_
+ * occur.
+ *
  * @param handle thread handle
  * @param p_result [OUT] pointer to write the thread return value or NULL
  * @return 0 on success, a standard error code otherwise.
@@ -585,7 +608,11 @@ int vlc_join (vlc_thread_t handle, void **result)
     return pthread_join (handle, result);
 
 #elif defined( UNDER_CE ) || defined( WIN32 )
-    WaitForSingleObject (handle->handle, INFINITE);
+    do
+        vlc_testcancel ();
+    while (WaitForSingleObjectEx (handle->handle, INFINITE, TRUE)
+                                                        == WAIT_IO_COMPLETION);
+
     CloseHandle (handle->handle);
     if (result)
         *result = handle->data;
@@ -836,3 +863,92 @@ void vlc_thread_cancel (vlc_object_t *obj)
     if (priv->b_thread)
         vlc_cancel (priv->thread_id);
 }
+
+#ifndef LIBVLC_USE_PTHREAD
+typedef struct vlc_cancel_t
+{
+    vlc_cleanup_t *cleaners;
+    bool           killable;
+    bool           killed;
+} vlc_cancel_t;
+#endif
+
+void vlc_control_cancel (int cmd, ...)
+{
+    /* NOTE: This function only modifies thread-specific data, so there is no
+     * need to lock anything. */
+#ifdef LIBVLC_USE_PTHREAD
+    (void) cmd;
+    assert (0);
+#else
+    va_list ap;
+
+    va_start (ap, cmd);
+
+    vlc_cancel_t *nfo = vlc_threadvar_get (&cancel_key);
+    if (nfo == NULL)
+    {
+        nfo = malloc (sizeof (*nfo));
+        if (nfo == NULL)
+            abort ();
+        nfo->cleaners = NULL;
+        nfo->killed = false;
+        nfo->killable = true;
+    }
+
+    switch (cmd)
+    {
+        case VLC_SAVE_CANCEL:
+        {
+            int *p_state = va_arg (ap, int *);
+            *p_state = nfo->killable;
+            nfo->killable = false;
+            break;
+        }
+
+        case VLC_RESTORE_CANCEL:
+        {
+            int state = va_arg (ap, int);
+            nfo->killable = state != 0;
+            break;
+        }
+
+        case VLC_TEST_CANCEL:
+            if (nfo->killable && nfo->killed)
+            {
+                for (vlc_cleanup_t *p = nfo->cleaners; p != NULL; p = p->next)
+                     p->proc (p->data);
+                free (nfo);
+#if defined (LIBVLC_USE_PTHREAD)
+                pthread_exit (PTHREAD_CANCELLED);
+#elif defined (WIN32)
+                _endthread ();
+#else
+# error Not implemented!
+#endif
+            }
+            break;
+
+        case VLC_DO_CANCEL:
+            nfo->killed = true;
+            break;
+
+        case VLC_CLEANUP_PUSH:
+        {
+            /* 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;
+            break;
+        }
+
+        case VLC_CLEANUP_POP:
+        {
+            nfo->cleaners = nfo->cleaners->next;
+            break;
+        }
+    }
+    va_end (ap);
+#endif
+}