QFutureInterface: make accesses to 'state' thread-safe

Introduce helper functions switch_{on,off,from_to} to make
the code more readable, and prepare everything for later
optimizations reducing the sizes of critical sections (by
locking the mutex later, or even never).

This commit, however, is only concerned with shutting up
tsan.

In waitForResult(), simplified the code by removing an
unneeded if guard: the condition is checked in the while
loop immediately following in the then-block, and the
local variable declaration that precedes the loop is not
worth guarding.

Change-Id: I24bfd864ca96f862302536ad8662065e6f366fa8
Reviewed-by: David Faure <david.faure@kdab.com>
This commit is contained in:
Marc Mutz 2013-09-20 02:38:22 +02:00
parent f1b4bd4790
commit 3691f7ca0c
2 changed files with 64 additions and 47 deletions

View File

@ -77,13 +77,33 @@ QFutureInterfaceBase::~QFutureInterfaceBase()
delete d;
}
static inline int switch_on(QAtomicInt &a, int which)
{
return a.fetchAndOrRelaxed(which) | which;
}
static inline int switch_off(QAtomicInt &a, int which)
{
return a.fetchAndAndRelaxed(~which) & ~which;
}
static inline int switch_from_to(QAtomicInt &a, int from, int to)
{
int newValue;
int expected = a.load();
do {
newValue = (expected & ~from) | to;
} while (!a.testAndSetRelaxed(expected, newValue, expected));
return newValue;
}
void QFutureInterfaceBase::cancel()
{
QMutexLocker locker(&d->m_mutex);
if (d->state & Canceled)
if (d->state.load() & Canceled)
return;
d->state = State((d->state & ~Paused) | Canceled);
switch_from_to(d->state, Paused, Canceled);
d->waitCondition.wakeAll();
d->pausedWaitCondition.wakeAll();
d->sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Canceled));
@ -93,10 +113,10 @@ void QFutureInterfaceBase::setPaused(bool paused)
{
QMutexLocker locker(&d->m_mutex);
if (paused) {
d->state = State(d->state | Paused);
switch_on(d->state, Paused);
d->sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Paused));
} else {
d->state = State(d->state & ~Paused);
switch_off(d->state, Paused);
d->pausedWaitCondition.wakeAll();
d->sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Resumed));
}
@ -105,29 +125,24 @@ void QFutureInterfaceBase::setPaused(bool paused)
void QFutureInterfaceBase::togglePaused()
{
QMutexLocker locker(&d->m_mutex);
if (d->state & Paused) {
d->state = State(d->state & ~Paused);
if (d->state.load() & Paused) {
switch_off(d->state, Paused);
d->pausedWaitCondition.wakeAll();
d->sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Resumed));
} else {
d->state = State(d->state | Paused);
switch_on(d->state, Paused);
d->sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Paused));
}
}
void QFutureInterfaceBase::setThrottled(bool enable)
{
// bail out if we are not changing the state
if ((enable && (d->state & Throttled)) || (!enable && !(d->state & Throttled)))
return;
// lock and change the state
QMutexLocker lock(&d->m_mutex);
if (enable) {
d->state = State(d->state | Throttled);
switch_on(d->state, Throttled);
} else {
d->state = State(d->state & ~Throttled);
if (!(d->state & Paused))
switch_off(d->state, Throttled);
if (!(d->state.load() & Paused))
d->pausedWaitCondition.wakeAll();
}
}
@ -178,11 +193,15 @@ bool QFutureInterfaceBase::waitForNextResult()
void QFutureInterfaceBase::waitForResume()
{
// return early if possible to avoid taking the mutex lock.
if ((d->state & Paused) == false || (d->state & Canceled))
return;
{
const int state = d->state.load();
if (!(state & Paused) || (state & Canceled))
return;
}
QMutexLocker lock(&d->m_mutex);
if ((d->state & Paused) == false || (d->state & Canceled))
const int state = d->state.load();
if (!(state & Paused) || (state & Canceled))
return;
// decrease active thread count since this thread will wait.
@ -230,7 +249,7 @@ bool QFutureInterfaceBase::isProgressUpdateNeeded() const
void QFutureInterfaceBase::reportStarted()
{
QMutexLocker locker(&d->m_mutex);
if ((d->state & Started) || (d->state & Canceled) || (d->state & Finished))
if (d->state.load() & (Started|Canceled|Finished))
return;
d->setState(State(Started | Running));
@ -246,11 +265,11 @@ void QFutureInterfaceBase::reportCanceled()
void QFutureInterfaceBase::reportException(const QException &exception)
{
QMutexLocker locker(&d->m_mutex);
if ((d->state & Canceled) || (d->state & Finished))
if (d->state.load() & (Canceled|Finished))
return;
d->m_exceptionStore.setException(exception);
d->state = State(d->state | Canceled);
switch_on(d->state, Canceled);
d->waitCondition.wakeAll();
d->pausedWaitCondition.wakeAll();
d->sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Canceled));
@ -260,8 +279,8 @@ void QFutureInterfaceBase::reportException(const QException &exception)
void QFutureInterfaceBase::reportFinished()
{
QMutexLocker locker(&d->m_mutex);
if (!(d->state & Finished)) {
d->state = State((d->state & ~Running) | Finished);
if (!isFinished()) {
switch_from_to(d->state, Running, Finished);
d->waitCondition.wakeAll();
d->sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Finished));
}
@ -281,7 +300,7 @@ int QFutureInterfaceBase::expectedResultCount()
bool QFutureInterfaceBase::queryState(State state) const
{
return (d->state & state);
return d->state.load() & state;
}
void QFutureInterfaceBase::waitForResult(int resultIndex)
@ -289,7 +308,7 @@ void QFutureInterfaceBase::waitForResult(int resultIndex)
d->m_exceptionStore.throwPossibleException();
QMutexLocker lock(&d->m_mutex);
if (!(d->state & Running))
if (!isRunning())
return;
lock.unlock();
@ -299,11 +318,9 @@ void QFutureInterfaceBase::waitForResult(int resultIndex)
lock.relock();
if (d->state & Running) {
const int waitIndex = (resultIndex == -1) ? INT_MAX : resultIndex;
while ((d->state & Running) && d->internal_isResultReadyAt(waitIndex) == false)
d->waitCondition.wait(&d->m_mutex);
}
const int waitIndex = (resultIndex == -1) ? INT_MAX : resultIndex;
while (isRunning() && !d->internal_isResultReadyAt(waitIndex))
d->waitCondition.wait(&d->m_mutex);
d->m_exceptionStore.throwPossibleException();
}
@ -311,7 +328,7 @@ void QFutureInterfaceBase::waitForResult(int resultIndex)
void QFutureInterfaceBase::waitForFinished()
{
QMutexLocker lock(&d->m_mutex);
const bool alreadyFinished = !(d->state & Running);
const bool alreadyFinished = !isRunning();
lock.unlock();
if (!alreadyFinished) {
@ -319,7 +336,7 @@ void QFutureInterfaceBase::waitForFinished()
lock.relock();
while (d->state & Running)
while (isRunning())
d->waitCondition.wait(&d->m_mutex);
}
@ -328,7 +345,7 @@ void QFutureInterfaceBase::waitForFinished()
void QFutureInterfaceBase::reportResultsReady(int beginIndex, int endIndex)
{
if ((d->state & Canceled) || (d->state & Finished) || beginIndex == endIndex)
if (beginIndex == endIndex || (d->state.load() & (Canceled|Finished)))
return;
d->waitCondition.wakeAll();
@ -390,7 +407,7 @@ void QFutureInterfaceBase::setProgressValueAndText(int progressValue,
if (d->m_progressValue >= progressValue)
return;
if ((d->state & Canceled) || (d->state & Finished))
if (d->state.load() & (Canceled|Finished))
return;
if (d->internal_updateProgress(progressValue, progressText)) {
@ -462,10 +479,10 @@ bool QFutureInterfaceBasePrivate::internal_waitForNextResult()
if (m_results.hasNextResult())
return true;
while ((state & QFutureInterfaceBase::Running) && m_results.hasNextResult() == false)
while ((state.load() & QFutureInterfaceBase::Running) && m_results.hasNextResult() == false)
waitCondition.wait(&m_mutex);
return (!(state & QFutureInterfaceBase::Canceled) && m_results.hasNextResult());
return !(state.load() & QFutureInterfaceBase::Canceled) && m_results.hasNextResult();
}
bool QFutureInterfaceBasePrivate::internal_updateProgress(int progress,
@ -488,16 +505,16 @@ bool QFutureInterfaceBasePrivate::internal_updateProgress(int progress,
void QFutureInterfaceBasePrivate::internal_setThrottled(bool enable)
{
// bail out if we are not changing the state
if ((enable && (state & QFutureInterfaceBase::Throttled))
|| (!enable && !(state & QFutureInterfaceBase::Throttled)))
if ((enable && (state.load() & QFutureInterfaceBase::Throttled))
|| (!enable && !(state.load() & QFutureInterfaceBase::Throttled)))
return;
// change the state
if (enable) {
state = QFutureInterfaceBase::State(state | QFutureInterfaceBase::Throttled);
switch_on(state, QFutureInterfaceBase::Throttled);
} else {
state = QFutureInterfaceBase::State(state & ~QFutureInterfaceBase::Throttled);
if (!(state & QFutureInterfaceBase::Paused))
switch_off(state, QFutureInterfaceBase::Throttled);
if (!(state.load() & QFutureInterfaceBase::Paused))
pausedWaitCondition.wakeAll();
}
}
@ -532,7 +549,7 @@ void QFutureInterfaceBasePrivate::connectOutputInterface(QFutureCallOutInterface
{
QMutexLocker locker(&m_mutex);
if (state & QFutureInterfaceBase::Started) {
if (state.load() & QFutureInterfaceBase::Started) {
interface->postCallOutEvent(QFutureCallOutEvent(QFutureCallOutEvent::Started));
interface->postCallOutEvent(QFutureCallOutEvent(QFutureCallOutEvent::ProgressRange,
m_progressMinimum,
@ -552,13 +569,13 @@ void QFutureInterfaceBasePrivate::connectOutputInterface(QFutureCallOutInterface
it.batchedAdvance();
}
if (state & QFutureInterfaceBase::Paused)
if (state.load() & QFutureInterfaceBase::Paused)
interface->postCallOutEvent(QFutureCallOutEvent(QFutureCallOutEvent::Paused));
if (state & QFutureInterfaceBase::Canceled)
if (state.load() & QFutureInterfaceBase::Canceled)
interface->postCallOutEvent(QFutureCallOutEvent(QFutureCallOutEvent::Canceled));
if (state & QFutureInterfaceBase::Finished)
if (state.load() & QFutureInterfaceBase::Finished)
interface->postCallOutEvent(QFutureCallOutEvent(QFutureCallOutEvent::Finished));
outputConnections.append(interface);
@ -577,7 +594,7 @@ void QFutureInterfaceBasePrivate::disconnectOutputInterface(QFutureCallOutInterf
void QFutureInterfaceBasePrivate::setState(QFutureInterfaceBase::State newState)
{
state = newState;
state.store(newState);
}
QT_END_NAMESPACE

View File

@ -155,7 +155,7 @@ public:
int m_progressValue; // TQ
int m_progressMinimum; // TQ
int m_progressMaximum; // TQ
QFutureInterfaceBase::State state;
QAtomicInt state; // reads and writes can happen unprotected, both must be atomic
QElapsedTimer progressTime;
QWaitCondition pausedWaitCondition;
QtPrivate::ResultStoreBase m_results;