Fix QThreadPool::maxThreadCount() usage

The docs claim that QThreadPool always creates at least one thread.
However, the user can (usually by mistake) request zero or a negative
number of threads.
The maxThreadCount() function is simply returning the value, that was
requested by the user.
Since it's a public API, it is used in several places in QtConcurrent,
where it is assumed that the value is always positive. This can lead
to a crash if the user sets zero as a maxThreadCount.

Update all such places with std::max(maxThreadCount(), 1).
Prefer this approach over changing the return value of
maxThreadCount(), because its behavior is documented and tested.

Amends 885eff053797d56f2e295558d0a71b030fbb1a69.

Fixes: QTBUG-120335
Pick-to: 6.5 6.2
Change-Id: Id3b2087cec7fbc7a2d42febca6586f2dacffe444
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
(cherry picked from commit 936e72d18075b79c8d29353618dfbd052ae59dae)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit 41a605e0f2251db78cb852bb161dd0020c63c935)
This commit is contained in:
Ivan Solovev 2023-12-22 15:06:10 +01:00 committed by Qt Cherry-pick Bot
parent a18292e906
commit e632241386
4 changed files with 25 additions and 3 deletions

View File

@ -67,7 +67,7 @@ namespace QtConcurrent {
*/ */
BlockSizeManager::BlockSizeManager(QThreadPool *pool, int iterationCount) BlockSizeManager::BlockSizeManager(QThreadPool *pool, int iterationCount)
: maxBlockSize(iterationCount / (pool->maxThreadCount() * 2)), : maxBlockSize(iterationCount / (std::max(pool->maxThreadCount(), 1) * 2)),
beforeUser(0), afterUser(0), beforeUser(0), afterUser(0),
m_blockSize(1) m_blockSize(1)
{ } { }

View File

@ -117,7 +117,7 @@ class ReduceKernel
public: public:
ReduceKernel(QThreadPool *pool, ReduceOptions _reduceOptions) ReduceKernel(QThreadPool *pool, ReduceOptions _reduceOptions)
: reduceOptions(_reduceOptions), progress(0), resultsMapSize(0), : reduceOptions(_reduceOptions), progress(0), resultsMapSize(0),
threadCount(pool->maxThreadCount()) threadCount(std::max(pool->maxThreadCount(), 1))
{ } { }
void runReduce(ReduceFunctor &reduce, void runReduce(ReduceFunctor &reduce,

View File

@ -965,7 +965,7 @@ void QHostInfoLookupManager::rescheduleWithMutexHeld()
isAlreadyRunning).second, isAlreadyRunning).second,
scheduledLookups.end()); scheduledLookups.end());
const int availableThreads = threadPool.maxThreadCount() - currentLookups.size(); const int availableThreads = std::max(threadPool.maxThreadCount(), 1) - currentLookups.size();
if (availableThreads > 0) { if (availableThreads > 0) {
int readyToStartCount = qMin(availableThreads, scheduledLookups.size()); int readyToStartCount = qMin(availableThreads, scheduledLookups.size());
auto it = scheduledLookups.begin(); auto it = scheduledLookups.begin();

View File

@ -185,6 +185,17 @@ void tst_QtConcurrentMap::map()
QCOMPARE(list, NonTemplateSequence({ 2, 4, 6 })); QCOMPARE(list, NonTemplateSequence({ 2, 4, 6 }));
} }
// custom pool with invalid number of threads
{
QList<int> list;
list << 1 << 2 << 3;
QThreadPool pool;
pool.setMaxThreadCount(0); // explicitly set incorrect value
// This should not crash
QtConcurrent::map(&pool, list, MultiplyBy2InPlace()).waitForFinished();
QCOMPARE(list, QList<int>() << 2 << 4 << 6);
}
#if 0 #if 0
// not allowed: map() with immutable sequences makes no sense // not allowed: map() with immutable sequences makes no sense
{ {
@ -1075,6 +1086,17 @@ void tst_QtConcurrentMap::mappedReducedThreadPool()
intCube, intSumReduce); intCube, intSumReduce);
QCOMPARE(result, sumOfCubes); QCOMPARE(result, sumOfCubes);
} }
{
// pool with invalid number of threads
QThreadPool pool;
pool.setMaxThreadCount(0); // explicitly set incorrect value
// This should not crash
NonTemplateSequence list { 1, 2, 3 };
auto future = QtConcurrent::mappedReduced(&pool, list, multiplyBy2, intSumReduce);
QCOMPARE(future.result(), 12);
}
} }
void tst_QtConcurrentMap::mappedReducedWithMoveOnlyCallable() void tst_QtConcurrentMap::mappedReducedWithMoveOnlyCallable()