[48270] | 1 | /* $Id: UIThreadPool.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
|
---|
| 2 | /** @file
|
---|
[82998] | 3 | * VBox Qt GUI - UIThreadPool class implementation.
|
---|
[48270] | 4 | */
|
---|
| 5 |
|
---|
| 6 | /*
|
---|
[98103] | 7 | * Copyright (C) 2013-2023 Oracle and/or its affiliates.
|
---|
[48270] | 8 | *
|
---|
[96407] | 9 | * This file is part of VirtualBox base platform packages, as
|
---|
| 10 | * available from https://www.virtualbox.org.
|
---|
| 11 | *
|
---|
| 12 | * This program is free software; you can redistribute it and/or
|
---|
| 13 | * modify it under the terms of the GNU General Public License
|
---|
| 14 | * as published by the Free Software Foundation, in version 3 of the
|
---|
| 15 | * License.
|
---|
| 16 | *
|
---|
| 17 | * This program is distributed in the hope that it will be useful, but
|
---|
| 18 | * WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
| 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
---|
| 20 | * General Public License for more details.
|
---|
| 21 | *
|
---|
| 22 | * You should have received a copy of the GNU General Public License
|
---|
| 23 | * along with this program; if not, see <https://www.gnu.org/licenses>.
|
---|
| 24 | *
|
---|
| 25 | * SPDX-License-Identifier: GPL-3.0-only
|
---|
[48270] | 26 | */
|
---|
| 27 |
|
---|
| 28 | /* Qt includes: */
|
---|
[76606] | 29 | #include <QThread>
|
---|
[48270] | 30 |
|
---|
| 31 | /* GUI includes: */
|
---|
[76606] | 32 | #include "COMDefs.h"
|
---|
| 33 | #include "UIDefs.h"
|
---|
[82998] | 34 | #include "UITask.h"
|
---|
[76606] | 35 | #include "UIThreadPool.h"
|
---|
[48270] | 36 |
|
---|
[54554] | 37 | /* Other VBox includes: */
|
---|
| 38 | #include <iprt/assert.h>
|
---|
| 39 |
|
---|
[52730] | 40 |
|
---|
[57626] | 41 | /** QThread extension used as worker-thread.
|
---|
| 42 | * Capable of executing COM-related tasks. */
|
---|
[48270] | 43 | class UIThreadWorker : public QThread
|
---|
| 44 | {
|
---|
| 45 | Q_OBJECT;
|
---|
| 46 |
|
---|
| 47 | signals:
|
---|
| 48 |
|
---|
[57626] | 49 | /** Notifies listeners about @a pWorker finished. */
|
---|
[48270] | 50 | void sigFinished(UIThreadWorker *pWorker);
|
---|
| 51 |
|
---|
| 52 | public:
|
---|
| 53 |
|
---|
[57626] | 54 | /** Constructs worker-thread for parent worker-thread @a pPool.
|
---|
[71526] | 55 | * @param iIndex Brings worker-thread index within the worker-thread pool registry. */
|
---|
[48270] | 56 | UIThreadWorker(UIThreadPool *pPool, int iIndex);
|
---|
| 57 |
|
---|
[57626] | 58 | /** Returns worker-thread index within the worker-thread pool registry. */
|
---|
[57631] | 59 | int index() const { return m_iIndex; }
|
---|
[48270] | 60 |
|
---|
[57626] | 61 | /** Disables sigFinished signal, for optimizing worker-thread pool termination. */
|
---|
[57631] | 62 | void setNoFinishedSignal() { m_fNoFinishedSignal = true; }
|
---|
[48908] | 63 |
|
---|
[48270] | 64 | private:
|
---|
| 65 |
|
---|
[57626] | 66 | /** Contains the worker-thread body. */
|
---|
[48270] | 67 | void run();
|
---|
| 68 |
|
---|
[57626] | 69 | /** Holds the worker-thread pool reference. */
|
---|
[48270] | 70 | UIThreadPool *m_pPool;
|
---|
[48908] | 71 |
|
---|
[57626] | 72 | /** Holds the worker-thread index within the worker-thread pool registry. */
|
---|
[82998] | 73 | int m_iIndex;
|
---|
[57626] | 74 |
|
---|
| 75 | /** Holds whether sigFinished signal should be emitted or not. */
|
---|
[82998] | 76 | bool m_fNoFinishedSignal;
|
---|
[48270] | 77 | };
|
---|
| 78 |
|
---|
[71526] | 79 |
|
---|
| 80 | /*********************************************************************************************************************************
|
---|
| 81 | * Class UIThreadPool implementation. *
|
---|
| 82 | *********************************************************************************************************************************/
|
---|
| 83 |
|
---|
[48911] | 84 | UIThreadPool::UIThreadPool(ulong cMaxWorkers /* = 3 */, ulong cMsWorkerIdleTimeout /* = 5000 */)
|
---|
[57618] | 85 | : m_cMsIdleTimeout(cMsWorkerIdleTimeout)
|
---|
| 86 | , m_workers(cMaxWorkers)
|
---|
[48908] | 87 | , m_cWorkers(0)
|
---|
| 88 | , m_cIdleWorkers(0)
|
---|
[57631] | 89 | , m_fTerminating(false)
|
---|
[48270] | 90 | {
|
---|
| 91 | }
|
---|
| 92 |
|
---|
| 93 | UIThreadPool::~UIThreadPool()
|
---|
| 94 | {
|
---|
[57631] | 95 | /* Set termination status: */
|
---|
[48908] | 96 | setTerminating();
|
---|
[48270] | 97 |
|
---|
[57631] | 98 | /* Lock initially: */
|
---|
| 99 | m_everythingLocker.lock();
|
---|
[48911] | 100 |
|
---|
[48270] | 101 | /* Cleanup all the workers: */
|
---|
[48908] | 102 | for (int idxWorker = 0; idxWorker < m_workers.size(); ++idxWorker)
|
---|
| 103 | {
|
---|
[57631] | 104 | /* Acquire the worker: */
|
---|
| 105 | UIThreadWorker *pWorker = m_workers.at(idxWorker);
|
---|
| 106 | /* Remove it from the registry: */
|
---|
| 107 | m_workers[idxWorker] = 0;
|
---|
[48908] | 108 |
|
---|
[57631] | 109 | /* Clean up the worker, if there was one: */
|
---|
[48908] | 110 | if (pWorker)
|
---|
| 111 | {
|
---|
[57631] | 112 | /* Decrease the number of workers: */
|
---|
| 113 | --m_cWorkers;
|
---|
| 114 | /* Unlock temporary to let the worker finish: */
|
---|
[48908] | 115 | m_everythingLocker.unlock();
|
---|
[57631] | 116 | /* Wait for the worker to finish: */
|
---|
[48908] | 117 | pWorker->wait();
|
---|
[57631] | 118 | /* Lock again: */
|
---|
[48908] | 119 | m_everythingLocker.lock();
|
---|
[57631] | 120 | /* Delete the worker finally: */
|
---|
[48908] | 121 | delete pWorker;
|
---|
| 122 | }
|
---|
| 123 | }
|
---|
| 124 |
|
---|
[57667] | 125 | /* Cleanup all the tasks: */
|
---|
| 126 | qDeleteAll(m_pendingTasks);
|
---|
| 127 | qDeleteAll(m_executingTasks);
|
---|
| 128 | m_pendingTasks.clear();
|
---|
| 129 | m_executingTasks.clear();
|
---|
| 130 |
|
---|
[57631] | 131 | /* Unlock finally: */
|
---|
[48908] | 132 | m_everythingLocker.unlock();
|
---|
[48270] | 133 | }
|
---|
| 134 |
|
---|
[57629] | 135 | bool UIThreadPool::isTerminating() const
|
---|
| 136 | {
|
---|
[57631] | 137 | /* Lock initially: */
|
---|
| 138 | m_everythingLocker.lock();
|
---|
| 139 |
|
---|
[57629] | 140 | /* Acquire termination-flag: */
|
---|
| 141 | bool fTerminating = m_fTerminating;
|
---|
[57631] | 142 |
|
---|
| 143 | /* Unlock finally: */
|
---|
[57629] | 144 | m_everythingLocker.unlock();
|
---|
| 145 |
|
---|
[57631] | 146 | /* Return termination-flag: */
|
---|
[57629] | 147 | return fTerminating;
|
---|
| 148 | }
|
---|
| 149 |
|
---|
| 150 | void UIThreadPool::setTerminating()
|
---|
| 151 | {
|
---|
[57631] | 152 | /* Lock initially: */
|
---|
[57629] | 153 | m_everythingLocker.lock();
|
---|
| 154 |
|
---|
[57631] | 155 | /* Assign termination-flag: */
|
---|
[57629] | 156 | m_fTerminating = true;
|
---|
| 157 |
|
---|
| 158 | /* Tell all threads to NOT queue any termination signals: */
|
---|
| 159 | for (int idxWorker = 0; idxWorker < m_workers.size(); ++idxWorker)
|
---|
| 160 | {
|
---|
[57631] | 161 | UIThreadWorker *pWorker = m_workers.at(idxWorker);
|
---|
[57629] | 162 | if (pWorker)
|
---|
| 163 | pWorker->setNoFinishedSignal();
|
---|
| 164 | }
|
---|
| 165 |
|
---|
| 166 | /* Wake up all idle worker threads: */
|
---|
| 167 | m_taskCondition.wakeAll();
|
---|
| 168 |
|
---|
[57631] | 169 | /* Unlock finally: */
|
---|
[57629] | 170 | m_everythingLocker.unlock();
|
---|
| 171 | }
|
---|
| 172 |
|
---|
[48270] | 173 | void UIThreadPool::enqueueTask(UITask *pTask)
|
---|
| 174 | {
|
---|
[57631] | 175 | /* Do nothing if terminating: */
|
---|
| 176 | AssertReturnVoid(!isTerminating());
|
---|
[48908] | 177 |
|
---|
[48270] | 178 | /* Prepare task: */
|
---|
[68190] | 179 | connect(pTask, &UITask::sigComplete,
|
---|
| 180 | this, &UIThreadPool::sltHandleTaskComplete, Qt::QueuedConnection);
|
---|
[48270] | 181 |
|
---|
[57631] | 182 | /* Lock initially: */
|
---|
[48908] | 183 | m_everythingLocker.lock();
|
---|
| 184 |
|
---|
[57631] | 185 | /* Put the task into the queue: */
|
---|
[57667] | 186 | m_pendingTasks.enqueue(pTask);
|
---|
[48270] | 187 |
|
---|
[48908] | 188 | /* Wake up an idle worker if we got one: */
|
---|
| 189 | if (m_cIdleWorkers > 0)
|
---|
[48270] | 190 | {
|
---|
| 191 | m_taskCondition.wakeOne();
|
---|
| 192 | }
|
---|
[48908] | 193 | /* No idle worker threads, should we create a new one? */
|
---|
| 194 | else if (m_cWorkers < m_workers.size())
|
---|
| 195 | {
|
---|
[48911] | 196 | /* Find free slot: */
|
---|
[48908] | 197 | int idxFirstUnused = m_workers.size();
|
---|
| 198 | while (idxFirstUnused-- > 0)
|
---|
[57631] | 199 | if (m_workers.at(idxFirstUnused) == 0)
|
---|
[48908] | 200 | {
|
---|
| 201 | /* Prepare the new worker: */
|
---|
| 202 | UIThreadWorker *pWorker = new UIThreadWorker(this, idxFirstUnused);
|
---|
[68190] | 203 | connect(pWorker, &UIThreadWorker::sigFinished,
|
---|
| 204 | this, &UIThreadPool::sltHandleWorkerFinished, Qt::QueuedConnection);
|
---|
[48908] | 205 | m_workers[idxFirstUnused] = pWorker;
|
---|
[57631] | 206 | ++m_cWorkers;
|
---|
[48270] | 207 |
|
---|
[48908] | 208 | /* And start it: */
|
---|
| 209 | pWorker->start();
|
---|
| 210 | break;
|
---|
| 211 | }
|
---|
[48270] | 212 | }
|
---|
[57631] | 213 | /* else: wait for some worker to complete
|
---|
| 214 | * whatever it's busy with and jump to it. */
|
---|
[48908] | 215 |
|
---|
[57631] | 216 | /* Unlock finally: */
|
---|
[48908] | 217 | m_everythingLocker.unlock();
|
---|
[48270] | 218 | }
|
---|
| 219 |
|
---|
[71526] | 220 | UITask *UIThreadPool::dequeueTask(UIThreadWorker *pWorker)
|
---|
[48270] | 221 | {
|
---|
[57631] | 222 | /* Lock initially: */
|
---|
[48908] | 223 | m_everythingLocker.lock();
|
---|
[48270] | 224 |
|
---|
[57631] | 225 | /* Dequeue a task, watching out for terminations.
|
---|
| 226 | * For optimal efficiency in enqueueTask() we keep count of idle threads.
|
---|
| 227 | * If the wait times out, we'll return 0 and terminate the thread. */
|
---|
[48908] | 228 | bool fIdleTimedOut = false;
|
---|
[48910] | 229 | while (!m_fTerminating)
|
---|
[48270] | 230 | {
|
---|
[57631] | 231 | /* Make sure that worker has proper index: */
|
---|
| 232 | Assert(m_workers.at(pWorker->index()) == pWorker);
|
---|
[48270] | 233 |
|
---|
[48908] | 234 | /* Dequeue task if there is one: */
|
---|
[57667] | 235 | if (!m_pendingTasks.isEmpty())
|
---|
[48908] | 236 | {
|
---|
[57667] | 237 | UITask *pTask = m_pendingTasks.dequeue();
|
---|
[48908] | 238 | if (pTask)
|
---|
| 239 | {
|
---|
[57667] | 240 | /* Put into the set of executing tasks: */
|
---|
| 241 | m_executingTasks << pTask;
|
---|
| 242 |
|
---|
[57631] | 243 | /* Unlock finally: */
|
---|
[48908] | 244 | m_everythingLocker.unlock();
|
---|
[57631] | 245 |
|
---|
| 246 | /* Return dequeued task: */
|
---|
[48908] | 247 | return pTask;
|
---|
| 248 | }
|
---|
| 249 | }
|
---|
[48270] | 250 |
|
---|
[48911] | 251 | /* If we timed out already, then quit the worker thread. To prevent a
|
---|
[57631] | 252 | * race between enqueueTask and the queue removal of the thread from
|
---|
| 253 | * the workers vector, we remove it here already. (This does not apply
|
---|
| 254 | * to the termination scenario.) */
|
---|
[48908] | 255 | if (fIdleTimedOut)
|
---|
| 256 | {
|
---|
[57631] | 257 | m_workers[pWorker->index()] = 0;
|
---|
| 258 | --m_cWorkers;
|
---|
[48908] | 259 | break;
|
---|
| 260 | }
|
---|
[48270] | 261 |
|
---|
[57631] | 262 | /* Wait for a task or timeout: */
|
---|
| 263 | ++m_cIdleWorkers;
|
---|
[48908] | 264 | fIdleTimedOut = !m_taskCondition.wait(&m_everythingLocker, m_cMsIdleTimeout);
|
---|
[57631] | 265 | --m_cIdleWorkers;
|
---|
[48270] | 266 | }
|
---|
| 267 |
|
---|
[57631] | 268 | /* Unlock finally: */
|
---|
[48908] | 269 | m_everythingLocker.unlock();
|
---|
[48270] | 270 |
|
---|
[57631] | 271 | /* Return 0 by default: */
|
---|
| 272 | return 0;
|
---|
[48270] | 273 | }
|
---|
| 274 |
|
---|
| 275 | void UIThreadPool::sltHandleTaskComplete(UITask *pTask)
|
---|
| 276 | {
|
---|
| 277 | /* Skip on termination: */
|
---|
| 278 | if (isTerminating())
|
---|
| 279 | return;
|
---|
| 280 |
|
---|
[57667] | 281 | /* Notify listeners: */
|
---|
[48270] | 282 | emit sigTaskComplete(pTask);
|
---|
[57667] | 283 |
|
---|
| 284 | /* Lock initially: */
|
---|
| 285 | m_everythingLocker.lock();
|
---|
| 286 |
|
---|
| 287 | /* Delete task finally: */
|
---|
[57693] | 288 | if ( !m_executingTasks.contains(pTask)
|
---|
| 289 | || !m_executingTasks.remove(pTask))
|
---|
| 290 | AssertMsgFailed(("Unable to find or remove complete task!"));
|
---|
[57667] | 291 | delete pTask;
|
---|
| 292 |
|
---|
| 293 | /* Unlock finally: */
|
---|
| 294 | m_everythingLocker.unlock();
|
---|
[48270] | 295 | }
|
---|
| 296 |
|
---|
| 297 | void UIThreadPool::sltHandleWorkerFinished(UIThreadWorker *pWorker)
|
---|
| 298 | {
|
---|
[48908] | 299 | /* Wait for the thread to finish completely, then delete the thread
|
---|
[57631] | 300 | * object. We have already removed the thread from the workers vector.
|
---|
| 301 | * Note! We don't want to use 'this' here, in case it's invalid. */
|
---|
[48908] | 302 | pWorker->wait();
|
---|
| 303 | delete pWorker;
|
---|
[48270] | 304 | }
|
---|
| 305 |
|
---|
| 306 |
|
---|
[71526] | 307 | /*********************************************************************************************************************************
|
---|
| 308 | * Class UIThreadWorker implementation. *
|
---|
| 309 | *********************************************************************************************************************************/
|
---|
| 310 |
|
---|
[48270] | 311 | UIThreadWorker::UIThreadWorker(UIThreadPool *pPool, int iIndex)
|
---|
| 312 | : m_pPool(pPool)
|
---|
| 313 | , m_iIndex(iIndex)
|
---|
[48908] | 314 | , m_fNoFinishedSignal(false)
|
---|
[48270] | 315 | {
|
---|
| 316 | }
|
---|
| 317 |
|
---|
| 318 | void UIThreadWorker::run()
|
---|
| 319 | {
|
---|
| 320 | /* Initialize COM: */
|
---|
| 321 | COMBase::InitializeCOM(false);
|
---|
| 322 |
|
---|
[57631] | 323 | /* Try get a task from the pool queue: */
|
---|
[48270] | 324 | while (UITask *pTask = m_pPool->dequeueTask(this))
|
---|
| 325 | {
|
---|
[48911] | 326 | /* Process the task if we are not terminating.
|
---|
[57667] | 327 | * Please take into account tasks are cleared by the UIThreadPool
|
---|
| 328 | * after all listeners notified about task is complete and handled it. */
|
---|
[48270] | 329 | if (!m_pPool->isTerminating())
|
---|
| 330 | pTask->start();
|
---|
| 331 | }
|
---|
| 332 |
|
---|
| 333 | /* Cleanup COM: */
|
---|
| 334 | COMBase::CleanupCOM();
|
---|
| 335 |
|
---|
[57631] | 336 | /* Queue a signal for the pool to do thread cleanup, unless the pool is
|
---|
[48908] | 337 | already terminating and doesn't need the signal. */
|
---|
| 338 | if (!m_fNoFinishedSignal)
|
---|
| 339 | emit sigFinished(this);
|
---|
[48270] | 340 | }
|
---|
| 341 |
|
---|
[71526] | 342 |
|
---|
[48270] | 343 | #include "UIThreadPool.moc"
|
---|