]> git.sesse.net Git - vlc/blobdiff - src/misc/threads.c
Deinline vlc_threadvar_(|s)get and kill useless indirection
[vlc] / src / misc / threads.c
index 988c9979697938a490838329447d7113cd11da0f..75aed7077c54218d39648cc27a1cc68b02e8028e 100644 (file)
@@ -41,6 +41,9 @@
 
 #if defined( LIBVLC_USE_PTHREAD )
 # include <sched.h>
+# ifdef __linux__
+#  include <sys/syscall.h> /* SYS_gettid */
+# endif
 #else
 static vlc_threadvar_t cancel_key;
 #endif
@@ -49,8 +52,8 @@ static vlc_threadvar_t cancel_key;
 # include <execinfo.h>
 #endif
 
-#ifdef UNDER_CE
-# define WaitForSingleObjectEx(a,b,c) WaitForSingleObject(a,b)
+#ifdef __APPLE__
+# include <sys/time.h> /* gettimeofday in vlc_cond_timedwait */
 #endif
 
 /**
@@ -72,11 +75,16 @@ void vlc_trace (const char *fn, const char *file, unsigned line)
 
 static inline unsigned long vlc_threadid (void)
 {
-#if defined(LIBVLC_USE_PTHREAD)
+#if defined (LIBVLC_USE_PTHREAD)
+# if defined (__linux__)
+     return syscall (SYS_gettid);
+
+# else
      union { pthread_t th; unsigned long int i; } v = { };
      v.th = pthread_self ();
      return v.i;
 
+#endif
 #elif defined (WIN32)
      return GetCurrentThreadId ();
 
@@ -144,9 +152,95 @@ typedef struct vlc_cancel_t
     vlc_cleanup_t *cleaners;
     bool           killable;
     bool           killed;
+# ifdef UNDER_CE
+    HANDLE         cancel_event;
+# endif
 } vlc_cancel_t;
 
-# define VLC_CANCEL_INIT { NULL, true, false }
+# ifndef UNDER_CE
+#  define VLC_CANCEL_INIT { NULL, true, false }
+# else
+#  define VLC_CANCEL_INIT { NULL, true, false, NULL }
+# endif
+#endif
+
+#ifdef UNDER_CE
+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)
+    {
+        /* 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;
+    DWORD result = WaitForMultipleObjects (count + 1, new_handles, FALSE,
+                                           delay);
+    if (result == WAIT_OBJECT_0 + count)
+    {
+        vlc_cancel_self (NULL);
+        return WAIT_IO_COMPLETION;
+    }
+    else
+    {
+        return result;
+    }
+}
+
+DWORD SleepEx (DWORD dwMilliseconds, BOOL bAlertable)
+{
+    if (bAlertable)
+    {
+        DWORD result = vlc_cancelable_wait (0, NULL, dwMilliseconds);
+        return (result == WAIT_TIMEOUT) ? 0 : WAIT_IO_COMPLETION;
+    }
+    else
+    {
+        Sleep(dwMilliseconds);
+        return 0;
+    }
+}
+
+DWORD WaitForSingleObjectEx (HANDLE hHandle, DWORD dwMilliseconds,
+                             BOOL bAlertable)
+{
+    if (bAlertable)
+    {
+        /* The MSDN documentation specifies different return codes,
+         * but in practice they are the same. We just check that it
+         * remains so. */
+#if WAIT_ABANDONED != WAIT_ABANDONED_0
+# error Windows headers changed, code needs to be rewritten!
+#endif
+        return vlc_cancelable_wait (1, &hHandle, dwMilliseconds);
+    }
+    else
+    {
+        return WaitForSingleObject (hHandle, dwMilliseconds);
+    }
+}
+
+DWORD WaitForMultipleObjectsEx (DWORD nCount, const HANDLE *lpHandles,
+                                BOOL bWaitAll, DWORD dwMilliseconds,
+                                BOOL bAlertable)
+{
+    if (bAlertable)
+    {
+        /* We do not support the bWaitAll case */
+        assert (! bWaitAll);
+        return vlc_cancelable_wait (nCount, lpHandles, dwMilliseconds);
+    }
+    else
+    {
+        return WaitForMultipleObjects (nCount, lpHandles, bWaitAll,
+                                       dwMilliseconds);
+    }
+}
 #endif
 
 #ifdef WIN32
@@ -252,7 +346,7 @@ void vlc_mutex_destroy (vlc_mutex_t *p_mutex)
     VLC_THREAD_ASSERT ("destroying mutex");
 
 #elif defined( WIN32 )
