1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2010-04-13
7  * Description : Dynamically active thread
8  *
9  * Copyright (C) 2010-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
10  *
11  * This program is free software; you can redistribute it
12  * and/or modify it under the terms of the GNU General
13  * Public License as published by the Free Software Foundation;
14  * either version 2, or (at your option)
15  * any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * ============================================================ */
23 
24 #include "dynamicthread.h"
25 
26 // Qt includes
27 
28 #include <QMutex>
29 #include <QMutexLocker>
30 #include <QWaitCondition>
31 
32 // Local includes
33 
34 #include "digikam_debug.h"
35 #include "threadmanager.h"
36 
37 namespace Digikam
38 {
39 
40 class Q_DECL_HIDDEN DynamicThread::Private : public QRunnable
41 {
42 public:
43 
Private(DynamicThread * const q)44     explicit Private(DynamicThread* const q)
45         : q                 (q),
46           assignedThread    (nullptr),
47           running           (true),
48           emitSignals       (false),
49           inDestruction     (false),
50           threadRequested   (false),
51           state             (DynamicThread::Inactive),
52           priority          (QThread::InheritPriority),
53           previousPriority  (QThread::InheritPriority)
54     {
55         setAutoDelete(false);
56     };
57 
58     void run() override;
59 
60     void         takingThread();
61     bool         transitionToRunning();
62     void         transitionToInactive();
63 
64 public:
65 
66     DynamicThread* const          q;
67     QThread*                      assignedThread;
68 
69     volatile bool                 running;
70     volatile bool                 emitSignals;
71     volatile bool                 inDestruction;
72     volatile bool                 threadRequested;
73 
74     volatile DynamicThread::State state;
75 
76     QThread::Priority             priority;
77     QThread::Priority             previousPriority;
78 
79     QMutex                        mutex;
80     QWaitCondition                condVar;
81 };
82 
takingThread()83 void DynamicThread::Private::takingThread()
84 {
85     QMutexLocker locker(&mutex);
86 
87     // The thread we requested from the pool has now "arrived"
88 
89     threadRequested = false;
90 }
91 
transitionToRunning()92 bool DynamicThread::Private::transitionToRunning()
93 {
94     QMutexLocker locker(&mutex);
95 
96     switch (state)
97     {
98         case DynamicThread::Scheduled:
99         {
100             // ensure that a newly scheduled thread does not run
101             // while an old, deactivated one has not yet called transitionToInactive
102 
103             while (assignedThread)
104             {
105                 condVar.wait(&mutex);
106             }
107 
108             state            = DynamicThread::Running;
109             running          = true;
110             assignedThread   = QThread::currentThread();
111             previousPriority = assignedThread->priority();
112 
113             if (priority != QThread::InheritPriority)
114             {
115                 assignedThread->setPriority(priority);
116             }
117 
118             return true;
119         }
120 
121         case DynamicThread::Deactivating:
122         {
123             return false;
124         }
125 
126         case DynamicThread::Running:
127         {
128             qCDebug(DIGIKAM_GENERAL_LOG) << "Transition to Running: Invalid Running state" << q;
129             return false;
130         }
131 
132         case DynamicThread::Inactive:
133         {
134             qCDebug(DIGIKAM_GENERAL_LOG) << "Transition to Running: Invalid Inactive state" << q;
135             return false;
136         }
137 
138         default:
139         {
140             qCDebug(DIGIKAM_GENERAL_LOG) << "Transition to Running: Should never reach here: assert?" << q;
141             return false;
142         }
143     }
144 }
145 
transitionToInactive()146 void DynamicThread::Private::transitionToInactive()
147 {
148     QMutexLocker locker(&mutex);
149 
150     switch (state)
151     {
152         case DynamicThread::Scheduled:
153         case DynamicThread::Deactivating:
154         case DynamicThread::Running:
155         {
156             if (previousPriority != QThread::InheritPriority)
157             {
158                 assignedThread->setPriority(previousPriority);
159                 previousPriority = QThread::InheritPriority;
160             }
161 
162             assignedThread = nullptr;
163 
164             if (state != DynamicThread::Scheduled)
165             {
166                 state = DynamicThread::Inactive;
167             }
168 
169             condVar.wakeAll();
170             break;
171         }
172 
173         case DynamicThread::Inactive:
174         {
175             qCDebug(DIGIKAM_GENERAL_LOG) << "Transition to Inactive: Invalid Inactive state" << q;
176             break;
177         }
178     }
179 }
180 
run()181 void DynamicThread::Private::run()
182 {
183     if (emitSignals)
184     {
185         emit q->starting();
186     }
187 
188     if (transitionToRunning())
189     {
190         takingThread();
191         q->run();
192     }
193     else
194     {
195         takingThread();
196     }
197 
198     if (emitSignals)
199     {
200         emit q->finished();
201     }
202 
203     transitionToInactive();
204 
205     // as soon as we are inactive, we may get deleted!
206 }
207 
208 // -----------------------------------------------------------------------------------------------
209 
DynamicThread(QObject * const parent)210 DynamicThread::DynamicThread(QObject* const parent)
211     : QObject(parent),
212       d      (new Private(this))
213 {
214     ThreadManager::instance()->initialize(this);
215 }
216 
~DynamicThread()217 DynamicThread::~DynamicThread()
218 {
219     shutDown();
220     delete d;
221 }
222 
shutDown()223 void DynamicThread::shutDown()
224 {
225     QMutexLocker locker(&d->mutex);
226     d->running       = false;
227     d->inDestruction = true;
228     stop(locker);
229     wait(locker);
230 }
231 
state() const232 DynamicThread::State DynamicThread::state() const
233 {
234     return d->state;
235 }
236 
isRunning() const237 bool DynamicThread::isRunning() const
238 {
239     return ((d->state == Scheduled) || (d->state == Running) || (d->state == Deactivating));
240 }
241 
threadMutex() const242 QMutex* DynamicThread::threadMutex() const
243 {
244     return &d->mutex;
245 }
246 
isFinished() const247 bool DynamicThread::isFinished() const
248 {
249     return (d->state == Inactive);
250 }
251 
setEmitSignals(bool emitThem)252 void DynamicThread::setEmitSignals(bool emitThem)
253 {
254     d->emitSignals = emitThem;
255 }
256 
setPriority(QThread::Priority priority)257 void DynamicThread::setPriority(QThread::Priority priority)
258 {
259     if (d->priority == priority)
260     {
261         return;
262     }
263 
264     d->priority = priority;
265 
266     if (d->priority != QThread::InheritPriority)
267     {
268         QMutexLocker locker(&d->mutex);
269 
270         if (d->assignedThread)
271         {
272             d->assignedThread->setPriority(d->priority);
273         }
274     }
275 }
276 
priority() const277 QThread::Priority DynamicThread::priority() const
278 {
279     return d->priority;
280 }
281 
start()282 void DynamicThread::start()
283 {
284     QMutexLocker locker(&d->mutex);
285     start(locker);
286 }
287 
stop()288 void DynamicThread::stop()
289 {
290     QMutexLocker locker(&d->mutex);
291     stop(locker);
292 }
293 
wait()294 void DynamicThread::wait()
295 {
296     QMutexLocker locker(&d->mutex);
297     wait(locker);
298 }
299 
start(QMutexLocker & locker)300 void DynamicThread::start(QMutexLocker& locker)
301 {
302     if (d->inDestruction)
303     {
304         return;
305     }
306 
307     switch (d->state)
308     {
309         case Inactive:
310         case Deactivating:
311         {
312             d->state   = Scheduled;
313             break;
314         }
315 
316         case Running:
317         case Scheduled:
318         {
319             return;
320         }
321     }
322 
323     if (!d->threadRequested)
324     {
325         // avoid issuing multiple thread requests after very fast start/stop/start calls
326 
327         d->threadRequested = true;
328 
329         locker.unlock();
330         ThreadManager::instance()->schedule(d);
331         locker.relock();
332     }
333 }
334 
stop(QMutexLocker & locker)335 void DynamicThread::stop(QMutexLocker& locker)
336 {
337     Q_UNUSED(locker);
338 
339     switch (d->state)
340     {
341         case Scheduled:
342         case Running:
343         {
344             d->running = false;
345             d->state   = Deactivating;
346             break;
347         }
348 
349         case Inactive:
350         case Deactivating:
351         {
352             break;
353         }
354     }
355 }
356 
wait(QMutexLocker & locker)357 void DynamicThread::wait(QMutexLocker& locker)
358 {
359     while (d->state != Inactive)
360     {
361         d->condVar.wait(locker.mutex());
362     }
363 }
364 
runningFlag() const365 bool DynamicThread::runningFlag() const volatile
366 {
367     return d->running;
368 }
369 
370 } // namespace Digikam
371