root/src/system/libroot/posix/pthread/pthread_cancel.cpp
/*
 * Copyright 2011, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Copyright 2008, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
 * Distributed under the terms of the MIT License.
 */


#include "pthread_private.h"

#include <syscalls.h>


static inline void
test_asynchronous_cancel(int32 flags)
{
        static const int32 kFlags = THREAD_CANCELED | THREAD_CANCEL_ENABLED
                | THREAD_CANCEL_ASYNCHRONOUS;

        if ((~flags & kFlags) == 0)
                pthread_exit(PTHREAD_CANCELED);
}


/*!     Signal handler like function invoked when this thread has been canceled.
        Has the simple signal handler signature, since it is invoked just like a
        signal handler.
*/
static void
asynchronous_cancel_thread(int)
{
        pthread_t thread = pthread_self();

        // Exit when asynchronous cancellation is enabled, otherwise we don't have
        // to do anything -- the syscall interrupting side effect is all we need.
        if ((atomic_get(&thread->flags) & THREAD_CANCEL_ASYNCHRONOUS) != 0)
                pthread_exit(PTHREAD_CANCELED);
}


// #pragma mark - public API


int
pthread_cancel(pthread_t thread)
{
        // set the canceled flag
        int32 oldFlags = atomic_or(&thread->flags, THREAD_CANCELED);

        // If the flag was already set, we're done.
        if ((oldFlags & THREAD_CANCELED) != 0)
                return 0;

        // If cancellation is enabled, notify the thread. This will call the
        // asynchronous_cancel_thread() handler.
        if ((oldFlags & THREAD_CANCEL_ENABLED) != 0) {
                int result = _kern_cancel_thread(thread->id, &asynchronous_cancel_thread);
                if (result == B_BAD_THREAD_ID)
                        return ESRCH;
                return result;
        }

        return 0;
}


int
pthread_setcancelstate(int state, int *_oldState)
{
        pthread_thread* thread = pthread_self();
        if (thread == NULL)
                return EINVAL;

        // set the new flags
        int32 oldFlags;
        if (state == PTHREAD_CANCEL_ENABLE) {
                oldFlags = atomic_or(&thread->flags, THREAD_CANCEL_ENABLED);
                test_asynchronous_cancel(oldFlags | THREAD_CANCEL_ENABLED);
        } else if (state == PTHREAD_CANCEL_DISABLE) {
                oldFlags = atomic_and(&thread->flags, ~(int32)THREAD_CANCEL_ENABLED);
                test_asynchronous_cancel(oldFlags);
        } else
                return EINVAL;

        // return the old state
        if (_oldState != NULL) {
                *_oldState = (oldFlags & THREAD_CANCEL_ENABLED) != 0
                        ? PTHREAD_CANCEL_ENABLE : PTHREAD_CANCEL_DISABLE;
        }

        return 0;
}


int
pthread_setcanceltype(int type, int *_oldType)
{
        pthread_thread* thread = pthread_self();
        if (thread == NULL)
                return EINVAL;

        // set the new type
        int32 oldFlags;
        if (type == PTHREAD_CANCEL_DEFERRED) {
                oldFlags = atomic_and(&thread->flags,
                        ~(int32)THREAD_CANCEL_ASYNCHRONOUS);
                test_asynchronous_cancel(oldFlags);
        } else if (type == PTHREAD_CANCEL_ASYNCHRONOUS) {
                oldFlags = atomic_or(&thread->flags, THREAD_CANCEL_ASYNCHRONOUS);
                test_asynchronous_cancel(oldFlags | THREAD_CANCEL_ASYNCHRONOUS);
        } else
                return EINVAL;

        // return the old type
        if (_oldType != NULL) {
                *_oldType = (oldFlags & THREAD_CANCEL_ASYNCHRONOUS) != 0
                        ? PTHREAD_CANCEL_ASYNCHRONOUS : PTHREAD_CANCEL_DEFERRED;
        }

        return 0;
}


void
pthread_testcancel(void)
{
        pthread_thread* thread = pthread_self();
        if (thread == NULL)
                return;

        static const int32 kFlags = THREAD_CANCELED | THREAD_CANCEL_ENABLED;

        if ((~atomic_get(&thread->flags) & kFlags) == 0)
                pthread_exit(PTHREAD_CANCELED);
}