-    assert (p_mutex->initialized);
+    assert (InterlockedExchange (&p_mutex->initialized, -1) == 1);
     DeleteCriticalSection (&p_mutex->mutex);
 
 #endif
@@ -262,6 +356,7 @@ void vlc_mutex_destroy (vlc_mutex_t *p_mutex)
  * Acquires a mutex. If needed, waits for any other thread to release it.
  * Beware of deadlocks when locking multiple mutexes at the same time,
  * or when using mutexes from callbacks.
+ * This function is not a cancellation-point.
  *
  * @param p_mutex mutex initialized with vlc_mutex_init() or
  *                vlc_mutex_init_recursive()
@@ -278,15 +373,57 @@ void vlc_mutex_lock (vlc_mutex_t *p_mutex)
         assert (p_mutex != &super_mutex); /* this one cannot be static */
 
         vlc_mutex_lock (&super_mutex);
-        vlc_mutex_init (p_mutex);
+        if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
+            vlc_mutex_init (p_mutex);
         /* FIXME: destroy the mutex some time... */
         vlc_mutex_unlock (&super_mutex);
     }
+    assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
     EnterCriticalSection (&p_mutex->mutex);
 
 #endif
 }
 
+/**
+ * Acquires a mutex if and only if it is not currently held by another thread.
+ * This function never sleeps and can be used in delay-critical code paths.
+ * This function is not a cancellation-point.
+ *
+ * <b>Beware</b>: If this function fails, then the mutex is held... by another
+ * thread. The calling thread must deal with the error appropriately. That
+ * typically implies postponing the operations that would have required the
+ * mutex. If the thread cannot defer those operations, then it must use
+ * vlc_mutex_lock(). If in doubt, use vlc_mutex_lock() instead.
+ *
+ * @param p_mutex mutex initialized with vlc_mutex_init() or
+ *                vlc_mutex_init_recursive()
+ * @return 0 if the mutex could be acquired, an error code otherwise.
+ */
+int vlc_mutex_trylock (vlc_mutex_t *p_mutex)
+{
+#if defined(LIBVLC_USE_PTHREAD)
+    int val = pthread_mutex_trylock( p_mutex );
+
+    if (val != EBUSY)
+        VLC_THREAD_ASSERT ("locking mutex");
+    return val;
+
+#elif defined( WIN32 )
+    if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
+    { /* ^^ We could also lock super_mutex all the time... sluggish */
+        assert (p_mutex != &super_mutex); /* this one cannot be static */
+
+        vlc_mutex_lock (&super_mutex);
+        if (InterlockedCompareExchange (&p_mutex->initialized, 0, 0) == 0)
+            vlc_mutex_init (p_mutex);
+        /* FIXME: destroy the mutex some time... */
+        vlc_mutex_unlock (&super_mutex);
+    }
+    assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
+    return TryEnterCriticalSection (&p_mutex->mutex) ? 0 : EBUSY;
+
+#endif
+}
 
 /**
  * Releases a mutex (or crashes if the mutex is not locked by the caller).
@@ -299,6 +436,7 @@ void vlc_mutex_unlock (vlc_mutex_t *p_mutex)
     VLC_THREAD_ASSERT ("unlocking mutex");
 
 #elif defined( WIN32 )
+    assert (InterlockedExchange (&p_mutex->initialized, 1) == 1);
     LeaveCriticalSection (&p_mutex->mutex);
 
 #endif
@@ -391,11 +529,6 @@ void vlc_cond_broadcast (vlc_cond_t *p_condvar)
 #endif
 }
 
-#ifdef UNDER_CE
-# warning FIXME
-# define WaitForMultipleObjectsEx(a,b,c) WaitForMultipleObjects(a,b)
-#endif
-
 /**
  * Waits for a condition variable. The calling thread will be suspended until
  * another thread calls vlc_cond_signal() or vlc_cond_broadcast() on the same
@@ -468,6 +601,16 @@ int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex,
                         mtime_t deadline)
 {
 #if defined(LIBVLC_USE_PTHREAD)
+#ifdef __APPLE__
+    /* mdate() is mac_absolute_time on osx, which we must convert to do
+     * the same base than gettimeofday() on which pthread_cond_timedwait
+     * counts on. */
+    mtime_t oldbase = mdate();
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    mtime_t newbase = (mtime_t)tv.tv_sec * 1000000 + (mtime_t) tv.tv_usec;
+    deadline = deadline - oldbase + newbase;
+#endif
     lldiv_t d = lldiv( deadline, CLOCK_FREQ );
     struct timespec ts = { d.quot, d.rem * (1000000000 / CLOCK_FREQ) };
 
