mirror of
git://sourceware.org/git/newlib-cygwin.git
synced 2025-01-17 03:49:46 +08:00
fdb7df230d
This patch adds pthread_getname_np and pthread_setname_np. These were added to glibc in 2.12[1] and are also present in some form on NetBSD and several UNIXes. The code is based on NetBSD's implementation with changes to better match Linux behaviour. Implementation quirks: * pthread_setname_np with a NULL pointer segfaults (as linux) * pthread_setname_np returns ERANGE for names longer than 16 characters (as linux) * pthread_getname_np with a NULL pointer returns EFAULT (as linux) * pthread_getname_np with a buffer length of less than 16 returns ERANGE (as linux) * pthread_getname_np truncates the thread name to fit the buffer length. This guarantees success even when the default thread name is longer than 16 characters, but means there is no way to discover the actual length of the thread name. (Linux always truncates the thread name to 16 characters) * Changing program_invocation_short_name changes the default thread name (on linux, it has no effect on the default thread name) I'll leave it up to you to decide if any of these matter. This is implemented via class pthread_attr to make it easier to add pthread_attr_[gs]etname_np (present in NetBSD and some UNIXes) should it ever be added to Linux (or we decide we want it anyway). [1] https://sourceware.org/git/?p=glibc.git;a=blob;f=NEWS
4202 lines
90 KiB
C++
4202 lines
90 KiB
C++
/* thread.cc: Locking and threading module functions
|
|
|
|
This file is part of Cygwin.
|
|
|
|
This software is a copyrighted work licensed under the terms of the
|
|
Cygwin license. Please consult the file "CYGWIN_LICENSE" for
|
|
details. */
|
|
|
|
/* Implementation overview and caveats:
|
|
|
|
Win32 puts some contraints on what can and cannot be implemented. Where
|
|
possible we work around those contrainsts. Where we cannot work around
|
|
the constraints we either pretend to be conformant, or return an error
|
|
code.
|
|
|
|
Some caveats: PROCESS_SHARED objects, while they pretend to be process
|
|
shared, may not actually work. Some test cases are needed to determine
|
|
win32's behaviour. My suspicion is that the win32 handle needs to be
|
|
opened with different flags for proper operation.
|
|
|
|
R.Collins, April 2001. */
|
|
|
|
#include "winsup.h"
|
|
#include "miscfuncs.h"
|
|
#include "path.h"
|
|
#include <stdlib.h>
|
|
#include "sigproc.h"
|
|
#include "fhandler.h"
|
|
#include "dtable.h"
|
|
#include "cygheap.h"
|
|
#include "ntdll.h"
|
|
#include "cygwait.h"
|
|
|
|
extern "C" void __fp_lock_all ();
|
|
extern "C" void __fp_unlock_all ();
|
|
extern "C" bool valid_sched_parameters(const struct sched_param *);
|
|
extern "C" int sched_get_thread_priority(HANDLE thread);
|
|
extern "C" int sched_set_thread_priority(HANDLE thread, int priority);
|
|
static inline verifyable_object_state
|
|
verifyable_object_isvalid (void const * objectptr, thread_magic_t magic,
|
|
void *static_ptr1 = NULL,
|
|
void *static_ptr2 = NULL,
|
|
void *static_ptr3 = NULL);
|
|
|
|
extern int threadsafe;
|
|
|
|
const pthread_t pthread_mutex::_new_mutex = (pthread_t) 1;
|
|
const pthread_t pthread_mutex::_unlocked_mutex = (pthread_t) 2;
|
|
const pthread_t pthread_mutex::_destroyed_mutex = (pthread_t) 3;
|
|
|
|
|
|
template <typename T>
|
|
static inline
|
|
void
|
|
delete_and_clear (T * * const ptr)
|
|
{
|
|
delete *ptr;
|
|
*ptr = 0;
|
|
}
|
|
|
|
|
|
inline bool
|
|
pthread_mutex::no_owner()
|
|
{
|
|
int res;
|
|
if (!owner)
|
|
{
|
|
debug_printf ("NULL owner value");
|
|
res = 1;
|
|
}
|
|
else if (owner == _destroyed_mutex)
|
|
{
|
|
paranoid_printf ("attempt to use destroyed mutex");
|
|
res = 1;
|
|
}
|
|
else if (owner == _new_mutex || owner == _unlocked_mutex)
|
|
res = 1;
|
|
else
|
|
res = 0;
|
|
return res;
|
|
}
|
|
|
|
#undef __getreent
|
|
extern "C" struct _reent *
|
|
__getreent ()
|
|
{
|
|
return &_my_tls.local_clib;
|
|
}
|
|
|
|
extern "C" void
|
|
__cygwin_lock_init (_LOCK_T *lock)
|
|
{
|
|
*lock = _LOCK_T_INITIALIZER;
|
|
}
|
|
|
|
extern "C" void
|
|
__cygwin_lock_init_recursive (_LOCK_T *lock)
|
|
{
|
|
*lock = _LOCK_T_RECURSIVE_INITIALIZER;
|
|
}
|
|
|
|
extern "C" void
|
|
__cygwin_lock_fini (_LOCK_T *lock)
|
|
{
|
|
pthread_mutex_destroy ((pthread_mutex_t*) lock);
|
|
}
|
|
|
|
extern "C" void
|
|
__cygwin_lock_lock (_LOCK_T *lock)
|
|
{
|
|
paranoid_printf ("threadcount %d. locking", MT_INTERFACE->threadcount);
|
|
pthread_mutex_lock ((pthread_mutex_t*) lock);
|
|
}
|
|
|
|
extern "C" int
|
|
__cygwin_lock_trylock (_LOCK_T *lock)
|
|
{
|
|
return pthread_mutex_trylock ((pthread_mutex_t*) lock);
|
|
}
|
|
|
|
|
|
extern "C" void
|
|
__cygwin_lock_unlock (_LOCK_T *lock)
|
|
{
|
|
pthread_mutex_unlock ((pthread_mutex_t*) lock);
|
|
paranoid_printf ("threadcount %d. unlocked", MT_INTERFACE->threadcount);
|
|
}
|
|
|
|
static inline verifyable_object_state
|
|
verifyable_object_isvalid (void const *objectptr, thread_magic_t magic, void *static_ptr1,
|
|
void *static_ptr2, void *static_ptr3)
|
|
{
|
|
verifyable_object_state state = INVALID_OBJECT;
|
|
|
|
__try
|
|
{
|
|
if (!objectptr || !(*(const char **) objectptr))
|
|
__leave;
|
|
|
|
verifyable_object **object = (verifyable_object **) objectptr;
|
|
|
|
if ((static_ptr1 && *object == static_ptr1) ||
|
|
(static_ptr2 && *object == static_ptr2) ||
|
|
(static_ptr3 && *object == static_ptr3))
|
|
state = VALID_STATIC_OBJECT;
|
|
else if ((*object)->magic == magic)
|
|
state = VALID_OBJECT;
|
|
}
|
|
__except (NO_ERROR) {}
|
|
__endtry
|
|
return state;
|
|
}
|
|
|
|
/* static members */
|
|
inline bool
|
|
pthread_attr::is_good_object (pthread_attr_t const *attr)
|
|
{
|
|
if (verifyable_object_isvalid (attr, PTHREAD_ATTR_MAGIC) != VALID_OBJECT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
inline bool
|
|
pthread_condattr::is_good_object (pthread_condattr_t const *attr)
|
|
{
|
|
if (verifyable_object_isvalid (attr, PTHREAD_CONDATTR_MAGIC) != VALID_OBJECT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
inline bool
|
|
pthread_rwlockattr::is_good_object (pthread_rwlockattr_t const *attr)
|
|
{
|
|
if (verifyable_object_isvalid (attr, PTHREAD_RWLOCKATTR_MAGIC) != VALID_OBJECT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
inline bool
|
|
pthread_key::is_good_object (pthread_key_t const *key)
|
|
{
|
|
if (verifyable_object_isvalid (key, PTHREAD_KEY_MAGIC) != VALID_OBJECT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
inline bool
|
|
pthread_spinlock::is_good_object (pthread_spinlock_t const *mutex)
|
|
{
|
|
if (verifyable_object_isvalid (mutex, PTHREAD_SPINLOCK_MAGIC) != VALID_OBJECT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
inline bool
|
|
pthread_mutex::is_good_object (pthread_mutex_t const *mutex)
|
|
{
|
|
if (verifyable_object_isvalid (mutex, PTHREAD_MUTEX_MAGIC) != VALID_OBJECT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
inline bool
|
|
pthread_mutex::is_initializer (pthread_mutex_t const *mutex)
|
|
{
|
|
if (verifyable_object_isvalid (mutex, PTHREAD_MUTEX_MAGIC,
|
|
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP,
|
|
PTHREAD_NORMAL_MUTEX_INITIALIZER_NP,
|
|
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP) != VALID_STATIC_OBJECT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
inline bool
|
|
pthread_mutex::is_initializer_or_object (pthread_mutex_t const *mutex)
|
|
{
|
|
if (verifyable_object_isvalid (mutex, PTHREAD_MUTEX_MAGIC,
|
|
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP,
|
|
PTHREAD_NORMAL_MUTEX_INITIALIZER_NP,
|
|
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP) == INVALID_OBJECT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
/* FIXME: Accommodate PTHREAD_MUTEX_ERRORCHECK */
|
|
inline bool
|
|
pthread_mutex::can_be_unlocked ()
|
|
{
|
|
pthread_t self = pthread::self ();
|
|
/* Check if the mutex is owned by the current thread and can be unlocked.
|
|
* Also check for the ANONYMOUS owner to cover NORMAL mutexes as well. */
|
|
bool res = type == PTHREAD_MUTEX_NORMAL || no_owner ()
|
|
|| (recursion_counter == 1 && pthread::equal (owner, self));
|
|
pthread_printf ("recursion_counter %u res %d", recursion_counter, res);
|
|
return res;
|
|
}
|
|
|
|
inline bool
|
|
pthread_mutexattr::is_good_object (pthread_mutexattr_t const * attr)
|
|
{
|
|
if (verifyable_object_isvalid (attr, PTHREAD_MUTEXATTR_MAGIC) != VALID_OBJECT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
inline bool __attribute__ ((used))
|
|
pthread::is_good_object (pthread_t const *thread)
|
|
{
|
|
if (verifyable_object_isvalid (thread, PTHREAD_MAGIC) != VALID_OBJECT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
/* Thread synchronisation */
|
|
inline bool
|
|
pthread_cond::is_good_object (pthread_cond_t const *cond)
|
|
{
|
|
if (verifyable_object_isvalid (cond, PTHREAD_COND_MAGIC) != VALID_OBJECT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
inline bool
|
|
pthread_cond::is_initializer (pthread_cond_t const *cond)
|
|
{
|
|
if (verifyable_object_isvalid (cond, PTHREAD_COND_MAGIC, PTHREAD_COND_INITIALIZER) != VALID_STATIC_OBJECT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
inline bool
|
|
pthread_cond::is_initializer_or_object (pthread_cond_t const *cond)
|
|
{
|
|
if (verifyable_object_isvalid (cond, PTHREAD_COND_MAGIC, PTHREAD_COND_INITIALIZER) == INVALID_OBJECT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
inline bool
|
|
pthread_barrierattr::is_good_object (pthread_barrierattr_t const *cond)
|
|
{
|
|
if (verifyable_object_isvalid (cond, PTHREAD_BARRIERATTR_MAGIC)
|
|
!= VALID_OBJECT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
inline bool
|
|
pthread_barrier::is_good_object (pthread_barrier_t const *cond)
|
|
{
|
|
if (verifyable_object_isvalid (cond, PTHREAD_BARRIER_MAGIC) != VALID_OBJECT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
/* RW locks */
|
|
inline bool
|
|
pthread_rwlock::is_good_object (pthread_rwlock_t const *rwlock)
|
|
{
|
|
if (verifyable_object_isvalid (rwlock, PTHREAD_RWLOCK_MAGIC) != VALID_OBJECT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
inline bool
|
|
pthread_rwlock::is_initializer (pthread_rwlock_t const *rwlock)
|
|
{
|
|
if (verifyable_object_isvalid (rwlock, PTHREAD_RWLOCK_MAGIC, PTHREAD_RWLOCK_INITIALIZER) != VALID_STATIC_OBJECT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
inline bool
|
|
pthread_rwlock::is_initializer_or_object (pthread_rwlock_t const *rwlock)
|
|
{
|
|
if (verifyable_object_isvalid (rwlock, PTHREAD_RWLOCK_MAGIC, PTHREAD_RWLOCK_INITIALIZER) == INVALID_OBJECT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
inline bool
|
|
semaphore::is_good_object (sem_t const * sem)
|
|
{
|
|
if (verifyable_object_isvalid (sem, SEM_MAGIC) != VALID_OBJECT)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
void
|
|
MTinterface::Init ()
|
|
{
|
|
pthread_mutex::init_mutex ();
|
|
pthread_cond::init_mutex ();
|
|
pthread_rwlock::init_mutex ();
|
|
}
|
|
|
|
void
|
|
MTinterface::fixup_before_fork ()
|
|
{
|
|
pthread_key::fixup_before_fork ();
|
|
semaphore::fixup_before_fork ();
|
|
}
|
|
|
|
/* This function is called from a single threaded process */
|
|
void
|
|
MTinterface::fixup_after_fork ()
|
|
{
|
|
pthread_key::fixup_after_fork ();
|
|
|
|
threadcount = 0;
|
|
pthread::init_mainthread ();
|
|
|
|
pthread::fixup_after_fork ();
|
|
pthread_mutex::fixup_after_fork ();
|
|
pthread_cond::fixup_after_fork ();
|
|
pthread_rwlock::fixup_after_fork ();
|
|
semaphore::fixup_after_fork ();
|
|
}
|
|
|
|
/* pthread calls */
|
|
|
|
/* static methods */
|
|
void
|
|
pthread::init_mainthread ()
|
|
{
|
|
pthread *thread = _my_tls.tid;
|
|
if (!thread)
|
|
{
|
|
thread = new pthread ();
|
|
if (!thread)
|
|
api_fatal ("failed to create mainthread object");
|
|
}
|
|
|
|
thread->set_tls_self_pointer ();
|
|
thread->thread_id = GetCurrentThreadId ();
|
|
if (!DuplicateHandle (GetCurrentProcess (), GetCurrentThread (),
|
|
GetCurrentProcess (), &thread->win32_obj_id,
|
|
0, FALSE, DUPLICATE_SAME_ACCESS))
|
|
api_fatal ("failed to create mainthread handle");
|
|
if (!thread->create_cancel_event ())
|
|
api_fatal ("couldn't create cancel event for main thread");
|
|
VerifyHandle (thread->win32_obj_id);
|
|
/* Make sure the pthread mutex is recursive. See comment in
|
|
pthread::precreate (called only for subsequent pthreads)
|
|
for a description. */
|
|
thread->mutex.set_type (PTHREAD_MUTEX_RECURSIVE);
|
|
thread->postcreate ();
|
|
}
|
|
|
|
pthread *
|
|
pthread::self ()
|
|
{
|
|
pthread *thread = _my_tls.tid;
|
|
if (!thread)
|
|
{
|
|
thread = pthread_null::get_null_pthread ();
|
|
thread->set_tls_self_pointer ();
|
|
}
|
|
return thread;
|
|
}
|
|
|
|
void
|
|
pthread::set_tls_self_pointer ()
|
|
{
|
|
cygtls = &_my_tls;
|
|
_my_tls.tid = this;
|
|
}
|
|
|
|
List<pthread> pthread::threads;
|
|
|
|
/* member methods */
|
|
pthread::pthread ():verifyable_object (PTHREAD_MAGIC), win32_obj_id (0),
|
|
valid (false), suspended (false), canceled (false),
|
|
cancelstate (0), canceltype (0), cancel_event (0),
|
|
joiner (NULL), next (NULL), cleanup_stack (NULL)
|
|
{
|
|
if (this != pthread_null::get_null_pthread ())
|
|
threads.insert (this);
|
|
sigprocmask (SIG_SETMASK, NULL, &parent_sigmask);
|
|
}
|
|
|
|
pthread::~pthread ()
|
|
{
|
|
if (win32_obj_id)
|
|
CloseHandle (win32_obj_id);
|
|
if (cancel_event)
|
|
CloseHandle (cancel_event);
|
|
|
|
if (this != pthread_null::get_null_pthread ())
|
|
threads.remove (this);
|
|
}
|
|
|
|
bool
|
|
pthread::create_cancel_event ()
|
|
{
|
|
cancel_event = ::CreateEvent (&sec_none_nih, true, false, NULL);
|
|
if (!cancel_event)
|
|
{
|
|
system_printf ("couldn't create cancel event, %E");
|
|
/* we need the event for correct behaviour */
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void
|
|
pthread::precreate (pthread_attr *newattr)
|
|
{
|
|
pthread_mutex *verifyable_mutex_obj = &mutex;
|
|
|
|
/* already running ? */
|
|
if (win32_obj_id)
|
|
return;
|
|
|
|
if (newattr)
|
|
{
|
|
attr.joinable = newattr->joinable;
|
|
attr.contentionscope = newattr->contentionscope;
|
|
attr.inheritsched = newattr->inheritsched;
|
|
attr.stackaddr = newattr->stackaddr;
|
|
attr.stacksize = newattr->stacksize;
|
|
attr.guardsize = newattr->guardsize;
|
|
}
|
|
|
|
if (!pthread_mutex::is_good_object (&verifyable_mutex_obj))
|
|
{
|
|
thread_printf ("New thread object access mutex is not valid. this %p",
|
|
this);
|
|
magic = 0;
|
|
return;
|
|
}
|
|
/* This mutex MUST be recursive. Consider the following scenario:
|
|
- The thread installs a cleanup handler.
|
|
- The cleanup handler calls a function which itself installs a
|
|
cleanup handler.
|
|
- pthread_cancel is called for this thread.
|
|
- The thread's cleanup handler is called under mutex lock condition.
|
|
- The cleanup handler calls the subsequent function with cleanup handler.
|
|
- The function runs to completion, so it calls pthread_cleanup_pop.
|
|
- pthread_cleanup_pop calls pthread::pop_cleanup_handler which will again
|
|
try to lock the mutex.
|
|
- Deadlock. */
|
|
mutex.set_type (PTHREAD_MUTEX_RECURSIVE);
|
|
if (!create_cancel_event ())
|
|
magic = 0;
|
|
}
|
|
|
|
bool
|
|
pthread::create (void *(*func) (void *), pthread_attr *newattr,
|
|
void *threadarg)
|
|
{
|
|
bool retval;
|
|
|
|
precreate (newattr);
|
|
if (!magic)
|
|
return false;
|
|
|
|
function = func;
|
|
arg = threadarg;
|
|
|
|
mutex.lock ();
|
|
/* stackaddr holds the uppermost stack address. See the comments in
|
|
pthread_attr_setstack and pthread_attr_setstackaddr for a description. */
|
|
ULONG stacksize = attr.stacksize ?: get_rlimit_stack ();
|
|
PVOID stackaddr = attr.stackaddr ? ((caddr_t) attr.stackaddr - stacksize)
|
|
: NULL;
|
|
win32_obj_id = CygwinCreateThread (thread_init_wrapper, this, stackaddr,
|
|
stacksize, attr.guardsize, 0, &thread_id);
|
|
|
|
if (!win32_obj_id)
|
|
{
|
|
thread_printf ("CreateThread failed: this %p, %E", this);
|
|
magic = 0;
|
|
}
|
|
else
|
|
{
|
|
postcreate ();
|
|
while (!cygtls)
|
|
yield ();
|
|
}
|
|
retval = magic;
|
|
mutex.unlock ();
|
|
return retval;
|
|
}
|
|
|
|
void
|
|
pthread::postcreate ()
|
|
{
|
|
valid = true;
|
|
|
|
InterlockedIncrement (&MT_INTERFACE->threadcount);
|
|
|
|
/* Per POSIX the new thread inherits the sched priority from its caller
|
|
thread if PTHREAD_INHERIT_SCHED is set.
|
|
FIXME: set the priority appropriately for system contention scope */
|
|
if (attr.inheritsched == PTHREAD_INHERIT_SCHED)
|
|
attr.schedparam.sched_priority
|
|
= sched_get_thread_priority (GetCurrentThread ());
|
|
if (attr.schedparam.sched_priority)
|
|
sched_set_thread_priority (win32_obj_id, attr.schedparam.sched_priority);
|
|
}
|
|
|
|
void
|
|
pthread::exit (void *value_ptr)
|
|
{
|
|
class pthread *thread = this;
|
|
_cygtls *tls = cygtls; /* Save cygtls before deleting this. */
|
|
|
|
// run cleanup handlers
|
|
pop_all_cleanup_handlers ();
|
|
|
|
pthread_key::run_all_destructors ();
|
|
|
|
mutex.lock ();
|
|
// cleanup if thread is in detached state and not joined
|
|
if (equal (joiner, thread))
|
|
delete this;
|
|
else
|
|
{
|
|
valid = false;
|
|
return_ptr = value_ptr;
|
|
mutex.unlock ();
|
|
}
|
|
|
|
if (_my_tls.local_clib.__sdidinit < 0)
|
|
_my_tls.local_clib.__sdidinit = 0;
|
|
(_reclaim_reent) (_REENT);
|
|
|
|
if (InterlockedDecrement (&MT_INTERFACE->threadcount) == 0)
|
|
::exit (0);
|
|
else
|
|
{
|
|
if (tls == _main_tls)
|
|
{
|
|
cygheap->find_tls (tls); /* Lock _main_tls mutex. */
|
|
_cygtls *dummy = (_cygtls *) malloc (sizeof (_cygtls));
|
|
*dummy = *_main_tls;
|
|
_main_tls = dummy;
|
|
_main_tls->initialized = 0;
|
|
}
|
|
/* This also unlocks and closes the _main_tls mutex. */
|
|
tls->remove (INFINITE);
|
|
ExitThread (0);
|
|
}
|
|
}
|
|
|
|
int
|
|
pthread::cancel ()
|
|
{
|
|
class pthread *thread = this;
|
|
class pthread *self = pthread::self ();
|
|
|
|
mutex.lock ();
|
|
|
|
if (!valid)
|
|
{
|
|
mutex.unlock ();
|
|
return 0;
|
|
}
|
|
|
|
if (canceltype == PTHREAD_CANCEL_DEFERRED ||
|
|
cancelstate == PTHREAD_CANCEL_DISABLE)
|
|
{
|
|
// cancel deferred
|
|
mutex.unlock ();
|
|
canceled = true;
|
|
SetEvent (cancel_event);
|
|
return 0;
|
|
}
|
|
else if (equal (thread, self))
|
|
{
|
|
mutex.unlock ();
|
|
cancel_self ();
|
|
return 0; // Never reached
|
|
}
|
|
|
|
// cancel asynchronous
|
|
SuspendThread (win32_obj_id);
|
|
if (WaitForSingleObject (win32_obj_id, 0) == WAIT_TIMEOUT)
|
|
{
|
|
CONTEXT context;
|
|
context.ContextFlags = CONTEXT_CONTROL;
|
|
GetThreadContext (win32_obj_id, &context);
|
|
/* The OS is not foolproof in terms of asynchronous thread cancellation
|
|
and tends to hang infinitely if we change the instruction pointer.
|
|
So just don't cancel asynchronously if the thread is currently
|
|
executing Windows code. Rely on deferred cancellation in this case. */
|
|
threadlist_t *tl_entry = cygheap->find_tls (cygtls);
|
|
if (!cygtls->inside_kernel (&context))
|
|
{
|
|
#ifdef __x86_64__
|
|
context.Rip = (ULONG_PTR) pthread::static_cancel_self;
|
|
#else
|
|
context.Eip = (DWORD) pthread::static_cancel_self;
|
|
#endif
|
|
SetThreadContext (win32_obj_id, &context);
|
|
}
|
|
cygheap->unlock_tls (tl_entry);
|
|
}
|
|
mutex.unlock ();
|
|
/* See above. For instance, a thread which waits for a semaphore in sem_wait
|
|
will call cygwait which in turn calls WFMO. While this WFMO call
|
|
is cancelable by setting the thread's cancel_event object, the OS
|
|
apparently refuses to set the thread's context and continues to wait for
|
|
the WFMO conditions. This is *not* reflected in the return value of
|
|
SetThreadContext or ResumeThread, btw.
|
|
So, what we do here is to set the cancel_event as well to allow at least
|
|
a deferred cancel. */
|
|
canceled = true;
|
|
SetEvent (cancel_event);
|
|
ResumeThread (win32_obj_id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* TODO: Insert pthread_testcancel into the required functions.
|
|
|
|
Here are the lists of required and optional functions per POSIX.1-2001
|
|
and POSIX.1-2008. A star (*) indicates that the Cygwin function already
|
|
is a cancellation point (aka "calls pthread_testcancel"), an o (o)
|
|
indicates that the function is not implemented in Cygwin.
|
|
|
|
Required cancellation points:
|
|
|
|
* accept ()
|
|
o aio_suspend ()
|
|
* clock_nanosleep ()
|
|
* close ()
|
|
* connect ()
|
|
* creat ()
|
|
* fcntl () F_SETLKW
|
|
* fdatasync ()
|
|
* fsync ()
|
|
o getmsg ()
|
|
o getpmsg ()
|
|
* lockf () F_LOCK
|
|
* mq_receive ()
|
|
* mq_send ()
|
|
* mq_timedreceive ()
|
|
* mq_timedsend ()
|
|
msgrcv ()
|
|
msgsnd ()
|
|
* msync ()
|
|
* nanosleep ()
|
|
* open ()
|
|
* openat ()
|
|
* pause ()
|
|
* poll ()
|
|
* pread ()
|
|
* pselect ()
|
|
* pthread_cond_timedwait ()
|
|
* pthread_cond_wait ()
|
|
* pthread_join ()
|
|
* pthread_testcancel ()
|
|
o putmsg ()
|
|
o putpmsg ()
|
|
* pwrite ()
|
|
* read ()
|
|
* readv ()
|
|
* recv ()
|
|
* recvfrom ()
|
|
* recvmsg ()
|
|
* select ()
|
|
* sem_timedwait ()
|
|
* sem_wait ()
|
|
* send ()
|
|
* sendmsg ()
|
|
* sendto ()
|
|
* sigpause ()
|
|
* sigsuspend ()
|
|
o sigtimedwait ()
|
|
* sigwait ()
|
|
* sigwaitinfo ()
|
|
* sleep ()
|
|
* system ()
|
|
* tcdrain ()
|
|
* usleep ()
|
|
* wait ()
|
|
* wait3()
|
|
o waitid ()
|
|
* waitpid ()
|
|
* write ()
|
|
* writev ()
|
|
|
|
Optional cancellation points:
|
|
|
|
access ()
|
|
asctime ()
|
|
asctime_r ()
|
|
catclose () Implemented externally: libcatgets
|
|
catgets () Implemented externally: libcatgets
|
|
catopen () Implemented externally: libcatgets
|
|
chmod ()
|
|
chown ()
|
|
closedir ()
|
|
closelog ()
|
|
ctermid ()
|
|
ctime ()
|
|
ctime_r ()
|
|
dbm_close () Implemented externally: libgdbm
|
|
dbm_delete () Implemented externally: libgdbm
|
|
dbm_fetch () Implemented externally: libgdbm
|
|
dbm_nextkey () Implemented externally: libgdbm
|
|
dbm_open () Implemented externally: libgdbm
|
|
dbm_store () Implemented externally: libgdbm
|
|
dlclose ()
|
|
dlopen ()
|
|
dprintf ()
|
|
endgrent ()
|
|
endhostent ()
|
|
o endnetent ()
|
|
endprotoent ()
|
|
endpwent ()
|
|
endservent ()
|
|
endutxent ()
|
|
faccessat ()
|
|
fchmod ()
|
|
fchmodat ()
|
|
fchown ()
|
|
fchownat ()
|
|
* fclose ()
|
|
* fcntl () (any value)
|
|
fflush ()
|
|
fgetc ()
|
|
fgetpos ()
|
|
fgets ()
|
|
fgetwc ()
|
|
fgetws ()
|
|
o fmtmsg ()
|
|
fopen ()
|
|
fpathconf ()
|
|
fprintf ()
|
|
fputc ()
|
|
fputs ()
|
|
fputwc ()
|
|
fputws ()
|
|
fread ()
|
|
freopen ()
|
|
fscanf ()
|
|
fseek ()
|
|
fseeko ()
|
|
fsetpos ()
|
|
fstat ()
|
|
fstatat ()
|
|
ftell ()
|
|
ftello ()
|
|
ftw ()
|
|
futimens ()
|
|
fwprintf ()
|
|
fwrite ()
|
|
fwscanf ()
|
|
getaddrinfo ()
|
|
getc ()
|
|
getc_unlocked ()
|
|
getchar ()
|
|
getchar_unlocked ()
|
|
getcwd ()
|
|
o getdate ()
|
|
getdelim ()
|
|
getgrent ()
|
|
getgrgid ()
|
|
getgrgid_r ()
|
|
getgrnam ()
|
|
getgrnam_r ()
|
|
gethostbyaddr ()
|
|
gethostbyname ()
|
|
gethostent ()
|
|
gethostid ()
|
|
gethostname ()
|
|
getline ()
|
|
getlogin ()
|
|
getlogin_r ()
|
|
getnameinfo ()
|
|
o getnetbyaddr ()
|
|
o getnetbyname ()
|
|
o getnetent ()
|
|
getopt () (if opterr is nonzero)
|
|
getprotobyname ()
|
|
getprotobynumber ()
|
|
getprotoent ()
|
|
getpwent ()
|
|
* getpwnam ()
|
|
* getpwnam_r ()
|
|
* getpwuid ()
|
|
* getpwuid_r ()
|
|
gets ()
|
|
getservbyname ()
|
|
getservbyport ()
|
|
getservent ()
|
|
getutxent ()
|
|
getutxid ()
|
|
getutxline ()
|
|
getwc ()
|
|
getwchar ()
|
|
getwd ()
|
|
glob ()
|
|
iconv_close () Implemented externally: libiconv
|
|
iconv_open () Implemented externally: libiconv
|
|
ioctl ()
|
|
link ()
|
|
linkat ()
|
|
o lio_listio ()
|
|
localtime ()
|
|
localtime_r ()
|
|
* lockf ()
|
|
lseek ()
|
|
lstat ()
|
|
mkdir ()
|
|
mkdirat ()
|
|
mkdtemp ()
|
|
mkfifo ()
|
|
mkfifoat ()
|
|
mknod ()
|
|
mknodat ()
|
|
mkstemp ()
|
|
mktime ()
|
|
nftw ()
|
|
opendir ()
|
|
openlog ()
|
|
pathconf ()
|
|
pclose ()
|
|
perror ()
|
|
popen ()
|
|
posix_fadvise ()
|
|
posix_fallocate ()
|
|
posix_madvise ()
|
|
posix_openpt ()
|
|
posix_spawn ()
|
|
posix_spawnp ()
|
|
o posix_trace_clear ()
|
|
o posix_trace_close ()
|
|
o posix_trace_create ()
|
|
o posix_trace_create_withlog ()
|
|
o posix_trace_eventtypelist_getnext_id ()
|
|
o posix_trace_eventtypelist_rewind ()
|
|
o posix_trace_flush ()
|
|
o posix_trace_get_attr ()
|
|
o posix_trace_get_filter ()
|
|
o posix_trace_get_status ()
|
|
o posix_trace_getnext_event ()
|
|
o posix_trace_open ()
|
|
o posix_trace_rewind ()
|
|
o posix_trace_set_filter ()
|
|
o posix_trace_shutdown ()
|
|
o posix_trace_timedgetnext_event ()
|
|
o posix_typed_mem_open ()
|
|
printf ()
|
|
psiginfo ()
|
|
psignal ()
|
|
pthread_rwlock_rdlock ()
|
|
o pthread_rwlock_timedrdlock ()
|
|
o pthread_rwlock_timedwrlock ()
|
|
pthread_rwlock_wrlock ()
|
|
putc ()
|
|
putc_unlocked ()
|
|
putchar ()
|
|
putchar_unlocked ()
|
|
puts ()
|
|
pututxline ()
|
|
putwc ()
|
|
putwchar ()
|
|
readdir ()
|
|
readdir_r ()
|
|
readlink ()
|
|
readlinkat ()
|
|
remove ()
|
|
rename ()
|
|
renameat ()
|
|
rewind ()
|
|
rewinddir ()
|
|
scandir ()
|
|
scanf ()
|
|
seekdir ()
|
|
semop ()
|
|
setgrent ()
|
|
sethostent ()
|
|
o setnetent ()
|
|
setprotoent ()
|
|
setpwent ()
|
|
setservent ()
|
|
setutxent ()
|
|
sigpause ()
|
|
stat ()
|
|
strerror ()
|
|
strerror_r ()
|
|
strftime ()
|
|
symlink ()
|
|
symlinkat ()
|
|
sync ()
|
|
syslog ()
|
|
tmpfile ()
|
|
tmpnam ()
|
|
ttyname ()
|
|
ttyname_r ()
|
|
tzset ()
|
|
ungetc ()
|
|
ungetwc ()
|
|
unlink ()
|
|
unlinkat ()
|
|
utime ()
|
|
utimensat ()
|
|
utimes ()
|
|
vdprintf ()
|
|
vfprintf ()
|
|
vfwprintf ()
|
|
vprintf ()
|
|
vwprintf ()
|
|
wcsftime ()
|
|
wordexp ()
|
|
wprintf ()
|
|
wscanf ()
|
|
|
|
An implementation may also mark other functions not specified in the
|
|
standard as cancellation points. In particular, an implementation is
|
|
likely to mark any nonstandard function that may block as a
|
|
cancellation point. */
|
|
|
|
void
|
|
pthread::testcancel ()
|
|
{
|
|
if (cancelstate == PTHREAD_CANCEL_DISABLE)
|
|
return;
|
|
|
|
/* We check for the canceled flag first. This allows to use the
|
|
pthread_testcancel function a lot without adding the overhead of
|
|
an OS call. Only if the thread is marked as canceled, we wait for
|
|
cancel_event being really set, on the off-chance that pthread_cancel
|
|
gets interrupted before calling SetEvent. */
|
|
if (canceled)
|
|
{
|
|
WaitForSingleObject (cancel_event, INFINITE);
|
|
cancel_self ();
|
|
}
|
|
}
|
|
|
|
/* Return cancel event handle if it exists *and* cancel is not disabled.
|
|
This function is supposed to be used from other functions which are
|
|
cancelable and need the cancel event in a WFMO call. */
|
|
HANDLE
|
|
pthread::get_cancel_event ()
|
|
{
|
|
pthread_t thread = pthread::self ();
|
|
|
|
return (thread && thread->cancel_event
|
|
&& thread->cancelstate != PTHREAD_CANCEL_DISABLE)
|
|
? thread->cancel_event : NULL;
|
|
}
|
|
|
|
void
|
|
pthread::static_cancel_self ()
|
|
{
|
|
pthread::self ()->cancel_self ();
|
|
}
|
|
|
|
int
|
|
pthread::setcancelstate (int state, int *oldstate)
|
|
{
|
|
if (state != PTHREAD_CANCEL_ENABLE && state != PTHREAD_CANCEL_DISABLE)
|
|
return EINVAL;
|
|
|
|
if (oldstate)
|
|
*oldstate = cancelstate;
|
|
cancelstate = state;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
pthread::setcanceltype (int type, int *oldtype)
|
|
{
|
|
if (type != PTHREAD_CANCEL_DEFERRED && type != PTHREAD_CANCEL_ASYNCHRONOUS)
|
|
return EINVAL;
|
|
|
|
if (oldtype)
|
|
*oldtype = canceltype;
|
|
canceltype = type;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
pthread::push_cleanup_handler (__pthread_cleanup_handler *handler)
|
|
{
|
|
if (this != self ())
|
|
// TODO: do it?
|
|
api_fatal ("Attempt to push a cleanup handler across threads");
|
|
handler->next = cleanup_stack;
|
|
cleanup_stack = handler;
|
|
}
|
|
|
|
void
|
|
pthread::pop_cleanup_handler (int const execute)
|
|
{
|
|
if (this != self ())
|
|
// TODO: send a signal or something to the thread ?
|
|
api_fatal ("Attempt to execute a cleanup handler across threads");
|
|
|
|
mutex.lock ();
|
|
|
|
if (cleanup_stack != NULL)
|
|
{
|
|
__pthread_cleanup_handler *handler = cleanup_stack;
|
|
|
|
if (execute)
|
|
(*handler->function) (handler->arg);
|
|
cleanup_stack = handler->next;
|
|
}
|
|
|
|
mutex.unlock ();
|
|
}
|
|
|
|
void
|
|
pthread::pop_all_cleanup_handlers ()
|
|
{
|
|
/* We will no honor cancels since the thread is exiting. */
|
|
cancelstate = PTHREAD_CANCEL_DISABLE;
|
|
|
|
while (cleanup_stack != NULL)
|
|
pop_cleanup_handler (1);
|
|
}
|
|
|
|
void
|
|
pthread::cancel_self ()
|
|
{
|
|
/* Can someone explain why the pthread:: is needed here? g++ complains
|
|
without it. */
|
|
pthread::exit (PTHREAD_CANCELED);
|
|
}
|
|
|
|
DWORD
|
|
pthread::get_thread_id ()
|
|
{
|
|
return thread_id;
|
|
}
|
|
|
|
void
|
|
pthread::_fixup_after_fork ()
|
|
{
|
|
/* set thread to not running if it is not the forking thread */
|
|
if (this != pthread::self ())
|
|
{
|
|
magic = 0;
|
|
valid = false;
|
|
win32_obj_id = NULL;
|
|
canceled = false;
|
|
cancel_event = NULL;
|
|
}
|
|
}
|
|
|
|
void
|
|
pthread::suspend_except_self ()
|
|
{
|
|
if (valid && this != pthread::self ())
|
|
SuspendThread (win32_obj_id);
|
|
}
|
|
|
|
void
|
|
pthread::resume ()
|
|
{
|
|
if (valid)
|
|
ResumeThread (win32_obj_id);
|
|
}
|
|
|
|
/* instance members */
|
|
|
|
pthread_attr::pthread_attr ():verifyable_object (PTHREAD_ATTR_MAGIC),
|
|
joinable (PTHREAD_CREATE_JOINABLE), contentionscope (PTHREAD_SCOPE_PROCESS),
|
|
inheritsched (PTHREAD_INHERIT_SCHED), stackaddr (NULL), stacksize (0),
|
|
guardsize (wincap.def_guard_page_size ()), name (NULL)
|
|
{
|
|
schedparam.sched_priority = 0;
|
|
}
|
|
|
|
pthread_attr::~pthread_attr ()
|
|
{
|
|
}
|
|
|
|
pthread_condattr::pthread_condattr ():verifyable_object
|
|
(PTHREAD_CONDATTR_MAGIC), shared (PTHREAD_PROCESS_PRIVATE),
|
|
clock_id (CLOCK_REALTIME)
|
|
{
|
|
}
|
|
|
|
pthread_condattr::~pthread_condattr ()
|
|
{
|
|
}
|
|
|
|
List<pthread_cond> pthread_cond::conds;
|
|
|
|
/* This is used for cond creation protection within a single process only */
|
|
fast_mutex NO_COPY pthread_cond::cond_initialization_lock;
|
|
|
|
/* We can only be called once.
|
|
TODO: (no rush) use a non copied memory section to
|
|
hold an initialization flag. */
|
|
void
|
|
pthread_cond::init_mutex ()
|
|
{
|
|
if (!cond_initialization_lock.init ())
|
|
api_fatal ("Could not create win32 Mutex for pthread cond static initializer support.");
|
|
}
|
|
|
|
pthread_cond::pthread_cond (pthread_condattr *attr) :
|
|
verifyable_object (PTHREAD_COND_MAGIC),
|
|
shared (0), clock_id (CLOCK_REALTIME), waiting (0), pending (0),
|
|
sem_wait (NULL), mtx_cond(NULL), next (NULL)
|
|
{
|
|
pthread_mutex *verifyable_mutex_obj;
|
|
|
|
if (attr)
|
|
{
|
|
clock_id = attr->clock_id;
|
|
|
|
if (attr->shared != PTHREAD_PROCESS_PRIVATE)
|
|
{
|
|
magic = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
verifyable_mutex_obj = &mtx_in;
|
|
if (!pthread_mutex::is_good_object (&verifyable_mutex_obj))
|
|
{
|
|
thread_printf ("Internal cond mutex is not valid. this %p", this);
|
|
magic = 0;
|
|
return;
|
|
}
|
|
/*
|
|
* Change the mutex type to NORMAL.
|
|
* This mutex MUST be of type normal
|
|
*/
|
|
mtx_in.set_type (PTHREAD_MUTEX_NORMAL);
|
|
|
|
verifyable_mutex_obj = &mtx_out;
|
|
if (!pthread_mutex::is_good_object (&verifyable_mutex_obj))
|
|
{
|
|
thread_printf ("Internal cond mutex is not valid. this %p", this);
|
|
magic = 0;
|
|
return;
|
|
}
|
|
/* Change the mutex type to NORMAL to speed up mutex operations */
|
|
mtx_out.set_type (PTHREAD_MUTEX_NORMAL);
|
|
|
|
sem_wait = ::CreateSemaphore (&sec_none_nih, 0, INT32_MAX, NULL);
|
|
if (!sem_wait)
|
|
{
|
|
pthread_printf ("CreateSemaphore failed. %E");
|
|
magic = 0;
|
|
return;
|
|
}
|
|
|
|
conds.insert (this);
|
|
}
|
|
|
|
pthread_cond::~pthread_cond ()
|
|
{
|
|
if (sem_wait)
|
|
CloseHandle (sem_wait);
|
|
|
|
conds.remove (this);
|
|
}
|
|
|
|
void
|
|
pthread_cond::unblock (const bool all)
|
|
{
|
|
LONG releaseable;
|
|
|
|
/*
|
|
* Block outgoing threads (and avoid simultanous unblocks)
|
|
*/
|
|
mtx_out.lock ();
|
|
|
|
releaseable = waiting - pending;
|
|
if (releaseable)
|
|
{
|
|
LONG released;
|
|
|
|
if (!pending)
|
|
{
|
|
/*
|
|
* Block incoming threads until all waiting threads are released.
|
|
*/
|
|
mtx_in.lock ();
|
|
|
|
/*
|
|
* Calculate releaseable again because threads can enter until
|
|
* the semaphore has been taken, but they can not leave, therefore pending
|
|
* is unchanged and releaseable can only get higher
|
|
*/
|
|
releaseable = waiting - pending;
|
|
}
|
|
|
|
released = all ? releaseable : 1;
|
|
pending += released;
|
|
/*
|
|
* Signal threads
|
|
*/
|
|
::ReleaseSemaphore (sem_wait, released, NULL);
|
|
}
|
|
|
|
/*
|
|
* And let the threads release.
|
|
*/
|
|
mtx_out.unlock ();
|
|
}
|
|
|
|
int
|
|
pthread_cond::wait (pthread_mutex_t mutex, PLARGE_INTEGER timeout)
|
|
{
|
|
DWORD rv;
|
|
|
|
mtx_in.lock ();
|
|
if (InterlockedIncrement (&waiting) == 1)
|
|
mtx_cond = mutex;
|
|
else if (mtx_cond != mutex)
|
|
{
|
|
InterlockedDecrement (&waiting);
|
|
mtx_in.unlock ();
|
|
return EINVAL;
|
|
}
|
|
mtx_in.unlock ();
|
|
|
|
/*
|
|
* Release the mutex and wait on semaphore
|
|
*/
|
|
++mutex->condwaits;
|
|
mutex->unlock ();
|
|
|
|
rv = cygwait (sem_wait, timeout, cw_cancel | cw_sig_eintr);
|
|
|
|
mtx_out.lock ();
|
|
|
|
if (rv != WAIT_OBJECT_0)
|
|
{
|
|
/*
|
|
* It might happen that a signal is sent while the thread got canceled
|
|
* or timed out. Try to take one.
|
|
* If the thread gets one than a signal|broadcast is in progress.
|
|
*/
|
|
if (WaitForSingleObject (sem_wait, 0) == WAIT_OBJECT_0)
|
|
/*
|
|
* thread got cancelled ot timed out while a signalling is in progress.
|
|
* Set wait result back to signaled
|
|
*/
|
|
rv = WAIT_OBJECT_0;
|
|
}
|
|
|
|
InterlockedDecrement (&waiting);
|
|
|
|
if (rv == WAIT_OBJECT_0 && --pending == 0)
|
|
/*
|
|
* All signaled threads are released,
|
|
* new threads can enter Wait
|
|
*/
|
|
mtx_in.unlock ();
|
|
|
|
mtx_out.unlock ();
|
|
|
|
mutex->lock ();
|
|
--mutex->condwaits;
|
|
|
|
if (rv == WAIT_CANCELED)
|
|
pthread::static_cancel_self ();
|
|
else if (rv == WAIT_SIGNALED)
|
|
/* SUSv3 states: If a signal is delivered to a thread waiting for a
|
|
condition variable, upon return from the signal handler the thread
|
|
resumes waiting for the condition variable as if it was not
|
|
interrupted, or it shall return zero due to spurious wakeup.
|
|
We opt for the latter choice here. */
|
|
return 0;
|
|
else if (rv == WAIT_TIMEOUT)
|
|
return ETIMEDOUT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
pthread_cond::_fixup_after_fork ()
|
|
{
|
|
waiting = pending = 0;
|
|
mtx_cond = NULL;
|
|
|
|
/* Unlock eventually locked mutexes */
|
|
mtx_in.unlock ();
|
|
mtx_out.unlock ();
|
|
|
|
sem_wait = ::CreateSemaphore (&sec_none_nih, 0, INT32_MAX, NULL);
|
|
if (!sem_wait)
|
|
api_fatal ("pthread_cond::_fixup_after_fork () failed to recreate win32 semaphore");
|
|
}
|
|
|
|
pthread_barrierattr::pthread_barrierattr ()
|
|
: verifyable_object (PTHREAD_BARRIERATTR_MAGIC)
|
|
, shared (PTHREAD_PROCESS_PRIVATE)
|
|
{
|
|
}
|
|
|
|
pthread_barrierattr::~pthread_barrierattr ()
|
|
{
|
|
}
|
|
|
|
pthread_barrier::pthread_barrier ()
|
|
: verifyable_object (PTHREAD_BARRIER_MAGIC)
|
|
{
|
|
}
|
|
|
|
pthread_barrier::~pthread_barrier ()
|
|
{
|
|
}
|
|
|
|
pthread_rwlockattr::pthread_rwlockattr ():verifyable_object
|
|
(PTHREAD_RWLOCKATTR_MAGIC), shared (PTHREAD_PROCESS_PRIVATE)
|
|
{
|
|
}
|
|
|
|
pthread_rwlockattr::~pthread_rwlockattr ()
|
|
{
|
|
}
|
|
|
|
List<pthread_rwlock> pthread_rwlock::rwlocks;
|
|
|
|
/* This is used for rwlock creation protection within a single process only */
|
|
fast_mutex NO_COPY pthread_rwlock::rwlock_initialization_lock;
|
|
|
|
/* We can only be called once.
|
|
TODO: (no rush) use a non copied memory section to
|
|
hold an initialization flag. */
|
|
void
|
|
pthread_rwlock::init_mutex ()
|
|
{
|
|
if (!rwlock_initialization_lock.init ())
|
|
api_fatal ("Could not create win32 Mutex for pthread rwlock static initializer support.");
|
|
}
|
|
|
|
pthread_rwlock::pthread_rwlock (pthread_rwlockattr *attr) :
|
|
verifyable_object (PTHREAD_RWLOCK_MAGIC),
|
|
shared (0), waiting_readers (0), waiting_writers (0), writer (NULL),
|
|
readers (NULL), readers_mx (), mtx (NULL), cond_readers (NULL), cond_writers (NULL),
|
|
next (NULL)
|
|
{
|
|
pthread_mutex *verifyable_mutex_obj = &mtx;
|
|
pthread_cond *verifyable_cond_obj;
|
|
|
|
if (!readers_mx.init ())
|
|
{
|
|
thread_printf ("Internal rwlock synchronisation mutex is not valid. this %p", this);
|
|
magic = 0;
|
|
return;
|
|
}
|
|
|
|
if (attr)
|
|
if (attr->shared != PTHREAD_PROCESS_PRIVATE)
|
|
{
|
|
magic = 0;
|
|
return;
|
|
}
|
|
|
|
if (!pthread_mutex::is_good_object (&verifyable_mutex_obj))
|
|
{
|
|
thread_printf ("Internal rwlock mutex is not valid. this %p", this);
|
|
magic = 0;
|
|
return;
|
|
}
|
|
/* Change the mutex type to NORMAL to speed up mutex operations */
|
|
mtx.set_type (PTHREAD_MUTEX_NORMAL);
|
|
|
|
verifyable_cond_obj = &cond_readers;
|
|
if (!pthread_cond::is_good_object (&verifyable_cond_obj))
|
|
{
|
|
thread_printf ("Internal rwlock readers cond is not valid. this %p", this);
|
|
magic = 0;
|
|
return;
|
|
}
|
|
|
|
verifyable_cond_obj = &cond_writers;
|
|
if (!pthread_cond::is_good_object (&verifyable_cond_obj))
|
|
{
|
|
thread_printf ("Internal rwlock writers cond is not valid. this %p", this);
|
|
magic = 0;
|
|
return;
|
|
}
|
|
|
|
|
|
rwlocks.insert (this);
|
|
}
|
|
|
|
pthread_rwlock::~pthread_rwlock ()
|
|
{
|
|
rwlocks.remove (this);
|
|
}
|
|
|
|
int
|
|
pthread_rwlock::rdlock ()
|
|
{
|
|
int result = 0;
|
|
struct RWLOCK_READER *reader;
|
|
|
|
mtx.lock ();
|
|
|
|
reader = lookup_reader ();
|
|
if (reader)
|
|
{
|
|
if (reader->n < UINT32_MAX)
|
|
++reader->n;
|
|
else
|
|
errno = EAGAIN;
|
|
goto DONE;
|
|
}
|
|
|
|
while (writer || waiting_writers)
|
|
{
|
|
pthread_cleanup_push (pthread_rwlock::rdlock_cleanup, this);
|
|
|
|
++waiting_readers;
|
|
cond_readers.wait (&mtx);
|
|
--waiting_readers;
|
|
|
|
pthread_cleanup_pop (0);
|
|
}
|
|
|
|
if ((reader = add_reader ()))
|
|
++reader->n;
|
|
else
|
|
{
|
|
result = EAGAIN;
|
|
goto DONE;
|
|
}
|
|
|
|
DONE:
|
|
mtx.unlock ();
|
|
|
|
return result;
|
|
}
|
|
|
|
int
|
|
pthread_rwlock::tryrdlock ()
|
|
{
|
|
int result = 0;
|
|
|
|
mtx.lock ();
|
|
|
|
if (writer || waiting_writers)
|
|
result = EBUSY;
|
|
else
|
|
{
|
|
RWLOCK_READER *reader = lookup_reader ();
|
|
if (!reader)
|
|
reader = add_reader ();
|
|
if (reader && reader->n < UINT32_MAX)
|
|
++reader->n;
|
|
else
|
|
result = EAGAIN;
|
|
}
|
|
|
|
mtx.unlock ();
|
|
|
|
return result;
|
|
}
|
|
|
|
int
|
|
pthread_rwlock::wrlock ()
|
|
{
|
|
int result = 0;
|
|
pthread_t self = pthread::self ();
|
|
|
|
mtx.lock ();
|
|
|
|
if (writer == self || lookup_reader ())
|
|
{
|
|
result = EDEADLK;
|
|
goto DONE;
|
|
}
|
|
|
|
while (writer || readers)
|
|
{
|
|
pthread_cleanup_push (pthread_rwlock::wrlock_cleanup, this);
|
|
|
|
++waiting_writers;
|
|
cond_writers.wait (&mtx);
|
|
--waiting_writers;
|
|
|
|
pthread_cleanup_pop (0);
|
|
}
|
|
|
|
writer = self;
|
|
|
|
DONE:
|
|
mtx.unlock ();
|
|
|
|
return result;
|
|
}
|
|
|
|
int
|
|
pthread_rwlock::trywrlock ()
|
|
{
|
|
int result = 0;
|
|
pthread_t self = pthread::self ();
|
|
|
|
mtx.lock ();
|
|
|
|
if (writer || readers)
|
|
result = EBUSY;
|
|
else
|
|
writer = self;
|
|
|
|
mtx.unlock ();
|
|
|
|
return result;
|
|
}
|
|
|
|
int
|
|
pthread_rwlock::unlock ()
|
|
{
|
|
int result = 0;
|
|
|
|
mtx.lock ();
|
|
|
|
if (writer)
|
|
{
|
|
if (writer != pthread::self ())
|
|
{
|
|
result = EPERM;
|
|
goto DONE;
|
|
}
|
|
|
|
writer = NULL;
|
|
}
|
|
else
|
|
{
|
|
struct RWLOCK_READER *reader = lookup_reader ();
|
|
|
|
if (!reader)
|
|
{
|
|
result = EPERM;
|
|
goto DONE;
|
|
}
|
|
if (--reader->n > 0)
|
|
goto DONE;
|
|
|
|
remove_reader (reader);
|
|
delete reader;
|
|
}
|
|
|
|
release ();
|
|
|
|
DONE:
|
|
mtx.unlock ();
|
|
|
|
return result;
|
|
}
|
|
|
|
pthread_rwlock::RWLOCK_READER *
|
|
pthread_rwlock::add_reader ()
|
|
{
|
|
RWLOCK_READER *rd = new RWLOCK_READER;
|
|
if (rd)
|
|
List_insert (readers, rd);
|
|
return rd;
|
|
}
|
|
|
|
void
|
|
pthread_rwlock::remove_reader (struct RWLOCK_READER *rd)
|
|
{
|
|
List_remove (readers_mx, readers, rd);
|
|
}
|
|
|
|
struct pthread_rwlock::RWLOCK_READER *
|
|
pthread_rwlock::lookup_reader ()
|
|
{
|
|
readers_mx.lock ();
|
|
pthread_t thread = pthread::self ();
|
|
|
|
struct RWLOCK_READER *cur = readers;
|
|
|
|
while (cur && cur->thread != thread)
|
|
cur = cur->next;
|
|
|
|
readers_mx.unlock ();
|
|
|
|
return cur;
|
|
}
|
|
|
|
void
|
|
pthread_rwlock::rdlock_cleanup (void *arg)
|
|
{
|
|
pthread_rwlock *rwlock = (pthread_rwlock *) arg;
|
|
|
|
--(rwlock->waiting_readers);
|
|
rwlock->release ();
|
|
rwlock->mtx.unlock ();
|
|
}
|
|
|
|
void
|
|
pthread_rwlock::wrlock_cleanup (void *arg)
|
|
{
|
|
pthread_rwlock *rwlock = (pthread_rwlock *) arg;
|
|
|
|
--(rwlock->waiting_writers);
|
|
rwlock->release ();
|
|
rwlock->mtx.unlock ();
|
|
}
|
|
|
|
void
|
|
pthread_rwlock::_fixup_after_fork ()
|
|
{
|
|
pthread_t self = pthread::self ();
|
|
struct RWLOCK_READER **temp = &readers;
|
|
|
|
waiting_readers = 0;
|
|
waiting_writers = 0;
|
|
|
|
if (!readers_mx.init ())
|
|
api_fatal ("pthread_rwlock::_fixup_after_fork () failed to recreate mutex");
|
|
|
|
/* Unlock eventually locked mutex */
|
|
mtx.unlock ();
|
|
/*
|
|
* Remove all readers except self
|
|
*/
|
|
while (*temp)
|
|
{
|
|
if ((*temp)->thread == self)
|
|
temp = &((*temp)->next);
|
|
else
|
|
{
|
|
struct RWLOCK_READER *cur = *temp;
|
|
*temp = (*temp)->next;
|
|
delete cur;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* pthread_key */
|
|
/* static members */
|
|
/* This stores pthread_key information across fork() boundaries */
|
|
List<pthread_key> pthread_key::keys;
|
|
|
|
/* non-static members */
|
|
|
|
pthread_key::pthread_key (void (*aDestructor) (void *)):verifyable_object (PTHREAD_KEY_MAGIC), destructor (aDestructor)
|
|
{
|
|
tls_index = TlsAlloc ();
|
|
if (tls_index == TLS_OUT_OF_INDEXES)
|
|
magic = 0;
|
|
else
|
|
keys.insert (this);
|
|
}
|
|
|
|
pthread_key::~pthread_key ()
|
|
{
|
|
/* We may need to make the list code lock the list during operations
|
|
*/
|
|
if (magic != 0)
|
|
{
|
|
keys.remove (this);
|
|
TlsFree (tls_index);
|
|
}
|
|
}
|
|
|
|
void
|
|
pthread_key::_fixup_before_fork ()
|
|
{
|
|
fork_buf = get ();
|
|
}
|
|
|
|
void
|
|
pthread_key::_fixup_after_fork ()
|
|
{
|
|
tls_index = TlsAlloc ();
|
|
if (tls_index == TLS_OUT_OF_INDEXES)
|
|
api_fatal ("pthread_key::recreate_key_from_buffer () failed to reallocate Tls storage");
|
|
set (fork_buf);
|
|
}
|
|
|
|
void
|
|
pthread_key::run_destructor ()
|
|
{
|
|
if (destructor)
|
|
{
|
|
void *oldValue = get ();
|
|
if (oldValue)
|
|
{
|
|
set (NULL);
|
|
destructor (oldValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* pshared mutexs */
|
|
|
|
/* static members */
|
|
|
|
List<pthread_mutex> pthread_mutex::mutexes;
|
|
|
|
/* This is used for mutex creation protection within a single process only */
|
|
fast_mutex NO_COPY pthread_mutex::mutex_initialization_lock;
|
|
|
|
void
|
|
pthread_mutex::init_mutex ()
|
|
{
|
|
if (!mutex_initialization_lock.init ())
|
|
api_fatal ("Could not create win32 Mutex for pthread mutex static initializer support.");
|
|
}
|
|
|
|
pthread_mutex::pthread_mutex (pthread_mutexattr *attr) :
|
|
verifyable_object (0), /* set magic to zero initially */
|
|
lock_counter (0),
|
|
win32_obj_id (NULL), owner (_new_mutex),
|
|
#ifdef DEBUGGING
|
|
tid (0),
|
|
#endif
|
|
recursion_counter (0), condwaits (0),
|
|
type (PTHREAD_MUTEX_NORMAL),
|
|
pshared (PTHREAD_PROCESS_PRIVATE)
|
|
{
|
|
win32_obj_id = ::CreateEvent (&sec_none_nih, false, false, NULL);
|
|
if (!win32_obj_id)
|
|
return;
|
|
/*attr checked in the C call */
|
|
if (!attr)
|
|
/* handled in the caller */;
|
|
else if (attr->pshared != PTHREAD_PROCESS_SHARED)
|
|
type = attr->mutextype;
|
|
else
|
|
return; /* Not implemented */
|
|
|
|
magic = PTHREAD_MUTEX_MAGIC;
|
|
mutexes.insert (this);
|
|
}
|
|
|
|
pthread_mutex::~pthread_mutex ()
|
|
{
|
|
if (win32_obj_id)
|
|
{
|
|
CloseHandle (win32_obj_id);
|
|
win32_obj_id = NULL;
|
|
}
|
|
|
|
mutexes.remove (this);
|
|
owner = _destroyed_mutex;
|
|
magic = 0;
|
|
}
|
|
|
|
int
|
|
pthread_mutex::lock ()
|
|
{
|
|
pthread_t self = ::pthread_self ();
|
|
int result = 0;
|
|
|
|
if (InterlockedIncrement (&lock_counter) == 1)
|
|
set_owner (self);
|
|
else if (type == PTHREAD_MUTEX_NORMAL /* potentially causes deadlock */
|
|
|| !pthread::equal (owner, self))
|
|
{
|
|
cygwait (win32_obj_id, cw_infinite, cw_sig | cw_sig_restart);
|
|
set_owner (self);
|
|
}
|
|
else
|
|
{
|
|
InterlockedDecrement (&lock_counter);
|
|
if (type == PTHREAD_MUTEX_RECURSIVE)
|
|
result = lock_recursive ();
|
|
else
|
|
result = EDEADLK;
|
|
}
|
|
|
|
pthread_printf ("mutex %p, self %p, owner %p, lock_counter %d, recursion_counter %u",
|
|
this, self, owner, lock_counter, recursion_counter);
|
|
return result;
|
|
}
|
|
|
|
int
|
|
pthread_mutex::unlock ()
|
|
{
|
|
int res = 0;
|
|
pthread_t self = ::pthread_self ();
|
|
if (type == PTHREAD_MUTEX_NORMAL)
|
|
/* no error checking */;
|
|
else if (no_owner ())
|
|
res = type == PTHREAD_MUTEX_ERRORCHECK ? EPERM : 0;
|
|
else if (!pthread::equal (owner, self))
|
|
res = EPERM;
|
|
if (!res && recursion_counter > 0 && --recursion_counter == 0)
|
|
/* Don't try to unlock anything if recursion_counter == 0.
|
|
This means the mutex was never locked or that we've forked. */
|
|
{
|
|
owner = (pthread_t) _unlocked_mutex;
|
|
#ifdef DEBUGGING
|
|
tid = 0; // thread-id
|
|
#endif
|
|
if (InterlockedDecrement (&lock_counter))
|
|
::SetEvent (win32_obj_id); // Another thread is waiting
|
|
res = 0;
|
|
}
|
|
|
|
pthread_printf ("mutex %p, owner %p, self %p, lock_counter %d, recursion_counter %u, type %d, res %d",
|
|
this, owner, self, lock_counter, recursion_counter, type, res);
|
|
return res;
|
|
}
|
|
|
|
int
|
|
pthread_mutex::trylock ()
|
|
{
|
|
pthread_t self = ::pthread_self ();
|
|
int result = 0;
|
|
|
|
if (InterlockedCompareExchange (&lock_counter, 1, 0) == 0)
|
|
set_owner (self);
|
|
else if (type == PTHREAD_MUTEX_RECURSIVE && pthread::equal (owner, self))
|
|
result = lock_recursive ();
|
|
else
|
|
result = EBUSY;
|
|
|
|
return result;
|
|
}
|
|
|
|
int
|
|
pthread_mutex::destroy ()
|
|
{
|
|
if (condwaits || trylock ())
|
|
// Do not destroy a condwaited or locked mutex
|
|
return EBUSY;
|
|
else if (recursion_counter > 1)
|
|
{
|
|
// Do not destroy a recursive locked mutex
|
|
recursion_counter--;
|
|
return EBUSY;
|
|
}
|
|
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
pthread_mutex::_fixup_after_fork ()
|
|
{
|
|
pthread_printf ("mutex %p", this);
|
|
if (pshared != PTHREAD_PROCESS_PRIVATE)
|
|
api_fatal ("pthread_mutex::_fixup_after_fork () doesn't understand PROCESS_SHARED mutex's");
|
|
|
|
/* All waiting threads are gone after a fork */
|
|
recursion_counter = 0;
|
|
lock_counter = 0;
|
|
condwaits = 0;
|
|
#ifdef DEBUGGING
|
|
tid = 0xffffffff; /* Don't know the tid after a fork */
|
|
#endif
|
|
win32_obj_id = ::CreateEvent (&sec_none_nih, false, false, NULL);
|
|
if (!win32_obj_id)
|
|
api_fatal ("pthread_mutex::_fixup_after_fork () failed to recreate win32 event for mutex");
|
|
}
|
|
|
|
pthread_mutexattr::pthread_mutexattr ():verifyable_object (PTHREAD_MUTEXATTR_MAGIC),
|
|
pshared (PTHREAD_PROCESS_PRIVATE), mutextype (PTHREAD_MUTEX_NORMAL)
|
|
{
|
|
}
|
|
|
|
pthread_mutexattr::~pthread_mutexattr ()
|
|
{
|
|
}
|
|
|
|
/* pshared spinlocks
|
|
|
|
The infrastructure is provided by the underlying pthread_mutex class.
|
|
The rest is a simplification implementing spin locking. */
|
|
|
|
pthread_spinlock::pthread_spinlock (int pshared) :
|
|
pthread_mutex (NULL)
|
|
{
|
|
magic = PTHREAD_SPINLOCK_MAGIC;
|
|
set_type (PTHREAD_MUTEX_NORMAL);
|
|
set_shared (pshared);
|
|
}
|
|
|
|
int
|
|
pthread_spinlock::lock ()
|
|
{
|
|
pthread_t self = ::pthread_self ();
|
|
int result = -1;
|
|
unsigned spins = 0;
|
|
|
|
/*
|
|
We want to spin using 'pause' instruction on multi-core system but we
|
|
want to avoid this on single-core systems.
|
|
|
|
The limit of 1000 spins is semi-arbitrary. Microsoft suggests (in their
|
|
InitializeCriticalSectionAndSpinCount documentation on MSDN) they are
|
|
using spin count limit 4000 for their heap manager critical
|
|
sections. Other source suggest spin count as small as 200 for fast path
|
|
of mutex locking.
|
|
*/
|
|
unsigned const FAST_SPINS_LIMIT = wincap.cpu_count () != 1 ? 1000 : 0;
|
|
|
|
do
|
|
{
|
|
if (InterlockedExchange (&lock_counter, 1) == 0)
|
|
{
|
|
set_owner (self);
|
|
result = 0;
|
|
}
|
|
else if (unlikely(pthread::equal (owner, self)))
|
|
result = EDEADLK;
|
|
else if (spins < FAST_SPINS_LIMIT)
|
|
{
|
|
++spins;
|
|
__asm__ volatile ("pause":::);
|
|
}
|
|
else
|
|
{
|
|
/* Minimal timeout to minimize CPU usage while still spinning. */
|
|
LARGE_INTEGER timeout;
|
|
timeout.QuadPart = -10000LL;
|
|
/* FIXME: no cancel? */
|
|
cygwait (win32_obj_id, &timeout, cw_sig);
|
|
}
|
|
}
|
|
while (result == -1);
|
|
pthread_printf ("spinlock %p, self %p, owner %p", this, self, owner);
|
|
return result;
|
|
}
|
|
|
|
int
|
|
pthread_spinlock::unlock ()
|
|
{
|
|
pthread_t self = ::pthread_self ();
|
|
int result = 0;
|
|
|
|
if (!pthread::equal (owner, self))
|
|
result = EPERM;
|
|
else
|
|
{
|
|
owner = (pthread_t) _unlocked_mutex;
|
|
#ifdef DEBUGGING
|
|
tid = 0; // thread-id
|
|
#endif
|
|
InterlockedExchange (&lock_counter, 0);
|
|
::SetEvent (win32_obj_id);
|
|
result = 0;
|
|
}
|
|
pthread_printf ("spinlock %p, owner %p, self %p, res %d",
|
|
this, owner, self, result);
|
|
return result;
|
|
}
|
|
|
|
DWORD WINAPI
|
|
pthread::thread_init_wrapper (void *arg)
|
|
{
|
|
pthread *thread = (pthread *) arg;
|
|
/* This *must* be set prior to calling set_tls_self_pointer or there is
|
|
a race with the signal processing code which may miss the signal mask
|
|
settings. */
|
|
_my_tls.sigmask = thread->parent_sigmask;
|
|
thread->set_tls_self_pointer ();
|
|
|
|
thread->mutex.lock ();
|
|
|
|
// if thread is detached force cleanup on exit
|
|
if (thread->attr.joinable == PTHREAD_CREATE_DETACHED && thread->joiner == NULL)
|
|
thread->joiner = thread;
|
|
thread->mutex.unlock ();
|
|
|
|
debug_printf ("tid %p", &_my_tls);
|
|
thread_printf ("started thread %p %p %p %p %p %p", arg, &_my_tls.local_clib,
|
|
_impure_ptr, thread, thread->function, thread->arg);
|
|
|
|
// call the user's thread
|
|
void *ret = thread->function (thread->arg);
|
|
|
|
thread->exit (ret);
|
|
|
|
return 0; // just for show. Never returns.
|
|
}
|
|
|
|
unsigned long
|
|
pthread::getsequence_np ()
|
|
{
|
|
return get_thread_id ();
|
|
}
|
|
|
|
int
|
|
pthread::create (pthread_t *thread, const pthread_attr_t *attr,
|
|
void *(*start_routine) (void *), void *arg)
|
|
{
|
|
if (attr && !pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
|
|
*thread = new pthread ();
|
|
if (!(*thread)->create (start_routine, attr ? *attr : NULL, arg))
|
|
{
|
|
delete (*thread);
|
|
*thread = NULL;
|
|
return EAGAIN;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
pthread::once (pthread_once_t *once_control, void (*init_routine) (void))
|
|
{
|
|
// already done ?
|
|
if (once_control->state)
|
|
return 0;
|
|
|
|
pthread_mutex_lock (&once_control->mutex);
|
|
/* Here we must set a cancellation handler to unlock the mutex if needed */
|
|
/* but a cancellation handler is not the right thing. We need this in the thread
|
|
*cleanup routine. Assumption: a thread can only be in one pthread_once routine
|
|
*at a time. Stote a mutex_t *in the pthread_structure. if that's non null unlock
|
|
*on pthread_exit ();
|
|
*/
|
|
if (!once_control->state)
|
|
{
|
|
init_routine ();
|
|
once_control->state = 1;
|
|
}
|
|
/* Here we must remove our cancellation handler */
|
|
pthread_mutex_unlock (&once_control->mutex);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
pthread::cancel (pthread_t thread)
|
|
{
|
|
if (!is_good_object (&thread))
|
|
return ESRCH;
|
|
|
|
return thread->cancel ();
|
|
}
|
|
|
|
void
|
|
pthread::atforkprepare ()
|
|
{
|
|
callback *cb = MT_INTERFACE->pthread_prepare;
|
|
while (cb)
|
|
{
|
|
cb->cb ();
|
|
cb = cb->next;
|
|
}
|
|
|
|
__fp_lock_all ();
|
|
|
|
MT_INTERFACE->fixup_before_fork ();
|
|
}
|
|
|
|
void
|
|
pthread::atforkparent ()
|
|
{
|
|
__fp_unlock_all ();
|
|
|
|
callback *cb = MT_INTERFACE->pthread_parent;
|
|
while (cb)
|
|
{
|
|
cb->cb ();
|
|
cb = cb->next;
|
|
}
|
|
}
|
|
|
|
void
|
|
pthread::atforkchild ()
|
|
{
|
|
MT_INTERFACE->fixup_after_fork ();
|
|
|
|
__fp_unlock_all ();
|
|
|
|
callback *cb = MT_INTERFACE->pthread_child;
|
|
while (cb)
|
|
{
|
|
cb->cb ();
|
|
cb = cb->next;
|
|
}
|
|
}
|
|
|
|
/* Register a set of functions to run before and after fork.
|
|
prepare calls are called in LI-FC order.
|
|
parent and child calls are called in FI-FC order. */
|
|
int
|
|
pthread::atfork (void (*prepare)(void), void (*parent)(void), void (*child)(void))
|
|
{
|
|
callback *prepcb = NULL, *parentcb = NULL, *childcb = NULL;
|
|
if (prepare)
|
|
{
|
|
prepcb = new callback;
|
|
if (!prepcb)
|
|
return ENOMEM;
|
|
}
|
|
if (parent)
|
|
{
|
|
parentcb = new callback;
|
|
if (!parentcb)
|
|
{
|
|
if (prepcb)
|
|
delete prepcb;
|
|
return ENOMEM;
|
|
}
|
|
}
|
|
if (child)
|
|
{
|
|
childcb = new callback;
|
|
if (!childcb)
|
|
{
|
|
if (prepcb)
|
|
delete prepcb;
|
|
if (parentcb)
|
|
delete parentcb;
|
|
return ENOMEM;
|
|
}
|
|
}
|
|
|
|
if (prepcb)
|
|
{
|
|
prepcb->cb = prepare;
|
|
List_insert (MT_INTERFACE->pthread_prepare, prepcb);
|
|
}
|
|
if (parentcb)
|
|
{
|
|
parentcb->cb = parent;
|
|
callback **t = &MT_INTERFACE->pthread_parent;
|
|
while (*t)
|
|
t = &(*t)->next;
|
|
/* t = pointer to last next in the list */
|
|
List_insert (*t, parentcb);
|
|
}
|
|
if (childcb)
|
|
{
|
|
childcb->cb = child;
|
|
callback **t = &MT_INTERFACE->pthread_child;
|
|
while (*t)
|
|
t = &(*t)->next;
|
|
/* t = pointer to last next in the list */
|
|
List_insert (*t, childcb);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_attr_init (pthread_attr_t *attr)
|
|
{
|
|
*attr = new pthread_attr;
|
|
if (!pthread_attr::is_good_object (attr))
|
|
{
|
|
delete (*attr);
|
|
*attr = NULL;
|
|
return ENOMEM;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_attr_getinheritsched (const pthread_attr_t *attr,
|
|
int *inheritsched)
|
|
{
|
|
if (!pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
*inheritsched = (*attr)->inheritsched;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_attr_getschedparam (const pthread_attr_t *attr,
|
|
struct sched_param *param)
|
|
{
|
|
if (!pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
*param = (*attr)->schedparam;
|
|
return 0;
|
|
}
|
|
|
|
/* From a pure code point of view, this should call a helper in sched.cc,
|
|
to allow for someone adding scheduler policy changes to win32 in the future.
|
|
However that's extremely unlikely, so short and sweet will do us */
|
|
extern "C" int
|
|
pthread_attr_getschedpolicy (const pthread_attr_t *attr, int *policy)
|
|
{
|
|
if (!pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
*policy = SCHED_FIFO;
|
|
return 0;
|
|
}
|
|
|
|
|
|
extern "C" int
|
|
pthread_attr_getscope (const pthread_attr_t *attr, int *contentionscope)
|
|
{
|
|
if (!pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
*contentionscope = (*attr)->contentionscope;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_attr_setdetachstate (pthread_attr_t *attr, int detachstate)
|
|
{
|
|
if (!pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
if (detachstate < 0 || detachstate > 1)
|
|
return EINVAL;
|
|
(*attr)->joinable = detachstate;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_attr_getdetachstate (const pthread_attr_t *attr, int *detachstate)
|
|
{
|
|
if (!pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
*detachstate = (*attr)->joinable;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_attr_setinheritsched (pthread_attr_t *attr, int inheritsched)
|
|
{
|
|
if (!pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
if (inheritsched != PTHREAD_INHERIT_SCHED
|
|
&& inheritsched != PTHREAD_EXPLICIT_SCHED)
|
|
return ENOTSUP;
|
|
(*attr)->inheritsched = inheritsched;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_attr_setschedparam (pthread_attr_t *attr,
|
|
const struct sched_param *param)
|
|
{
|
|
if (!pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
if (!valid_sched_parameters (param))
|
|
return ENOTSUP;
|
|
(*attr)->schedparam = *param;
|
|
return 0;
|
|
}
|
|
|
|
/* See __pthread_attr_getschedpolicy for some notes */
|
|
extern "C" int
|
|
pthread_attr_setschedpolicy (pthread_attr_t *attr, int policy)
|
|
{
|
|
if (!pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
if (policy != SCHED_FIFO)
|
|
return ENOTSUP;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_attr_setscope (pthread_attr_t *attr, int contentionscope)
|
|
{
|
|
if (!pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
if (contentionscope != PTHREAD_SCOPE_SYSTEM
|
|
&& contentionscope != PTHREAD_SCOPE_PROCESS)
|
|
return EINVAL;
|
|
/* In future, we may be able to support system scope by escalating the thread
|
|
priority to exceed the priority class. For now we only support PROCESS scope. */
|
|
if (contentionscope != PTHREAD_SCOPE_PROCESS)
|
|
return ENOTSUP;
|
|
(*attr)->contentionscope = contentionscope;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_attr_setstack (pthread_attr_t *attr, void *addr, size_t size)
|
|
{
|
|
if (!pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
if (addr == NULL)
|
|
return EINVAL;
|
|
if (size < PTHREAD_STACK_MIN)
|
|
return EINVAL;
|
|
/* The incoming address addr points to the lowest addressable byte of a
|
|
buffer of size bytes. Due to the way pthread_attr_setstackaddr is defined
|
|
on Linux, the lowest address ot the stack can't be reliably computed when
|
|
using pthread_attr_setstackaddr/pthread_attr_setstacksize. Therefore we
|
|
store the uppermost address of the stack in stackaddr. See also the
|
|
comment in pthread_attr_setstackaddr. */
|
|
(*attr)->stackaddr = (caddr_t) addr + size;
|
|
(*attr)->stacksize = size;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_attr_getstack (const pthread_attr_t *attr, void **addr, size_t *size)
|
|
{
|
|
if (!pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
/* stackaddr holds the uppermost stack address. See the comment in
|
|
pthread_attr_setstack. */
|
|
*addr = (caddr_t) (*attr)->stackaddr - (*attr)->stacksize;
|
|
*size = (*attr)->stacksize;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_attr_setstackaddr (pthread_attr_t *attr, void *addr)
|
|
{
|
|
if (!pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
if (addr == NULL)
|
|
return EINVAL;
|
|
/* This function is deprecated in SUSv4, but SUSv3 didn't define
|
|
if the incoming stack address is the lowest address of the memory
|
|
area defined as stack, or if it's the start address of the stack
|
|
at which it begins its growth. On Linux it's the latter which
|
|
means the uppermost stack address on x86 based systems. See comment
|
|
in pthread_attr_setstack as well. */
|
|
(*attr)->stackaddr = addr;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_attr_getstackaddr (const pthread_attr_t *attr, void **addr)
|
|
{
|
|
if (!pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
/* See comment in pthread_attr_setstackaddr. */
|
|
*addr = (*attr)->stackaddr;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_attr_setstacksize (pthread_attr_t *attr, size_t size)
|
|
{
|
|
if (!pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
if (size < PTHREAD_STACK_MIN)
|
|
return EINVAL;
|
|
(*attr)->stacksize = size;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_attr_getstacksize (const pthread_attr_t *attr, size_t *size)
|
|
{
|
|
if (!pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
/* If the stacksize has not been set by the application, return the
|
|
default stacksize. Note that this is different from what
|
|
pthread_attr_getstack returns. */
|
|
*size = (*attr)->stacksize ?: get_rlimit_stack ();
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_attr_setguardsize (pthread_attr_t *attr, size_t size)
|
|
{
|
|
if (!pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
/* We don't support a guardsize of more than 1 Meg. */
|
|
if (size > 1024 * 1024)
|
|
return EINVAL;
|
|
(*attr)->guardsize = size;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_attr_getguardsize (const pthread_attr_t *attr, size_t *size)
|
|
{
|
|
if (!pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
*size = (*attr)->guardsize;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_attr_destroy (pthread_attr_t *attr)
|
|
{
|
|
if (!pthread_attr::is_good_object (attr))
|
|
return EINVAL;
|
|
delete (*attr);
|
|
*attr = NULL;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
pthread::join (pthread_t *thread, void **return_val)
|
|
{
|
|
pthread_t joiner = self ();
|
|
|
|
joiner->testcancel ();
|
|
|
|
// Initialize return val with NULL
|
|
if (return_val)
|
|
*return_val = NULL;
|
|
|
|
if (!is_good_object (&joiner))
|
|
return EINVAL;
|
|
|
|
if (!is_good_object (thread))
|
|
return ESRCH;
|
|
|
|
if (equal (*thread,joiner))
|
|
return EDEADLK;
|
|
|
|
(*thread)->mutex.lock ();
|
|
|
|
if ((*thread)->attr.joinable == PTHREAD_CREATE_DETACHED)
|
|
{
|
|
(*thread)->mutex.unlock ();
|
|
return EINVAL;
|
|
}
|
|
else
|
|
{
|
|
(*thread)->joiner = joiner;
|
|
(*thread)->attr.joinable = PTHREAD_CREATE_DETACHED;
|
|
(*thread)->mutex.unlock ();
|
|
|
|
switch (cygwait ((*thread)->win32_obj_id, cw_infinite,
|
|
cw_sig | cw_sig_restart | cw_cancel))
|
|
{
|
|
case WAIT_OBJECT_0:
|
|
if (return_val)
|
|
*return_val = (*thread)->return_ptr;
|
|
delete (*thread);
|
|
break;
|
|
case WAIT_CANCELED:
|
|
// set joined thread back to joinable since we got canceled
|
|
(*thread)->joiner = NULL;
|
|
(*thread)->attr.joinable = PTHREAD_CREATE_JOINABLE;
|
|
joiner->cancel_self ();
|
|
// never reached
|
|
break;
|
|
default:
|
|
// should never happen
|
|
return EINVAL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
pthread::detach (pthread_t *thread)
|
|
{
|
|
if (!is_good_object (thread))
|
|
return ESRCH;
|
|
|
|
(*thread)->mutex.lock ();
|
|
if ((*thread)->attr.joinable == PTHREAD_CREATE_DETACHED)
|
|
{
|
|
(*thread)->mutex.unlock ();
|
|
return EINVAL;
|
|
}
|
|
|
|
// check if thread is still alive
|
|
if ((*thread)->valid && WaitForSingleObject ((*thread)->win32_obj_id, 0) == WAIT_TIMEOUT)
|
|
{
|
|
// force cleanup on exit
|
|
(*thread)->joiner = *thread;
|
|
(*thread)->attr.joinable = PTHREAD_CREATE_DETACHED;
|
|
(*thread)->mutex.unlock ();
|
|
}
|
|
else
|
|
{
|
|
// thread has already terminated.
|
|
(*thread)->mutex.unlock ();
|
|
delete (*thread);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
pthread::suspend (pthread_t *thread)
|
|
{
|
|
if (!is_good_object (thread))
|
|
return ESRCH;
|
|
|
|
if ((*thread)->suspended == false)
|
|
{
|
|
(*thread)->suspended = true;
|
|
SuspendThread ((*thread)->win32_obj_id);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
pthread::resume (pthread_t *thread)
|
|
{
|
|
if (!is_good_object (thread))
|
|
return ESRCH;
|
|
|
|
if ((*thread)->suspended == true)
|
|
ResumeThread ((*thread)->win32_obj_id);
|
|
(*thread)->suspended = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_getattr_np (pthread_t thread, pthread_attr_t *attr)
|
|
{
|
|
THREAD_BASIC_INFORMATION tbi;
|
|
NTSTATUS status;
|
|
|
|
if (!pthread::is_good_object (&thread))
|
|
return ESRCH;
|
|
|
|
/* attr may not be pre-initialized */
|
|
if (!pthread_attr::is_good_object (attr))
|
|
{
|
|
int rv = pthread_attr_init (attr);
|
|
if (rv != 0)
|
|
return rv;
|
|
}
|
|
|
|
(*attr)->joinable = thread->attr.joinable;
|
|
(*attr)->contentionscope = thread->attr.contentionscope;
|
|
(*attr)->inheritsched = thread->attr.inheritsched;
|
|
(*attr)->schedparam = thread->attr.schedparam;
|
|
(*attr)->guardsize = thread->attr.guardsize;
|
|
|
|
status = NtQueryInformationThread (thread->win32_obj_id,
|
|
ThreadBasicInformation,
|
|
&tbi, sizeof (tbi), NULL);
|
|
if (NT_SUCCESS (status))
|
|
{
|
|
PTEB teb = (PTEB) tbi.TebBaseAddress;
|
|
/* stackaddr holds the uppermost stack address. See the comments
|
|
in pthread_attr_setstack and pthread_attr_setstackaddr for a
|
|
description. */
|
|
(*attr)->stackaddr = teb->Tib.StackBase;
|
|
(*attr)->stacksize = (uintptr_t) teb->Tib.StackBase
|
|
- (uintptr_t) (teb->DeallocationStack ?: teb->Tib.StackLimit);
|
|
}
|
|
else
|
|
{
|
|
debug_printf ("NtQueryInformationThread(ThreadBasicInformation), "
|
|
"status %y", status);
|
|
(*attr)->stackaddr = thread->attr.stackaddr;
|
|
(*attr)->stacksize = thread->attr.stacksize;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* For Linux compatibility, the length of a thread name is 16 characters. */
|
|
#define THRNAMELEN 16
|
|
|
|
extern "C" int
|
|
pthread_getname_np (pthread_t thread, char *buf, size_t buflen)
|
|
{
|
|
char *name;
|
|
|
|
if (!pthread::is_good_object (&thread))
|
|
return ESRCH;
|
|
|
|
if (!thread->attr.name)
|
|
name = program_invocation_short_name;
|
|
else
|
|
name = thread->attr.name;
|
|
|
|
/* Return ERANGE if the provided buffer is less than THRNAMELEN. Truncate
|
|
and zero-terminate the name to fit in buf. This means we always return
|
|
something if the buffer is THRNAMELEN or larger, but there is no way to
|
|
tell if we have the whole name. */
|
|
if (buflen < THRNAMELEN)
|
|
return ERANGE;
|
|
|
|
int ret = 0;
|
|
__try
|
|
{
|
|
strlcpy (buf, name, buflen);
|
|
}
|
|
__except (NO_ERROR)
|
|
{
|
|
ret = EFAULT;
|
|
}
|
|
__endtry
|
|
|
|
return ret;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_setname_np (pthread_t thread, const char *name)
|
|
{
|
|
char *oldname, *cp;
|
|
|
|
if (!pthread::is_good_object (&thread))
|
|
return ESRCH;
|
|
|
|
if (strlen (name) > THRNAMELEN)
|
|
return ERANGE;
|
|
|
|
cp = strdup (name);
|
|
if (!cp)
|
|
return ENOMEM;
|
|
|
|
oldname = thread->attr.name;
|
|
thread->attr.name = cp;
|
|
|
|
if (oldname)
|
|
free (oldname);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#undef THRNAMELEN
|
|
|
|
/* provided for source level compatability.
|
|
See http://www.opengroup.org/onlinepubs/007908799/xsh/pthread_getconcurrency.html
|
|
*/
|
|
extern "C" int
|
|
pthread_getconcurrency ()
|
|
{
|
|
return MT_INTERFACE->concurrency;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_getcpuclockid (pthread_t thread, clockid_t *clk_id)
|
|
{
|
|
if (!pthread::is_good_object (&thread))
|
|
return (ESRCH);
|
|
*clk_id = (clockid_t) THREADID_TO_CLOCKID (thread->getsequence_np ());
|
|
return 0;
|
|
}
|
|
|
|
/* keep this in sync with sched.cc */
|
|
extern "C" int
|
|
pthread_getschedparam (pthread_t thread, int *policy,
|
|
struct sched_param *param)
|
|
{
|
|
if (!pthread::is_good_object (&thread))
|
|
return ESRCH;
|
|
*policy = SCHED_FIFO;
|
|
param->sched_priority = sched_get_thread_priority (thread->win32_obj_id);
|
|
return 0;
|
|
}
|
|
|
|
/* Thread Specific Data */
|
|
extern "C" int
|
|
pthread_key_create (pthread_key_t *key, void (*destructor) (void *))
|
|
{
|
|
*key = new pthread_key (destructor);
|
|
|
|
if (!pthread_key::is_good_object (key))
|
|
{
|
|
delete (*key);
|
|
*key = NULL;
|
|
return EAGAIN;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_key_delete (pthread_key_t key)
|
|
{
|
|
if (!pthread_key::is_good_object (&key))
|
|
return EINVAL;
|
|
|
|
delete (key);
|
|
return 0;
|
|
}
|
|
|
|
/* provided for source level compatability. See
|
|
http://www.opengroup.org/onlinepubs/007908799/xsh/pthread_getconcurrency.html
|
|
*/
|
|
extern "C" int
|
|
pthread_setconcurrency (int new_level)
|
|
{
|
|
if (new_level < 0)
|
|
return EINVAL;
|
|
MT_INTERFACE->concurrency = new_level;
|
|
return 0;
|
|
}
|
|
|
|
/* keep syncronised with sched.cc */
|
|
extern "C" int
|
|
pthread_setschedparam (pthread_t thread, int policy,
|
|
const struct sched_param *param)
|
|
{
|
|
if (!pthread::is_good_object (&thread))
|
|
return ESRCH;
|
|
if (policy != SCHED_FIFO)
|
|
return ENOTSUP;
|
|
if (!param)
|
|
return EINVAL;
|
|
int rv =
|
|
sched_set_thread_priority (thread->win32_obj_id, param->sched_priority);
|
|
if (!rv)
|
|
thread->attr.schedparam.sched_priority = param->sched_priority;
|
|
return rv;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_setschedprio (pthread_t thread, int priority)
|
|
{
|
|
if (!pthread::is_good_object (&thread))
|
|
return ESRCH;
|
|
int rv =
|
|
sched_set_thread_priority (thread->win32_obj_id, priority);
|
|
if (!rv)
|
|
thread->attr.schedparam.sched_priority = priority;
|
|
return rv;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_setspecific (pthread_key_t key, const void *value)
|
|
{
|
|
if (!pthread_key::is_good_object (&key))
|
|
return EINVAL;
|
|
(key)->set (value);
|
|
return 0;
|
|
}
|
|
|
|
extern "C" void *
|
|
pthread_getspecific (pthread_key_t key)
|
|
{
|
|
if (!pthread_key::is_good_object (&key))
|
|
return NULL;
|
|
|
|
return (key)->get ();
|
|
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_cond_destroy (pthread_cond_t *cond)
|
|
{
|
|
if (pthread_cond::is_initializer (cond))
|
|
return 0;
|
|
if (!pthread_cond::is_good_object (cond))
|
|
return EINVAL;
|
|
|
|
/* reads are atomic */
|
|
if ((*cond)->waiting)
|
|
return EBUSY;
|
|
|
|
delete (*cond);
|
|
*cond = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
pthread_cond::init (pthread_cond_t *cond, const pthread_condattr_t *attr)
|
|
{
|
|
pthread_cond_t new_cond;
|
|
|
|
if (attr && !pthread_condattr::is_good_object (attr))
|
|
return EINVAL;
|
|
|
|
cond_initialization_lock.lock ();
|
|
|
|
new_cond = new pthread_cond (attr ? (*attr) : NULL);
|
|
if (!is_good_object (&new_cond))
|
|
{
|
|
delete new_cond;
|
|
cond_initialization_lock.unlock ();
|
|
return EAGAIN;
|
|
}
|
|
|
|
int ret = 0;
|
|
|
|
__try
|
|
{
|
|
*cond = new_cond;
|
|
}
|
|
__except (NO_ERROR)
|
|
{
|
|
delete new_cond;
|
|
ret = EINVAL;
|
|
}
|
|
__endtry
|
|
cond_initialization_lock.unlock ();
|
|
return ret;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_cond_broadcast (pthread_cond_t *cond)
|
|
{
|
|
if (pthread_cond::is_initializer (cond))
|
|
return 0;
|
|
if (!pthread_cond::is_good_object (cond))
|
|
return EINVAL;
|
|
|
|
(*cond)->unblock (true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_cond_signal (pthread_cond_t *cond)
|
|
{
|
|
if (pthread_cond::is_initializer (cond))
|
|
return 0;
|
|
if (!pthread_cond::is_good_object (cond))
|
|
return EINVAL;
|
|
|
|
(*cond)->unblock (false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
__pthread_cond_wait_init (pthread_cond_t *cond, pthread_mutex_t *mutex)
|
|
{
|
|
if (!pthread_mutex::is_good_object (mutex))
|
|
return EINVAL;
|
|
if (!(*mutex)->can_be_unlocked ())
|
|
return EPERM;
|
|
|
|
if (pthread_cond::is_initializer (cond))
|
|
pthread_cond::init (cond, NULL);
|
|
if (!pthread_cond::is_good_object (cond))
|
|
return EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_cond_timedwait (pthread_cond_t *cond, pthread_mutex_t *mutex,
|
|
const struct timespec *abstime)
|
|
{
|
|
struct timespec tp;
|
|
LARGE_INTEGER timeout;
|
|
|
|
pthread_testcancel ();
|
|
|
|
__try
|
|
{
|
|
int err = __pthread_cond_wait_init (cond, mutex);
|
|
if (err)
|
|
return err;
|
|
|
|
/* According to SUSv3, the abstime value must be checked for validity. */
|
|
if (abstime->tv_sec < 0
|
|
|| abstime->tv_nsec < 0
|
|
|| abstime->tv_nsec > 999999999)
|
|
__leave;
|
|
|
|
clock_gettime ((*cond)->clock_id, &tp);
|
|
|
|
/* Check for immediate timeout before converting */
|
|
if (tp.tv_sec > abstime->tv_sec
|
|
|| (tp.tv_sec == abstime->tv_sec
|
|
&& tp.tv_nsec > abstime->tv_nsec))
|
|
return ETIMEDOUT;
|
|
|
|
timeout.QuadPart = abstime->tv_sec * NSPERSEC
|
|
+ (abstime->tv_nsec + 99LL) / 100LL;
|
|
|
|
switch ((*cond)->clock_id)
|
|
{
|
|
case CLOCK_REALTIME:
|
|
timeout.QuadPart += FACTOR;
|
|
break;
|
|
default:
|
|
/* other clocks must be handled as relative timeout */
|
|
timeout.QuadPart -= tp.tv_sec * NSPERSEC + tp.tv_nsec / 100LL;
|
|
timeout.QuadPart *= -1LL;
|
|
break;
|
|
}
|
|
return (*cond)->wait (*mutex, &timeout);
|
|
}
|
|
__except (NO_ERROR) {}
|
|
__endtry
|
|
return EINVAL;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex)
|
|
{
|
|
pthread_testcancel ();
|
|
|
|
int err = __pthread_cond_wait_init (cond, mutex);
|
|
if (err)
|
|
return err;
|
|
return (*cond)->wait (*mutex, NULL);
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_condattr_init (pthread_condattr_t *condattr)
|
|
{
|
|
*condattr = new pthread_condattr;
|
|
if (!pthread_condattr::is_good_object (condattr))
|
|
{
|
|
delete (*condattr);
|
|
*condattr = NULL;
|
|
return ENOMEM;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_condattr_getpshared (const pthread_condattr_t *attr, int *pshared)
|
|
{
|
|
if (!pthread_condattr::is_good_object (attr))
|
|
return EINVAL;
|
|
*pshared = (*attr)->shared;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_condattr_setpshared (pthread_condattr_t *attr, int pshared)
|
|
{
|
|
if (!pthread_condattr::is_good_object (attr))
|
|
return EINVAL;
|
|
if ((pshared < 0) || (pshared > 1))
|
|
return EINVAL;
|
|
/* shared cond vars not currently supported */
|
|
if (pshared != PTHREAD_PROCESS_PRIVATE)
|
|
return EINVAL;
|
|
(*attr)->shared = pshared;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_condattr_getclock (const pthread_condattr_t *attr, clockid_t *clock_id)
|
|
{
|
|
if (!pthread_condattr::is_good_object (attr))
|
|
return EINVAL;
|
|
*clock_id = (*attr)->clock_id;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_condattr_setclock (pthread_condattr_t *attr, clockid_t clock_id)
|
|
{
|
|
if (!pthread_condattr::is_good_object (attr))
|
|
return EINVAL;
|
|
switch (clock_id)
|
|
{
|
|
case CLOCK_REALTIME:
|
|
case CLOCK_MONOTONIC:
|
|
break;
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
(*attr)->clock_id = clock_id;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_condattr_destroy (pthread_condattr_t *condattr)
|
|
{
|
|
if (!pthread_condattr::is_good_object (condattr))
|
|
return EINVAL;
|
|
delete (*condattr);
|
|
*condattr = NULL;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_rwlock_destroy (pthread_rwlock_t *rwlock)
|
|
{
|
|
if (pthread_rwlock::is_initializer (rwlock))
|
|
return 0;
|
|
if (!pthread_rwlock::is_good_object (rwlock))
|
|
return EINVAL;
|
|
|
|
if ((*rwlock)->writer || (*rwlock)->readers ||
|
|
(*rwlock)->waiting_readers || (*rwlock)->waiting_writers)
|
|
return EBUSY;
|
|
|
|
delete (*rwlock);
|
|
*rwlock = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
pthread_rwlock::init (pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr)
|
|
{
|
|
pthread_rwlock_t new_rwlock;
|
|
|
|
if (attr && !pthread_rwlockattr::is_good_object (attr))
|
|
return EINVAL;
|
|
|
|
rwlock_initialization_lock.lock ();
|
|
|
|
new_rwlock = new pthread_rwlock (attr ? (*attr) : NULL);
|
|
if (!is_good_object (&new_rwlock))
|
|
{
|
|
delete new_rwlock;
|
|
rwlock_initialization_lock.unlock ();
|
|
return EAGAIN;
|
|
}
|
|
|
|
int ret = 0;
|
|
|
|
__try
|
|
{
|
|
*rwlock = new_rwlock;
|
|
}
|
|
__except (NO_ERROR)
|
|
{
|
|
delete new_rwlock;
|
|
ret = EINVAL;
|
|
}
|
|
__endtry
|
|
rwlock_initialization_lock.unlock ();
|
|
return ret;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_rwlock_rdlock (pthread_rwlock_t *rwlock)
|
|
{
|
|
pthread_testcancel ();
|
|
|
|
if (pthread_rwlock::is_initializer (rwlock))
|
|
pthread_rwlock::init (rwlock, NULL);
|
|
if (!pthread_rwlock::is_good_object (rwlock))
|
|
return EINVAL;
|
|
|
|
return (*rwlock)->rdlock ();
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock)
|
|
{
|
|
if (pthread_rwlock::is_initializer (rwlock))
|
|
pthread_rwlock::init (rwlock, NULL);
|
|
if (!pthread_rwlock::is_good_object (rwlock))
|
|
return EINVAL;
|
|
|
|
return (*rwlock)->tryrdlock ();
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_rwlock_wrlock (pthread_rwlock_t *rwlock)
|
|
{
|
|
pthread_testcancel ();
|
|
|
|
if (pthread_rwlock::is_initializer (rwlock))
|
|
pthread_rwlock::init (rwlock, NULL);
|
|
if (!pthread_rwlock::is_good_object (rwlock))
|
|
return EINVAL;
|
|
|
|
return (*rwlock)->wrlock ();
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock)
|
|
{
|
|
if (pthread_rwlock::is_initializer (rwlock))
|
|
pthread_rwlock::init (rwlock, NULL);
|
|
if (!pthread_rwlock::is_good_object (rwlock))
|
|
return EINVAL;
|
|
|
|
return (*rwlock)->trywrlock ();
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_rwlock_unlock (pthread_rwlock_t *rwlock)
|
|
{
|
|
if (pthread_rwlock::is_initializer (rwlock))
|
|
return 0;
|
|
if (!pthread_rwlock::is_good_object (rwlock))
|
|
return EINVAL;
|
|
|
|
return (*rwlock)->unlock ();
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_rwlockattr_init (pthread_rwlockattr_t *rwlockattr)
|
|
{
|
|
*rwlockattr = new pthread_rwlockattr;
|
|
if (!pthread_rwlockattr::is_good_object (rwlockattr))
|
|
{
|
|
delete (*rwlockattr);
|
|
*rwlockattr = NULL;
|
|
return ENOMEM;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_rwlockattr_getpshared (const pthread_rwlockattr_t *attr, int *pshared)
|
|
{
|
|
if (!pthread_rwlockattr::is_good_object (attr))
|
|
return EINVAL;
|
|
*pshared = (*attr)->shared;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_rwlockattr_setpshared (pthread_rwlockattr_t *attr, int pshared)
|
|
{
|
|
if (!pthread_rwlockattr::is_good_object (attr))
|
|
return EINVAL;
|
|
if ((pshared < 0) || (pshared > 1))
|
|
return EINVAL;
|
|
/* shared rwlock vars not currently supported */
|
|
if (pshared != PTHREAD_PROCESS_PRIVATE)
|
|
return EINVAL;
|
|
(*attr)->shared = pshared;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_rwlockattr_destroy (pthread_rwlockattr_t *rwlockattr)
|
|
{
|
|
if (!pthread_rwlockattr::is_good_object (rwlockattr))
|
|
return EINVAL;
|
|
delete (*rwlockattr);
|
|
*rwlockattr = NULL;
|
|
return 0;
|
|
}
|
|
|
|
/* Thread signal */
|
|
extern "C" int
|
|
pthread_kill (pthread_t thread, int sig)
|
|
{
|
|
// lock myself, for the use of thread2signal
|
|
// two different kills might clash: FIXME
|
|
|
|
if (!pthread::is_good_object (&thread))
|
|
return EINVAL;
|
|
|
|
siginfo_t si = {0};
|
|
si.si_signo = sig;
|
|
si.si_code = SI_USER;
|
|
si.si_pid = myself->pid;
|
|
si.si_uid = myself->uid;
|
|
int rval;
|
|
if (!thread->valid)
|
|
rval = ESRCH;
|
|
else if (sig)
|
|
{
|
|
rval = sig_send (NULL, si, thread->cygtls);
|
|
if (rval == -1)
|
|
rval = get_errno ();
|
|
}
|
|
else
|
|
switch (WaitForSingleObject (thread->win32_obj_id, 0))
|
|
{
|
|
case WAIT_TIMEOUT:
|
|
rval = 0;
|
|
break;
|
|
default:
|
|
rval = ESRCH;
|
|
break;
|
|
}
|
|
|
|
// unlock myself
|
|
return rval;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_sigmask (int operation, const sigset_t *set, sigset_t *old_set)
|
|
{
|
|
int res = handle_sigprocmask (operation, set, old_set, _my_tls.sigmask);
|
|
syscall_printf ("%d = pthread_sigmask(%d, %p, %p)",
|
|
res, operation, set, old_set);
|
|
return res;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_sigqueue (pthread_t *thread, int sig, const union sigval value)
|
|
{
|
|
siginfo_t si = {0};
|
|
|
|
if (!pthread::is_good_object (thread))
|
|
return EINVAL;
|
|
if (!(*thread)->valid)
|
|
return ESRCH;
|
|
|
|
si.si_signo = sig;
|
|
si.si_code = SI_QUEUE;
|
|
si.si_value = value;
|
|
si.si_pid = myself->pid;
|
|
si.si_uid = myself->uid;
|
|
return sig_send (NULL, si, (*thread)->cygtls);
|
|
}
|
|
|
|
/* ID */
|
|
|
|
extern "C" int
|
|
pthread_equal (pthread_t t1, pthread_t t2)
|
|
{
|
|
return pthread::equal (t1, t2);
|
|
}
|
|
|
|
/* Mutexes */
|
|
|
|
int
|
|
pthread_mutex::init (pthread_mutex_t *mutex,
|
|
const pthread_mutexattr_t *attr,
|
|
const pthread_mutex_t initializer)
|
|
{
|
|
if (attr && !pthread_mutexattr::is_good_object (attr))
|
|
return EINVAL;
|
|
|
|
mutex_initialization_lock.lock ();
|
|
if (initializer == NULL || pthread_mutex::is_initializer (mutex))
|
|
{
|
|
pthread_mutex_t new_mutex = new pthread_mutex (attr ? (*attr) : NULL);
|
|
if (!is_good_object (&new_mutex))
|
|
{
|
|
delete new_mutex;
|
|
mutex_initialization_lock.unlock ();
|
|
return EAGAIN;
|
|
}
|
|
|
|
if (!attr && initializer)
|
|
{
|
|
if (initializer == PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP)
|
|
new_mutex->type = PTHREAD_MUTEX_RECURSIVE;
|
|
else if (initializer == PTHREAD_NORMAL_MUTEX_INITIALIZER_NP)
|
|
new_mutex->type = PTHREAD_MUTEX_NORMAL;
|
|
else if (initializer == PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP)
|
|
new_mutex->type = PTHREAD_MUTEX_ERRORCHECK;
|
|
}
|
|
|
|
__try
|
|
{
|
|
*mutex = new_mutex;
|
|
}
|
|
__except (NO_ERROR)
|
|
{
|
|
delete new_mutex;
|
|
mutex_initialization_lock.unlock ();
|
|
return EINVAL;
|
|
}
|
|
__endtry
|
|
}
|
|
mutex_initialization_lock.unlock ();
|
|
pthread_printf ("*mutex %p, attr %p, initializer %p", *mutex, attr, initializer);
|
|
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_mutex_getprioceiling (const pthread_mutex_t *mutex,
|
|
int *prioceiling)
|
|
{
|
|
/* We don't define _POSIX_THREAD_PRIO_PROTECT because we do't currently support
|
|
mutex priorities.
|
|
|
|
We can support mutex priorities in the future though:
|
|
Store a priority with each mutex.
|
|
When the mutex is optained, set the thread priority as appropriate
|
|
When the mutex is released, reset the thread priority. */
|
|
return ENOSYS;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_mutex_lock (pthread_mutex_t *mutex)
|
|
{
|
|
if (pthread_mutex::is_initializer (mutex))
|
|
pthread_mutex::init (mutex, NULL, *mutex);
|
|
if (!pthread_mutex::is_good_object (mutex))
|
|
return EINVAL;
|
|
return (*mutex)->lock ();
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_mutex_trylock (pthread_mutex_t *mutex)
|
|
{
|
|
if (pthread_mutex::is_initializer (mutex))
|
|
pthread_mutex::init (mutex, NULL, *mutex);
|
|
if (!pthread_mutex::is_good_object (mutex))
|
|
return EINVAL;
|
|
return (*mutex)->trylock ();
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_mutex_unlock (pthread_mutex_t *mutex)
|
|
{
|
|
if (pthread_mutex::is_initializer (mutex))
|
|
pthread_mutex::init (mutex, NULL, *mutex);
|
|
if (!pthread_mutex::is_good_object (mutex))
|
|
return EINVAL;
|
|
return (*mutex)->unlock ();
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_mutex_destroy (pthread_mutex_t *mutex)
|
|
{
|
|
int rv;
|
|
|
|
if (pthread_mutex::is_initializer (mutex))
|
|
return 0;
|
|
if (!pthread_mutex::is_good_object (mutex))
|
|
return EINVAL;
|
|
|
|
rv = (*mutex)->destroy ();
|
|
if (rv)
|
|
return rv;
|
|
|
|
*mutex = NULL;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_mutex_setprioceiling (pthread_mutex_t *mutex, int prioceiling,
|
|
int *old_ceiling)
|
|
{
|
|
return ENOSYS;
|
|
}
|
|
|
|
/* Spinlocks */
|
|
|
|
int
|
|
pthread_spinlock::init (pthread_spinlock_t *spinlock, int pshared)
|
|
{
|
|
pthread_spinlock_t new_spinlock = new pthread_spinlock (pshared);
|
|
if (!is_good_object (&new_spinlock))
|
|
{
|
|
delete new_spinlock;
|
|
return EAGAIN;
|
|
}
|
|
|
|
__try
|
|
{
|
|
*spinlock = new_spinlock;
|
|
}
|
|
__except (NO_ERROR)
|
|
{
|
|
delete new_spinlock;
|
|
return EINVAL;
|
|
}
|
|
__endtry
|
|
pthread_printf ("*spinlock %p, pshared %d", *spinlock, pshared);
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_spin_lock (pthread_spinlock_t *spinlock)
|
|
{
|
|
if (!pthread_spinlock::is_good_object (spinlock))
|
|
return EINVAL;
|
|
return (*spinlock)->lock ();
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_spin_trylock (pthread_spinlock_t *spinlock)
|
|
{
|
|
if (!pthread_spinlock::is_good_object (spinlock))
|
|
return EINVAL;
|
|
return (*spinlock)->trylock ();
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_spin_unlock (pthread_spinlock_t *spinlock)
|
|
{
|
|
if (!pthread_spinlock::is_good_object (spinlock))
|
|
return EINVAL;
|
|
return (*spinlock)->unlock ();
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_spin_destroy (pthread_spinlock_t *spinlock)
|
|
{
|
|
if (!pthread_spinlock::is_good_object (spinlock))
|
|
return EINVAL;
|
|
return (*spinlock)->destroy ();
|
|
}
|
|
|
|
/* Win32 doesn't support mutex priorities - see __pthread_mutex_getprioceiling
|
|
for more detail */
|
|
extern "C" int
|
|
pthread_mutexattr_getprotocol (const pthread_mutexattr_t *attr,
|
|
int *protocol)
|
|
{
|
|
if (!pthread_mutexattr::is_good_object (attr))
|
|
return EINVAL;
|
|
return ENOSYS;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_mutexattr_getpshared (const pthread_mutexattr_t *attr,
|
|
int *pshared)
|
|
{
|
|
if (!pthread_mutexattr::is_good_object (attr))
|
|
return EINVAL;
|
|
*pshared = (*attr)->pshared;
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_mutexattr_gettype (const pthread_mutexattr_t *attr, int *type)
|
|
{
|
|
if (!pthread_mutexattr::is_good_object (attr))
|
|
return EINVAL;
|
|
*type = (*attr)->mutextype;
|
|
return 0;
|
|
}
|
|
|
|
/* FIXME: write and test process shared mutex's. */
|
|
extern "C" int
|
|
pthread_mutexattr_init (pthread_mutexattr_t *attr)
|
|
{
|
|
*attr = new pthread_mutexattr ();
|
|
if (!pthread_mutexattr::is_good_object (attr))
|
|
{
|
|
delete (*attr);
|
|
*attr = NULL;
|
|
return ENOMEM;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_mutexattr_destroy (pthread_mutexattr_t *attr)
|
|
{
|
|
if (!pthread_mutexattr::is_good_object (attr))
|
|
return EINVAL;
|
|
delete (*attr);
|
|
*attr = NULL;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Win32 doesn't support mutex priorities */
|
|
extern "C" int
|
|
pthread_mutexattr_setprotocol (pthread_mutexattr_t *attr, int protocol)
|
|
{
|
|
if (!pthread_mutexattr::is_good_object (attr))
|
|
return EINVAL;
|
|
return ENOSYS;
|
|
}
|
|
|
|
/* Win32 doesn't support mutex priorities */
|
|
extern "C" int
|
|
pthread_mutexattr_setprioceiling (pthread_mutexattr_t *attr,
|
|
int prioceiling)
|
|
{
|
|
if (!pthread_mutexattr::is_good_object (attr))
|
|
return EINVAL;
|
|
return ENOSYS;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_mutexattr_getprioceiling (const pthread_mutexattr_t *attr,
|
|
int *prioceiling)
|
|
{
|
|
if (!pthread_mutexattr::is_good_object (attr))
|
|
return EINVAL;
|
|
return ENOSYS;
|
|
}
|
|
|
|
extern "C" int
|
|
pthread_mutexattr_setpshared (pthread_mutexattr_t *attr, int pshared)
|
|
{
|
|
if (!pthread_mutexattr::is_good_object (attr))
|
|
return EINVAL;
|
|
/* we don't use pshared for anything as yet. We need to test PROCESS_SHARED
|
|
*functionality
|
|
*/
|
|
if (pshared != PTHREAD_PROCESS_PRIVATE)
|
|
return EINVAL;
|
|
(*attr)->pshared = pshared;
|
|
return 0;
|
|
}
|
|
|
|
/* see pthread_mutex_gettype */
|
|
extern "C" int
|
|
pthread_mutexattr_settype (pthread_mutexattr_t *attr, int type)
|
|
{
|
|
if (!pthread_mutexattr::is_good_object (attr))
|
|
return EINVAL;
|
|
|
|
switch (type)
|
|
{
|
|
case PTHREAD_MUTEX_ERRORCHECK:
|
|
case PTHREAD_MUTEX_RECURSIVE:
|
|
case PTHREAD_MUTEX_NORMAL:
|
|
(*attr)->mutextype = type;
|
|
break;
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Semaphores */
|
|
|
|
List<semaphore> semaphore::semaphores;
|
|
|
|
semaphore::semaphore (int pshared, unsigned int value)
|
|
: verifyable_object (SEM_MAGIC),
|
|
shared (pshared),
|
|
currentvalue (-1),
|
|
startvalue (value),
|
|
fd (-1),
|
|
hash (0ULL),
|
|
sem (NULL)
|
|
{
|
|
SECURITY_ATTRIBUTES sa = (pshared != PTHREAD_PROCESS_PRIVATE)
|
|
? sec_all : sec_none_nih;
|
|
this->win32_obj_id = ::CreateSemaphore (&sa, value, INT32_MAX, NULL);
|
|
if (!this->win32_obj_id)
|
|
magic = 0;
|
|
|
|
semaphores.insert (this);
|
|
}
|
|
|
|
semaphore::semaphore (unsigned long long shash, LUID sluid, int sfd,
|
|
sem_t *ssem, int oflag, mode_t mode, unsigned int value)
|
|
: verifyable_object (SEM_MAGIC),
|
|
shared (PTHREAD_PROCESS_SHARED),
|
|
currentvalue (-1), /* Unused for named semaphores. */
|
|
startvalue (value),
|
|
fd (sfd),
|
|
hash (shash),
|
|
luid (sluid),
|
|
sem (ssem)
|
|
{
|
|
char name[MAX_PATH];
|
|
|
|
__small_sprintf (name, "semaphore/%016X%08x%08x",
|
|
hash, luid.HighPart, luid.LowPart);
|
|
this->win32_obj_id = ::CreateSemaphore (&sec_all, value, INT32_MAX, name);
|
|
if (!this->win32_obj_id)
|
|
magic = 0;
|
|
if (GetLastError () == ERROR_ALREADY_EXISTS && (oflag & O_EXCL))
|
|
{
|
|
__seterrno ();
|
|
CloseHandle (this->win32_obj_id);
|
|
magic = 0;
|
|
}
|
|
|
|
semaphores.insert (this);
|
|
}
|
|
|
|
semaphore::~semaphore ()
|
|
{
|
|
if (win32_obj_id)
|
|
CloseHandle (win32_obj_id);
|
|
|
|
semaphores.remove (this);
|
|
}
|
|
|
|
void
|
|
semaphore::_post ()
|
|
{
|
|
LONG dummy;
|
|
ReleaseSemaphore (win32_obj_id, 1, &dummy);
|
|
}
|
|
|
|
int
|
|
semaphore::_getvalue (int *sval)
|
|
{
|
|
NTSTATUS status;
|
|
SEMAPHORE_BASIC_INFORMATION sbi;
|
|
|
|
status = NtQuerySemaphore (win32_obj_id, SemaphoreBasicInformation, &sbi,
|
|
sizeof sbi, NULL);
|
|
int res;
|
|
if (NT_SUCCESS (status))
|
|
{
|
|
*sval = sbi.CurrentCount;
|
|
res = 0;
|
|
}
|
|
else
|
|
{
|
|
*sval = startvalue;
|
|
__seterrno_from_nt_status (status);
|
|
res = -1;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
int
|
|
semaphore::_trywait ()
|
|
{
|
|
/* FIXME: signals should be able to interrupt semaphores...
|
|
We probably need WaitForMultipleObjects here. */
|
|
if (WaitForSingleObject (win32_obj_id, 0) == WAIT_TIMEOUT)
|
|
{
|
|
set_errno (EAGAIN);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
semaphore::_timedwait (const struct timespec *abstime)
|
|
{
|
|
LARGE_INTEGER timeout;
|
|
|
|
__try
|
|
{
|
|
timeout.QuadPart = abstime->tv_sec * NSPERSEC
|
|
+ (abstime->tv_nsec + 99) / 100 + FACTOR;
|
|
|
|
switch (cygwait (win32_obj_id, &timeout,
|
|
cw_cancel | cw_cancel_self | cw_sig_eintr))
|
|
{
|
|
case WAIT_OBJECT_0:
|
|
break;
|
|
case WAIT_SIGNALED:
|
|
set_errno (EINTR);
|
|
return -1;
|
|
case WAIT_TIMEOUT:
|
|
set_errno (ETIMEDOUT);
|
|
return -1;
|
|
default:
|
|
pthread_printf ("cygwait failed. %E");
|
|
__seterrno ();
|
|
return -1;
|
|
}
|
|
}
|
|
__except (NO_ERROR)
|
|
{
|
|
/* According to SUSv3, abstime need not be checked for validity,
|
|
if the semaphore can be locked immediately. */
|
|
if (_trywait ())
|
|
{
|
|
set_errno (EINVAL);
|
|
return -1;
|
|
}
|
|
}
|
|
__endtry
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
semaphore::_wait ()
|
|
{
|
|
switch (cygwait (win32_obj_id, cw_infinite,
|
|
cw_cancel | cw_cancel_self | cw_sig_eintr))
|
|
{
|
|
case WAIT_OBJECT_0:
|
|
break;
|
|
case WAIT_SIGNALED:
|
|
set_errno (EINTR);
|
|
return -1;
|
|
default:
|
|
pthread_printf ("cygwait failed. %E");
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
semaphore::_fixup_before_fork ()
|
|
{
|
|
NTSTATUS status;
|
|
SEMAPHORE_BASIC_INFORMATION sbi;
|
|
|
|
status = NtQuerySemaphore (win32_obj_id, SemaphoreBasicInformation, &sbi,
|
|
sizeof sbi, NULL);
|
|
if (NT_SUCCESS (status))
|
|
currentvalue = sbi.CurrentCount;
|
|
else
|
|
currentvalue = startvalue;
|
|
}
|
|
|
|
void
|
|
semaphore::_fixup_after_fork ()
|
|
{
|
|
if (shared == PTHREAD_PROCESS_PRIVATE)
|
|
{
|
|
pthread_printf ("sem %p", this);
|
|
win32_obj_id = ::CreateSemaphore (&sec_none_nih, currentvalue,
|
|
INT32_MAX, NULL);
|
|
if (!win32_obj_id)
|
|
api_fatal ("failed to create new win32 semaphore, "
|
|
"currentvalue %ld, %E", currentvalue);
|
|
}
|
|
}
|
|
|
|
void
|
|
semaphore::_terminate ()
|
|
{
|
|
int _sem_close (sem_t *, bool);
|
|
|
|
if (sem)
|
|
_sem_close (sem, false);
|
|
}
|
|
|
|
/* static members */
|
|
|
|
int
|
|
semaphore::init (sem_t *sem, int pshared, unsigned int value)
|
|
{
|
|
/*
|
|
We can't tell the difference between reinitialising an
|
|
existing semaphore and initialising a semaphore who's
|
|
contents happen to be a valid pointer
|
|
*/
|
|
if (is_good_object (sem))
|
|
paranoid_printf ("potential attempt to reinitialise a semaphore");
|
|
|
|
if (value > SEM_VALUE_MAX)
|
|
{
|
|
set_errno(EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
*sem = new semaphore (pshared, value);
|
|
|
|
if (!is_good_object (sem))
|
|
{
|
|
delete (*sem);
|
|
*sem = NULL;
|
|
set_errno(EAGAIN);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
semaphore::destroy (sem_t *sem)
|
|
{
|
|
if (!is_good_object (sem))
|
|
{
|
|
set_errno(EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
/* It's invalid to destroy a semaphore not opened with sem_init. */
|
|
if ((*sem)->fd != -1)
|
|
{
|
|
set_errno(EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
/* FIXME - new feature - test for busy against threads... */
|
|
|
|
delete (*sem);
|
|
*sem = NULL;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
semaphore::close (sem_t *sem)
|
|
{
|
|
if (!is_good_object (sem))
|
|
{
|
|
set_errno(EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
/* It's invalid to close a semaphore not opened with sem_open. */
|
|
if ((*sem)->fd == -1)
|
|
{
|
|
set_errno(EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
delete (*sem);
|
|
delete sem;
|
|
return 0;
|
|
}
|
|
|
|
sem_t *
|
|
semaphore::open (unsigned long long hash, LUID luid, int fd, int oflag,
|
|
mode_t mode, unsigned int value, bool &wasopen)
|
|
{
|
|
if (value > SEM_VALUE_MAX)
|
|
{
|
|
set_errno (EINVAL);
|
|
return NULL;
|
|
}
|
|
|
|
/* sem_open is supposed to return the same pointer, if the same named
|
|
semaphore is opened multiple times in the same process, as long as
|
|
the semaphore hasn't been closed or unlinked in the meantime. */
|
|
semaphores.mx.lock ();
|
|
for (semaphore *sema = semaphores.head; sema; sema = sema->next)
|
|
if (sema->fd >= 0 && sema->hash == hash
|
|
&& sema->luid.HighPart == luid.HighPart
|
|
&& sema->luid.LowPart == luid.LowPart)
|
|
{
|
|
wasopen = true;
|
|
semaphores.mx.unlock ();
|
|
return sema->sem;
|
|
}
|
|
semaphores.mx.unlock ();
|
|
|
|
wasopen = false;
|
|
sem_t *sem = new sem_t;
|
|
if (!sem)
|
|
{
|
|
set_errno (ENOMEM);
|
|
return NULL;
|
|
}
|
|
|
|
*sem = new semaphore (hash, luid, fd, sem, oflag, mode, value);
|
|
|
|
if (!is_good_object (sem))
|
|
{
|
|
delete *sem;
|
|
delete sem;
|
|
return NULL;
|
|
}
|
|
return sem;
|
|
}
|
|
|
|
int
|
|
semaphore::wait (sem_t *sem)
|
|
{
|
|
pthread_testcancel ();
|
|
|
|
if (!is_good_object (sem))
|
|
{
|
|
set_errno (EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
return (*sem)->_wait ();
|
|
}
|
|
|
|
int
|
|
semaphore::trywait (sem_t *sem)
|
|
{
|
|
if (!is_good_object (sem))
|
|
{
|
|
set_errno (EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
return (*sem)->_trywait ();
|
|
}
|
|
|
|
int
|
|
semaphore::timedwait (sem_t *sem, const struct timespec *abstime)
|
|
{
|
|
if (!is_good_object (sem))
|
|
{
|
|
set_errno (EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
return (*sem)->_timedwait (abstime);
|
|
}
|
|
|
|
int
|
|
semaphore::post (sem_t *sem)
|
|
{
|
|
if (!is_good_object (sem))
|
|
{
|
|
set_errno (EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
(*sem)->_post ();
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
semaphore::getvalue (sem_t *sem, int *sval)
|
|
{
|
|
__try
|
|
{
|
|
if (is_good_object (sem))
|
|
return (*sem)->_getvalue (sval);
|
|
}
|
|
__except (NO_ERROR) {}
|
|
__endtry
|
|
set_errno (EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
semaphore::getinternal (sem_t *sem, int *sfd, unsigned long long *shash,
|
|
LUID *sluid, unsigned int *sval)
|
|
{
|
|
__try
|
|
{
|
|
if (!is_good_object (sem))
|
|
__leave;
|
|
if ((*sfd = (*sem)->fd) < 0)
|
|
__leave;
|
|
*shash = (*sem)->hash;
|
|
*sluid = (*sem)->luid;
|
|
/* POSIX defines the value in calls to sem_init/sem_open as unsigned,
|
|
but the sem_getvalue gets a pointer to int to return the value.
|
|
Go figure! */
|
|
return (*sem)->_getvalue ((int *)sval);
|
|
}
|
|
__except (NO_ERROR) {}
|
|
__endtry
|
|
set_errno (EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
/* pthread_null */
|
|
pthread *
|
|
pthread_null::get_null_pthread ()
|
|
{
|
|
/* because of weird entry points */
|
|
_instance.magic = 0;
|
|
return &_instance;
|
|
}
|
|
|
|
pthread_null::pthread_null ()
|
|
{
|
|
attr.joinable = PTHREAD_CREATE_DETACHED;
|
|
/* Mark ourselves as invalid */
|
|
magic = 0;
|
|
}
|
|
|
|
pthread_null::~pthread_null ()
|
|
{
|
|
}
|
|
|
|
bool
|
|
pthread_null::create (void *(*)(void *), pthread_attr *, void *)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void
|
|
pthread_null::exit (void *value_ptr)
|
|
{
|
|
_my_tls.remove (INFINITE);
|
|
ExitThread (0);
|
|
}
|
|
|
|
int
|
|
pthread_null::cancel ()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
pthread_null::testcancel ()
|
|
{
|
|
}
|
|
|
|
int
|
|
pthread_null::setcancelstate (int state, int *oldstate)
|
|
{
|
|
return EINVAL;
|
|
}
|
|
|
|
int
|
|
pthread_null::setcanceltype (int type, int *oldtype)
|
|
{
|
|
return EINVAL;
|
|
}
|
|
|
|
void
|
|
pthread_null::push_cleanup_handler (__pthread_cleanup_handler *handler)
|
|
{
|
|
}
|
|
|
|
void
|
|
pthread_null::pop_cleanup_handler (int const execute)
|
|
{
|
|
}
|
|
|
|
unsigned long
|
|
pthread_null::getsequence_np ()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
pthread_null pthread_null::_instance;
|
|
|
|
|
|
extern "C"
|
|
int
|
|
pthread_barrierattr_init (pthread_barrierattr_t * battr)
|
|
{
|
|
if (unlikely (battr == NULL))
|
|
return EINVAL;
|
|
|
|
*battr = new pthread_barrierattr;
|
|
(*battr)->shared = PTHREAD_PROCESS_PRIVATE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
extern "C"
|
|
int
|
|
pthread_barrierattr_setpshared (pthread_barrierattr_t * battr, int shared)
|
|
{
|
|
if (unlikely (! pthread_barrierattr::is_good_object (battr)))
|
|
return EINVAL;
|
|
|
|
if (unlikely (shared != PTHREAD_PROCESS_SHARED
|
|
&& shared != PTHREAD_PROCESS_PRIVATE))
|
|
return EINVAL;
|
|
|
|
(*battr)->shared = shared;
|
|
return 0;
|
|
}
|
|
|
|
|
|
extern "C"
|
|
int
|
|
pthread_barrierattr_getpshared (const pthread_barrierattr_t * battr,
|
|
int * shared)
|
|
{
|
|
if (unlikely (! pthread_barrierattr::is_good_object (battr)
|
|
|| shared == NULL))
|
|
return EINVAL;
|
|
|
|
*shared = (*battr)->shared;
|
|
return 0;
|
|
}
|
|
|
|
|
|
extern "C"
|
|
int
|
|
pthread_barrierattr_destroy (pthread_barrierattr_t * battr)
|
|
{
|
|
if (unlikely (! pthread_barrierattr::is_good_object (battr)))
|
|
return EINVAL;
|
|
|
|
delete_and_clear (battr);
|
|
return 0;
|
|
}
|
|
|
|
|
|
extern "C"
|
|
int
|
|
pthread_barrier_init (pthread_barrier_t * bar,
|
|
const pthread_barrierattr_t * attr, unsigned count)
|
|
{
|
|
if (unlikely (bar == NULL))
|
|
return EINVAL;
|
|
|
|
*bar = new pthread_barrier;
|
|
return (*bar)->init (attr, count);
|
|
}
|
|
|
|
|
|
int
|
|
pthread_barrier::init (const pthread_barrierattr_t * attr, unsigned count)
|
|
{
|
|
pthread_mutex_t * mutex = NULL;
|
|
|
|
if (unlikely ((attr != NULL
|
|
&& (! pthread_barrierattr::is_good_object (attr)
|
|
|| (*attr)->shared == PTHREAD_PROCESS_SHARED))
|
|
|| count == 0))
|
|
return EINVAL;
|
|
|
|
int retval = pthread_mutex_init (&mtx, NULL);
|
|
if (unlikely (retval != 0))
|
|
return retval;
|
|
|
|
retval = pthread_cond_init (&cond, NULL);
|
|
if (unlikely (retval != 0))
|
|
{
|
|
int ret = pthread_mutex_destroy (mutex);
|
|
if (ret != 0)
|
|
api_fatal ("pthread_mutex_destroy (%p) = %d", mutex, ret);
|
|
|
|
mtx = NULL;
|
|
return retval;
|
|
}
|
|
|
|
cnt = count;
|
|
cyc = 0;
|
|
wt = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
extern "C"
|
|
int
|
|
pthread_barrier_destroy (pthread_barrier_t * bar)
|
|
{
|
|
if (unlikely (! pthread_barrier::is_good_object (bar)))
|
|
return EINVAL;
|
|
|
|
int ret;
|
|
ret = (*bar)->destroy ();
|
|
if (ret == 0)
|
|
delete_and_clear (bar);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int
|
|
pthread_barrier::destroy ()
|
|
{
|
|
if (unlikely (wt != 0))
|
|
return EBUSY;
|
|
|
|
int retval = pthread_cond_destroy (&cond);
|
|
if (unlikely (retval != 0))
|
|
return retval;
|
|
else
|
|
cond = NULL;
|
|
|
|
retval = pthread_mutex_destroy (&mtx);
|
|
if (unlikely (retval != 0))
|
|
return retval;
|
|
else
|
|
mtx = NULL;
|
|
|
|
cnt = 0;
|
|
cyc = 0;
|
|
wt = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
extern "C"
|
|
int
|
|
pthread_barrier_wait (pthread_barrier_t * bar)
|
|
{
|
|
if (unlikely (! pthread_barrier::is_good_object (bar)))
|
|
return EINVAL;
|
|
|
|
return (*bar)->wait ();
|
|
}
|
|
|
|
|
|
int
|
|
pthread_barrier::wait ()
|
|
{
|
|
int retval = pthread_mutex_lock (&mtx);
|
|
if (unlikely (retval != 0))
|
|
return retval;
|
|
|
|
if (unlikely (wt >= cnt))
|
|
{
|
|
api_fatal ("wt >= cnt (%u >= %u)", wt, cnt);
|
|
return EINVAL;
|
|
}
|
|
|
|
if (unlikely (++wt == cnt))
|
|
{
|
|
++cyc;
|
|
/* This is the last thread to reach the barrier. Signal the waiting
|
|
threads to wake up and continue. */
|
|
retval = pthread_cond_broadcast (&cond);
|
|
if (unlikely (retval != 0))
|
|
goto cond_error;
|
|
|
|
wt = 0;
|
|
retval = pthread_mutex_unlock (&mtx);
|
|
if (unlikely (retval != 0))
|
|
abort ();
|
|
|
|
return PTHREAD_BARRIER_SERIAL_THREAD;
|
|
}
|
|
else
|
|
{
|
|
uint64_t cycle = cyc;
|
|
do
|
|
{
|
|
retval = pthread_cond_wait (&cond, &mtx);
|
|
if (unlikely (retval != 0))
|
|
goto cond_error;
|
|
}
|
|
while (unlikely (cycle == cyc));
|
|
|
|
retval = pthread_mutex_unlock (&mtx);
|
|
if (unlikely (retval != 0))
|
|
api_fatal ("pthread_mutex_unlock (%p) = %d", &mtx, retval);
|
|
|
|
return 0;
|
|
}
|
|
|
|
cond_error:
|
|
{
|
|
--wt;
|
|
int ret = pthread_mutex_unlock (&mtx);
|
|
if (unlikely (ret != 0))
|
|
api_fatal ("pthread_mutex_unlock (%p) = %d", &mtx, ret);
|
|
|
|
return retval;
|
|
}
|
|
}
|