The previous code did not synchronize some of the flags, so it was
possible that `cancel()` did not work properly (e.g. no APC queued).
This change uses atomics to set/read the flags, it also avoids using
the global thread lock for synchronization as getting the condvar before
queuing the APC avoids having to sync with `docancel()`. However, to
make sure we don't use a stale and potentially already freed condvar
when the thread is canceled a new lock is added and held until the
condvar has been signaled (using atomics for that member doesn't work as
the ordering rules enforced by atomics can not avoid that the thread in
`cancel()` potentially reads a stale value, or the race between reading
and signaling it).
*/
static spinlock_t *threads_lock;
*/
static spinlock_t *threads_lock;
+/**
+ * Lock for setting condvars on threads
+ */
+static spinlock_t *condvar_lock;
+
/**
* Counter to assign printable thread IDs
*/
/**
* Counter to assign printable thread IDs
*/
thread = get_current_thread();
thread = get_current_thread();
- threads_lock->lock(threads_lock);
+ condvar_lock->lock(condvar_lock);
thread->condvar = condvar;
thread->condvar = condvar;
- threads_lock->unlock(threads_lock);
+ condvar_lock->unlock(condvar_lock);
/* this is a cancellation point, as condvar wait is one */
SleepEx(0, TRUE);
/* this is a cancellation point, as condvar wait is one */
SleepEx(0, TRUE);
{
private_thread_t *this = (private_thread_t*)dwParam;
{
private_thread_t *this = (private_thread_t*)dwParam;
- /* make sure cancel() does not access this anymore */
- threads_lock->lock(threads_lock);
- threads_lock->unlock(threads_lock);
-
end_thread(this);
ExitThread(0);
}
end_thread(this);
ExitThread(0);
}
METHOD(thread_t, cancel, void,
private_thread_t *this)
{
METHOD(thread_t, cancel, void,
private_thread_t *this)
{
- this->canceled = TRUE;
- if (this->cancelability)
+ atomic_set_bool(&this->canceled, TRUE);
+ if (atomic_get_bool(&this->cancelability) &&
+ cas_bool(&this->cancel_pending, FALSE, TRUE))
- threads_lock->lock(threads_lock);
- if (!this->cancel_pending)
+ CONDITION_VARIABLE *cv;
+
+ condvar_lock->lock(condvar_lock);
+ cv = this->condvar;
+ QueueUserAPC(docancel, this->handle, (uintptr_t)this);
+ if (cv)
- this->cancel_pending = TRUE;
- QueueUserAPC(docancel, this->handle, (uintptr_t)this);
- if (this->condvar)
- {
- WakeAllConditionVariable(this->condvar);
- }
+ WakeAllConditionVariable(cv);
- threads_lock->unlock(threads_lock);
+ condvar_lock->unlock(condvar_lock);
static DWORD thread_cb(private_thread_t *this)
{
/* Enable cancelability once the thread starts. We must check for any
static DWORD thread_cb(private_thread_t *this)
{
/* Enable cancelability once the thread starts. We must check for any
- * pending cancellation request an queue the APC that gets executed
+ * pending cancellation request and queue the APC that gets executed
* at the first cancellation point. */
* at the first cancellation point. */
- this->cancelability = TRUE;
- if (this->canceled)
+ atomic_set_bool(&this->cancelability, TRUE);
+ if (atomic_get_bool(&this->canceled))
bool old;
this = get_current_thread();
bool old;
this = get_current_thread();
- old = this->cancelability;
- this->cancelability = enable;
+ old = atomic_get_bool(&this->cancelability);
+ atomic_set_bool(&this->cancelability, enable);
- if (enable && !old && this->canceled)
+ if (enable && !old && atomic_get_bool(&this->canceled))
void threads_init()
{
threads_lock = spinlock_create();
void threads_init()
{
threads_lock = spinlock_create();
+ condvar_lock = spinlock_create();
threads = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4);
/* reset counter should we initialize more than once */
threads = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4);
/* reset counter should we initialize more than once */
threads_lock->destroy(threads_lock);
threads_lock = NULL;
threads_lock->destroy(threads_lock);
threads_lock = NULL;
+ condvar_lock->destroy(condvar_lock);
+ condvar_lock = NULL;
threads->destroy(threads);
}
threads->destroy(threads);
}