@@ -510,8 +653,6 @@ int vlc_threadvar_create( vlc_threadvar_t *p_tls, void (*destr) (void *) )
 
 #if defined( LIBVLC_USE_PTHREAD )
     i_ret =  pthread_key_create( p_tls, destr );
-#elif defined( UNDER_CE )
-    i_ret = ENOSYS;
 #elif defined( WIN32 )
     /* FIXME: remember/use the destr() callback and stop leaking whatever */
     *p_tls = TlsAlloc();
@@ -526,7 +667,6 @@ void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
 {
 #if defined( LIBVLC_USE_PTHREAD )
     pthread_key_delete (*p_tls);
-#elif defined( UNDER_CE )
 #elif defined( WIN32 )
     TlsFree (*p_tls);
 #else
@@ -534,14 +674,51 @@ void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
 #endif
 }
 
+/**
+ * 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)
+{
+#if defined(LIBVLC_USE_PTHREAD)
+    return pthread_setspecific (key, value);
+#elif defined( UNDER_CE ) || defined( WIN32 )
+    return TlsSetValue (key, p_value) ? ENOMEM : 0;
+#else
+# error Unimplemented!
+#endif
+}
+
+/**
+ * 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)
+{
+#if defined(LIBVLC_USE_PTHREAD)
+    return pthread_getspecific (key);
+#elif defined( UNDER_CE ) || defined( WIN32 )
+    return TlsGetValue (key);
+#else
+# error Unimplemented!
+#endif
+}
+
 #if defined (LIBVLC_USE_PTHREAD)
 #elif defined (WIN32)
 static unsigned __stdcall vlc_entry (void *data)
 {
     vlc_cancel_t cancel_data = VLC_CANCEL_INIT;
     vlc_thread_t self = data;
+#ifdef UNDER_CE
+    cancel_data.cancel_event = self->cancel_event;
+#endif
 
-    vlc_threadvar_set (&cancel_key, &cancel_data);
+    vlc_threadvar_set (cancel_key, &cancel_data);
     self->data = self->entry (self->data);
     return 0;
 }
@@ -637,7 +814,13 @@ int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
     th->data = data;
     th->entry = entry;
 #if defined( UNDER_CE )
-    hThread = CreateThread (NULL, 0, vlc_entry, th, CREATE_SUSPENDED, NULL);
+    th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL);
+    if (th->cancel_event == NULL)
+    {
+        free(th);
+        return errno;
+    }
+    hThread = CreateThread (NULL, 128*1024, vlc_entry, th, CREATE_SUSPENDED, NULL);
 #else
     hThread = (HANDLE)(uintptr_t)
         _beginthreadex (NULL, 0, vlc_entry, th, CREATE_SUSPENDED, NULL);
@@ -645,6 +828,7 @@ int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
 
     if (hThread)
     {
+#ifndef UNDER_CE
         /* Thread closes the handle when exiting, duplicate it here
          * to be on the safe side when joining. */
         if (!DuplicateHandle (GetCurrentProcess (), hThread,
@@ -655,6 +839,9 @@ int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
             free (th);
             return ENOMEM;
         }
+#else
+        th->handle = hThread;
+#endif
 
         ResumeThread (hThread);
         if (priority)
@@ -694,18 +881,7 @@ void vlc_cancel (vlc_thread_t thread_id)
 #if defined (LIBVLC_USE_PTHREAD_CANCEL)
     pthread_cancel (thread_id);
 #elif defined (UNDER_CE)
-    /* HACK:There is no way to use something
-     * like QueueUserAPC on Windows CE, so I rely
-     * on some crappy arch specific code */
-    CONTEXT context;
-    context.ContextFlags = CONTEXT_CONTROL;
-    GetThreadContext (thread_id->handle, &context);
-    /* Setting the instruction pointer for the canceled thread */
-#if defined(_ARM_) || defined(ARM)
-    context.Pc = (DWORD_PTR) vlc_cancel_self;
-#endif
-    SetThreadContext (thread_id->handle, &context);
-
+    SetEvent (thread_id->cancel_event);
 #elif defined (WIN32)
     QueueUserAPC (vlc_cancel_self, thread_id->handle, 0);
 #else
@@ -726,8 +902,7 @@ void vlc_join (vlc_thread_t handle, void **result)
 {
 #if defined( LIBVLC_USE_PTHREAD )
     int val = pthread_join (handle, result);
-    if (val)
-        vlc_thread_fatal ("joining thread", val, __func__, __FILE__, __LINE__);
+    VLC_THREAD_ASSERT ("joining thread");
 
 #elif defined( UNDER_CE ) || defined( WIN32 )
     do
@@ -738,11 +913,111 @@ void vlc_join (vlc_thread_t handle, void **result)
     CloseHandle (handle->handle);
     if (result)
         *result = handle->data;
+#if defined( UNDER_CE )
+    CloseHandle (handle->cancel_event);
+#endif
     free (handle);
 
 #endif
 }
 
+/**
+ * Save the current cancellation state (enabled or disabled), then disable
+ * cancellation for the calling thread.
+ * This function must be called before entering a piece of code that is not
+ * cancellation-safe, unless it can be proven that the calling thread will not
+ * be cancelled.
+ * @return Previous cancellation state (opaque value for vlc_restorecancel()).
+ */
+int vlc_savecancel (void)
+{
+    int state;
+
+#if defined (LIBVLC_USE_PTHREAD_CANCEL)
+    int val = pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &state);
+    VLC_THREAD_ASSERT ("saving cancellation");
+
+#else
+    vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
+    if (nfo == NULL)
+        return false; /* Main thread - cannot be cancelled anyway */
+
+     state = nfo->killable;
+     nfo->killable = false;
+
+#endif
+    return state;
+}
+
+/**
+ * Restore the cancellation state for the calling thread.
+ * @param state previous state as returned by vlc_savecancel().
+ * @return Nothing, always succeeds.
+ */
+void vlc_restorecancel (int state)
+{
+#if defined (LIBVLC_USE_PTHREAD_CANCEL)
+# ifndef NDEBUG
+    int oldstate, val;
+
+    val = pthread_setcancelstate (state, &oldstate);
+    /* This should fail if an invalid value for given for state */
+    VLC_THREAD_ASSERT ("restoring cancellation");
+
+    if (oldstate != PTHREAD_CANCEL_DISABLE)
+         vlc_thread_fatal ("restoring cancellation while not disabled", EINVAL,
+                           __func__, __FILE__, __LINE__);
+# else
+    pthread_setcancelstate (state, NULL);
+# endif
+
+#else
+    vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
+    assert (state == false || state == true);
+
+    if (nfo == NULL)
+        return; /* Main thread - cannot be cancelled anyway */
+
+    assert (!nfo->killable);
+    nfo->killable = state != 0;
+
+#endif
+}
+
+/**
+ * Issues an explicit deferred cancellation point.
+ * This has no effect if thread cancellation is disabled.
+ * This can be called when there is a rather slow non-sleeping operation.
+ * This is also used to force a cancellation point in a function that would
+ * otherwise "not always" be a one (block_FifoGet() is an example).
+ */
+void vlc_testcancel (void)
+{
+#if defined (LIBVLC_USE_PTHREAD_CANCEL)
+    pthread_testcancel ();
+
+#else
+    vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
+    if (nfo == NULL)
+        return; /* Main thread - cannot be cancelled anyway */
+
+    if (nfo->killable && nfo->killed)
+    {
+        for (vlc_cleanup_t *p = nfo->cleaners; p != NULL; p = p->next)
+             p->proc (p->data);
+# if defined (LIBVLC_USE_PTHREAD)
+        pthread_exit (PTHREAD_CANCELLED);
+# elif defined (UNDER_CE)
+        ExitThread(0);
+# elif defined (WIN32)
+        _endthread ();
+# else
+#  error Not implemented!
+# endif
+    }
+#endif
+}
+
 
 struct vlc_thread_boot
 {
@@ -763,15 +1038,16 @@ static void *thread_entry (void *data)
     return NULL;
 }
 
