Revision control

Copy as Markdown

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qthread.h"
#include "qthread_p.h"
#include "qcoreapplication.h"
#include <private/qcoreapplication_p.h>
#include <private/qeventdispatcher_win_p.h>
#include "qloggingcategory.h"
#include "qmutex.h"
#include <private/quniquehandle_types_p.h>
#include <private/qwin32ticks_p.h>
#include <qt_windows.h>
#ifndef _MT
# define _MT
#endif // _MT
#include <process.h>
extern "C" {
// MinGW is missing the declaration of SetThreadDescription:
WINBASEAPI
HRESULT
WINAPI
SetThreadDescription(
_In_ HANDLE hThread,
_In_ PCWSTR lpThreadDescription
);
}
#ifndef THREAD_POWER_THROTTLING_EXECUTION_SPEED
#define THREAD_POWER_THROTTLING_EXECUTION_SPEED 0x1
#define THREAD_POWER_THROTTLING_CURRENT_VERSION 1
typedef struct _THREAD_POWER_THROTTLING_STATE {
ULONG Version;
ULONG ControlMask;
ULONG StateMask;
} THREAD_POWER_THROTTLING_STATE;
#endif
#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x2
#endif
QT_BEGIN_NAMESPACE
Q_STATIC_LOGGING_CATEGORY(lcQThread, "qt.core.thread", QtWarningMsg)
#if QT_CONFIG(thread)
Q_CONSTINIT static thread_local QThreadData *currentThreadData = nullptr;
static void deref_current_thread_data(QThreadData *data);
static void destroy_current_thread_data(void *p)
{
QThreadData *data = static_cast<QThreadData *>(p);
QThread *thread = data->thread.loadAcquire();
if (data->isAdopted) {
// If this is an adopted thread, then QThreadData owns the QThread and
// this is very likely the last reference. These pointers cannot be
// null and there is no race.
QThreadPrivate *thread_p = static_cast<QThreadPrivate *>(QObjectPrivate::get(thread));
thread_p->finish();
} else {
// We may be racing the QThread destructor in another thread and it may
// have begun destruction; we must not dereference the QThread pointer.
}
deref_current_thread_data(data);
}
// static
void deref_current_thread_data(QThreadData *data)
{
// the QThread object may still have a reference, so this may not delete
data->deref();
// ... but we must reset it to zero before returning so we aren't
// leaving a dangling pointer.
currentThreadData = nullptr;
}
static QThreadData *get_thread_data()
{
return currentThreadData;
}
static void set_thread_data(QThreadData *data) noexcept
{
if (data) {
struct Cleanup {
Cleanup() { QThreadStoragePrivate::init(); }
~Cleanup() { destroy_current_thread_data(currentThreadData); }
};
static thread_local Cleanup currentThreadCleanup;
}
currentThreadData = data;
}
/*
QThreadData
*/
void QThreadData::clearCurrentThreadData()
{
set_thread_data(nullptr);
}
QThreadData *QThreadData::currentThreadData() noexcept
{
return get_thread_data();
}
QThreadData *QThreadData::createCurrentThreadData()
{
Q_ASSERT(!currentThreadData());
QThreadData *data = new QThreadData();
// This needs to be called prior to new QAdoptedThread() to avoid
// recursion (see qobject.cpp).
set_thread_data(data);
QT_TRY {
data->thread.storeRelease(new QAdoptedThread(data));
} QT_CATCH(...) {
deref_current_thread_data(data);
QT_RETHROW;
}
return data;
}
void QAdoptedThread::init()
{
d_func()->handle = GetCurrentThread();
}
/**************************************************************************
** QThreadPrivate
*************************************************************************/
#endif // QT_CONFIG(thread)
QAbstractEventDispatcher *QThreadPrivate::createEventDispatcher(QThreadData *data)
{
Q_UNUSED(data);
return new QEventDispatcherWin32;
}
#if QT_CONFIG(thread)
unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void *arg) noexcept
{
QThread *thr = reinterpret_cast<QThread *>(arg);
QThreadData *data = QThreadData::get2(thr);
data->ref();
set_thread_data(data);
data->threadId.storeRelaxed(QThread::currentThreadId());
// If a QThread is restarted, reuse the QBindingStatus, too
data->reuseBindingStatusForNewNativeThread();
{
QMutexLocker locker(&thr->d_func()->mutex);
data->quitNow = thr->d_func()->exited;
if (thr->d_func()->serviceLevel != QThread::QualityOfService::Auto)
thr->d_func()->setQualityOfServiceLevel(thr->d_func()->serviceLevel);
}
data->ensureEventDispatcher();
data->eventDispatcher.loadRelaxed()->startingUp();
// sets the name of the current thread.
QString threadName = std::exchange(thr->d_func()->objectName, {});
if (Q_LIKELY(threadName.isEmpty()))
threadName = QString::fromUtf8(thr->metaObject()->className());
SetThreadDescription(GetCurrentThread(), reinterpret_cast<const wchar_t *>(threadName.utf16()));
emit thr->started(QThread::QPrivateSignal());
QThread::setTerminationEnabled(true);
thr->run();
thr->d_func()->finish();
return 0;
}
void QThreadPrivate::setQualityOfServiceLevel(QThread::QualityOfService qosLevel)
{
Q_Q(QThread);
serviceLevel = qosLevel;
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN10_RS3)
qCDebug(lcQThread) << "Setting thread QoS class to" << qosLevel << "for thread" << q;
THREAD_POWER_THROTTLING_STATE state;
memset(&state, 0, sizeof(state));
state.Version = THREAD_POWER_THROTTLING_CURRENT_VERSION;
switch (qosLevel) {
case QThread::QualityOfService::Auto:
state.ControlMask = 0; // Unset control of QoS
state.StateMask = 0;
break;
case QThread::QualityOfService::Eco:
state.ControlMask = THREAD_POWER_THROTTLING_EXECUTION_SPEED;
state.StateMask = THREAD_POWER_THROTTLING_EXECUTION_SPEED;
break;
case QThread::QualityOfService::High:
state.ControlMask = THREAD_POWER_THROTTLING_EXECUTION_SPEED;
state.StateMask = 0; // Ask to disable throttling
break;
}
if (!SetThreadInformation(::GetCurrentThread(), THREAD_INFORMATION_CLASS::ThreadPowerThrottling,
&state, sizeof(state))) {
qErrnoWarning("Failed to set thread power throttling state");
}
#endif
}
/*
For regularly terminating threads, this will be called and executed by the thread as the
last code before the thread exits. In that case, \a arg is the current QThread.
However, this function will also be called by QThread::terminate (as well as wait() and
setTerminationEnabled) to give Qt a chance to update the terminated thread's state and
process pending DeleteLater events for objects that live in the terminated thread. And for
adopted thread, this method is called by the thread watcher.
In those cases, \a arg will not be the current thread.
*/
void QThreadPrivate::finish(bool lockAnyway) noexcept
{
QThreadPrivate *d = this;
QThread *thr = q_func();
QMutexLocker locker(lockAnyway ? &d->mutex : nullptr);
d->threadState = QThreadPrivate::Finishing;
d->priority = QThread::InheritPriority;
if (lockAnyway)
locker.unlock();
emit thr->finished(QThread::QPrivateSignal());
QCoreApplicationPrivate::sendPostedEvents(nullptr, QEvent::DeferredDelete, d->data);
QThreadStoragePrivate::finish(&d->data->tls);
if (lockAnyway)
locker.relock();
QAbstractEventDispatcher *eventDispatcher = d->data->eventDispatcher.loadRelaxed();
if (eventDispatcher) {
d->data->eventDispatcher = 0;
if (lockAnyway)
locker.unlock();
eventDispatcher->closingDown();
delete eventDispatcher;
if (lockAnyway)
locker.relock();
}
d->threadState = QThreadPrivate::Finished;
d->interruptionRequested.store(false, std::memory_order_relaxed);
if (!d->waiters) {
CloseHandle(d->handle);
d->handle = 0;
}
}
/**************************************************************************
** QThread
*************************************************************************/
Qt::HANDLE QThread::currentThreadIdImpl() noexcept
{
return reinterpret_cast<Qt::HANDLE>(quintptr(GetCurrentThreadId()));
}
int QThread::idealThreadCount() noexcept
{
// Since Windows 11 & Server 2022, the process defaults to affinity to all
// processor groups, even though each thread is limited to a specific
// processor group. Prior to that, the application starts with affinity to
// a specific group but the process becomes multi-group if the application
// changes any thread's affinity to another group.
//
auto countArray = [](QSpan<const USHORT> groups) {
int total = 0;
for (USHORT group : groups)
total += GetActiveProcessorCount(group);
return total;
};
// Typical case: all but the largest systems will fit this array
// Note: this API has been around since Windows 7.
USHORT groups[16];
USHORT groupCount = std::size(groups);
if (Q_LIKELY(GetProcessGroupAffinity(HANDLE(-1), &groupCount, groups))) {
return countArray({ groups, groupCount });
} else if (groupCount > std::size(groups)) {
// use a larger buffer
auto buffer = static_cast<USHORT *>(alloca(sizeof(USHORT) * groupCount));
if (GetProcessGroupAffinity(HANDLE(-1), &groupCount, buffer))
return countArray({ buffer, groupCount });
}
// The new API failed, for some reason (it shouldn't have!). Fall back to
// the oldest of APIs. Note this may be limited to 64 logical processors.
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
return sysinfo.dwNumberOfProcessors;
}
void QThread::yieldCurrentThread()
{
SwitchToThread();
}
#endif // QT_CONFIG(thread)
void QThread::sleep(std::chrono::nanoseconds nsecs)
{
using namespace std::chrono;
if (nsecs < 30ms) {
QUniqueWin32NullHandle waitableTimerHandle;
waitableTimerHandle.reset(CreateWaitableTimerEx(
nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS));
if (waitableTimerHandle) {
using namespace std::chrono_literals;
// SetWaitableTimerEx's uses intervals of 100ns (0.1µs)
const auto ticks100ns = ceil<QtPrivate::win32_ticks>(nsecs);
LARGE_INTEGER i;
// Negate to make it a relative timeout:
i.QuadPart = std::min(-(ticks100ns.count()), -1ll);
BOOL timerResult = SetWaitableTimerEx(
waitableTimerHandle.get(), &i,
0, // not periodic
nullptr, nullptr, // no callback
nullptr, // no wake context / don't wake from sleep
0); // lowest tolerated delay
if (timerResult == TRUE) {
if (WaitForSingleObject(waitableTimerHandle.get(), INFINITE) == ERROR_SUCCESS)
return;
}
}
}
// Fallback:
::Sleep(DWORD(ceil<milliseconds>(nsecs).count()));
}
void QThread::sleep(unsigned long secs)
{
::Sleep(secs * 1000);
}
void QThread::msleep(unsigned long msecs)
{
::Sleep(msecs);
}
void QThread::usleep(unsigned long usecs)
{
sleep(std::chrono::microseconds(usecs));
}
#if QT_CONFIG(thread)
void QThread::start(Priority priority)
{
Q_D(QThread);
QMutexLocker locker(&d->mutex);
if (d->threadState == QThreadPrivate::Finishing) {
locker.unlock();
wait();
locker.relock();
}
if (d->threadState == QThreadPrivate::Running)
return;
// avoid interacting with the binding system
d->objectName = d->extraData ? d->extraData->objectName.valueBypassingBindings()
: QString();
d->threadState = QThreadPrivate::Running;
d->exited = false;
d->returnCode = 0;
d->interruptionRequested.store(false, std::memory_order_relaxed);
/*
NOTE: we create the thread in the suspended state, set the
priority and then resume the thread.
since threads are created with normal priority by default, we
could get into a case where a thread (with priority less than
NormalPriority) tries to create a new thread (also with priority
less than NormalPriority), but the newly created thread preempts
its 'parent' and runs at normal priority.
*/
#if defined(Q_CC_MSVC) && !defined(_DLL)
// MSVC -MT or -MTd build
d->handle = (Qt::HANDLE) _beginthreadex(NULL, d->stackSize, QThreadPrivate::start,
this, CREATE_SUSPENDED, nullptr);
#else
// MSVC -MD or -MDd or MinGW build
d->handle = CreateThread(nullptr, d->stackSize,
reinterpret_cast<LPTHREAD_START_ROUTINE>(QThreadPrivate::start),
this, CREATE_SUSPENDED, nullptr);
#endif
if (!d->handle) {
qErrnoWarning("QThread::start: Failed to create thread");
d->threadState = QThreadPrivate::NotStarted;
return;
}
int prio;
d->priority = priority;
switch (priority) {
case IdlePriority:
prio = THREAD_PRIORITY_IDLE;
break;
case LowestPriority:
prio = THREAD_PRIORITY_LOWEST;
break;
case LowPriority:
prio = THREAD_PRIORITY_BELOW_NORMAL;
break;
case NormalPriority:
prio = THREAD_PRIORITY_NORMAL;
break;
case HighPriority:
prio = THREAD_PRIORITY_ABOVE_NORMAL;
break;
case HighestPriority:
prio = THREAD_PRIORITY_HIGHEST;
break;
case TimeCriticalPriority:
prio = THREAD_PRIORITY_TIME_CRITICAL;
break;
case InheritPriority:
default:
prio = GetThreadPriority(GetCurrentThread());
break;
}
if (!SetThreadPriority(d->handle, prio)) {
qErrnoWarning("QThread::start: Failed to set thread priority");
}
if (ResumeThread(d->handle) == (DWORD) -1) {
qErrnoWarning("QThread::start: Failed to resume new thread");
}
}
void QThread::terminate()
{
Q_D(QThread);
QMutexLocker locker(&d->mutex);
if (d->threadState != QThreadPrivate::Running)
return;
if (!d->terminationEnabled) {
d->terminatePending = true;
return;
}
TerminateThread(d->handle, 0);
d->finish(false);
}
bool QThreadPrivate::wait(QMutexLocker<QMutex> &locker, QDeadlineTimer deadline)
{
Q_ASSERT(threadState != QThreadPrivate::Finished);
Q_ASSERT(locker.isLocked());
QThreadPrivate *d = this;
++d->waiters;
locker.mutex()->unlock();
bool ret = false;
switch (WaitForSingleObject(d->handle, deadline.remainingTime())) {
case WAIT_OBJECT_0:
ret = true;
break;
case WAIT_FAILED:
qErrnoWarning("QThread::wait: Thread wait failure");
break;
case WAIT_ABANDONED:
case WAIT_TIMEOUT:
default:
break;
}
locker.mutex()->lock();
--d->waiters;
if (ret && d->threadState < QThreadPrivate::Finished) {
// thread was terminated by someone else
d->finish(false);
}
if (d->threadState == QThreadPrivate::Finished && !d->waiters) {
CloseHandle(d->handle);
d->handle = 0;
}
return ret;
}
void QThread::setTerminationEnabled(bool enabled)
{
QThreadData *thr = QThreadData::currentThreadData();
Q_ASSERT_X(thr && !thr->isAdopted, "QThread::setTerminationEnabled()",
"Current thread was not started with QThread.");
QThreadPrivate *d = thr->thread.loadRelaxed()->d_func();
QMutexLocker locker(&d->mutex);
d->terminationEnabled = enabled;
if (enabled && d->terminatePending) {
d->finish(false);
locker.unlock(); // don't leave the mutex locked!
_endthreadex(0);
}
}
// Caller must hold the mutex
void QThreadPrivate::setPriority(QThread::Priority threadPriority)
{
// copied from start() with a few modifications:
int prio;
priority = threadPriority;
switch (threadPriority) {
case QThread::IdlePriority:
prio = THREAD_PRIORITY_IDLE;
break;
case QThread::LowestPriority:
prio = THREAD_PRIORITY_LOWEST;
break;
case QThread::LowPriority:
prio = THREAD_PRIORITY_BELOW_NORMAL;
break;
case QThread::NormalPriority:
prio = THREAD_PRIORITY_NORMAL;
break;
case QThread::HighPriority:
prio = THREAD_PRIORITY_ABOVE_NORMAL;
break;
case QThread::HighestPriority:
prio = THREAD_PRIORITY_HIGHEST;
break;
case QThread::TimeCriticalPriority:
prio = THREAD_PRIORITY_TIME_CRITICAL;
break;
default:
return;
}
if (!SetThreadPriority(handle, prio)) {
qErrnoWarning("QThread::setPriority: Failed to set thread priority");
}
}
#endif // QT_CONFIG(thread)
QT_END_NAMESPACE