+#undef vlc_thread_create
 /*****************************************************************************
- * vlc_thread_create: create a thread, inner version
+ * vlc_thread_create: create a thread
  *****************************************************************************
  * Note that i_priority is only taken into account on platforms supporting
  * userland real-time priority threads.
  *****************************************************************************/
-int __vlc_thread_create( vlc_object_t *p_this, const char * psz_file, int i_line,
-                         const char *psz_name, void * ( *func ) ( vlc_object_t * ),
-                         int i_priority, bool b_wait )
+int vlc_thread_create( vlc_object_t *p_this, const char * psz_file, int i_line,
+                       const char *psz_name, void *(*func) ( vlc_object_t * ),
+                       int i_priority )
 {
     int i_ret;
     vlc_object_internals_t *p_priv = vlc_internals( p_this );
@@ -786,8 +1062,6 @@ int __vlc_thread_create( vlc_object_t *p_this, const char * psz_file, int i_line
     assert( !p_priv->b_thread );
 
 #if defined( LIBVLC_USE_PTHREAD )
-    if (b_wait)
-        sem_init (&p_priv->thread_ready, 0, 0);
 #ifndef __APPLE__
     if( config_GetInt( p_this, "rt-priority" ) > 0 )
 #endif
@@ -796,30 +1070,13 @@ int __vlc_thread_create( vlc_object_t *p_this, const char * psz_file, int i_line
         if( config_GetType( p_this, "rt-offset" ) )
             i_priority += config_GetInt( p_this, "rt-offset" );
     }
-#elif defined (WIN32)
-    if (b_wait)
-        p_priv->thread_ready = CreateEvent (NULL, TRUE, FALSE, NULL);
 #endif
 
     p_priv->b_thread = true;
     i_ret = vlc_clone( &p_priv->thread_id, thread_entry, boot, i_priority );
     if( i_ret == 0 )
-    {
-        msg_Dbg( p_this, "thread %lu (%s) created at priority %d (%s:%d)",
-                 (unsigned long)p_priv->thread_id, psz_name, i_priority,
-                 psz_file, i_line );
-        if( b_wait )
-        {
-            msg_Dbg( p_this, "waiting for thread initialization" );
-#if defined (LIBVLC_USE_PTHREAD)
-            sem_wait (&p_priv->thread_ready);
-            sem_destroy (&p_priv->thread_ready);
-#elif defined (WIN32)
-            WaitForSingleObject (p_priv->thread_ready, INFINITE);
-            CloseHandle (p_priv->thread_ready);
-#endif
-        }
-    }
+        msg_Dbg( p_this, "thread (%s) created at priority %d (%s:%d)",
+                 psz_name, i_priority, psz_file, i_line );
     else
     {
         p_priv->b_thread = false;
@@ -831,20 +1088,6 @@ int __vlc_thread_create( vlc_object_t *p_this, const char * psz_file, int i_line
     return i_ret;
 }
 
-#undef vlc_thread_ready
-void vlc_thread_ready (vlc_object_t *obj)
-{
-    vlc_object_internals_t *priv = vlc_internals (obj);
-
-    assert (priv->b_thread);
-#if defined (LIBVLC_USE_PTHREAD)
-    assert (pthread_equal (pthread_self (), priv->thread_id));
-    sem_post (&priv->thread_ready);
-#elif defined (WIN32)
-    SetEvent (priv->thread_ready);
-#endif
-}
-
 /*****************************************************************************
  * vlc_thread_set_priority: set the priority of the current thread when we
  * couldn't set it in vlc_thread_create (for instance for the main thread)
@@ -920,6 +1163,7 @@ void __vlc_thread_join( vlc_object_t *p_this )
     FILETIME create_ft, exit_ft, kernel_ft, user_ft;
     int64_t real_time, kernel_time, user_time;
 
+#ifndef UNDER_CE
     if( ! DuplicateHandle(GetCurrentProcess(),
             p_priv->thread_id->handle,
             GetCurrentProcess(),
@@ -931,6 +1175,9 @@ void __vlc_thread_join( vlc_object_t *p_this )
         p_priv->b_thread = false;
         return; /* We have a problem! */
     }
+#else
+    hThread = p_priv->thread_id->handle;
+#endif
 
     vlc_join( p_priv->thread_id, NULL );
 
@@ -986,7 +1233,7 @@ void vlc_control_cancel (int cmd, ...)
 #else
     va_list ap;
 
-    vlc_cancel_t *nfo = vlc_threadvar_get (&cancel_key);
+    vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
     if (nfo == NULL)
     {
 #ifdef WIN32
@@ -997,48 +1244,13 @@ void vlc_control_cancel (int cmd, ...)
         if (nfo == NULL)
             return; /* Uho! Expect problems! */
         *nfo = VLC_CANCEL_INIT;
-        vlc_threadvar_set (&cancel_key, nfo);
+        vlc_threadvar_set (cancel_key, nfo);
 #endif
     }
 
     va_start (ap, cmd);
     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);
-#ifndef WIN32
-                free (nfo);
-#endif
-#if defined (LIBVLC_USE_PTHREAD)
-                pthread_exit (PTHREAD_CANCELLED);
-#elif defined (UNDER_CE)
-                ExitThread(0);
-#elif defined (WIN32)
-                _endthread ();
-#else
-# error Not implemented!
-#endif
-            }
-            break;
-
         case VLC_DO_CANCEL:
             nfo->killed = true;
             break;