1/*
2 * Copyright (C) 2005 Justin Karneges <justin@affinix.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301 USA
18 *
19 */
20
21#include "qca_safetimer.h"
22#include "qca_support.h"
23
24#include <QAbstractEventDispatcher>
25#include <QCoreApplication>
26#include <QElapsedTimer>
27#include <QEvent>
28#include <QMutex>
29#include <QPair>
30#include <QWaitCondition>
31
32// #define TIMERFIXER_DEBUG
33
34#ifdef TIMERFIXER_DEBUG
35#include <stdio.h>
36#endif
37
38namespace QCA {
39
40//----------------------------------------------------------------------------
41// TimerFixer
42//----------------------------------------------------------------------------
43class TimerFixer : public QObject
44{
45 Q_OBJECT
46public:
47 struct TimerInfo
48 {
49 int id;
50 int interval;
51 QElapsedTimer time;
52 bool fixInterval;
53
54 TimerInfo()
55 : fixInterval(false)
56 {
57 }
58 };
59
60 TimerFixer *fixerParent;
61 QList<TimerFixer *> fixerChildren;
62
63 QObject *target;
64 QAbstractEventDispatcher *ed;
65 QList<TimerInfo> timers;
66
67 static bool haveFixer(QObject *obj)
68 {
69 return obj->findChild<TimerFixer *>() ? true : false;
70 }
71
72 TimerFixer(QObject *_target, TimerFixer *_fp = nullptr)
73 : QObject(_target)
74 {
75 ed = nullptr;
76
77 target = _target;
78 fixerParent = _fp;
79 if (fixerParent)
80 fixerParent->fixerChildren.append(t: this);
81
82#ifdef TIMERFIXER_DEBUG
83 printf("TimerFixer[%p] pairing with %p (%s)\n", this, target, target->metaObject()->className());
84#endif
85 edlink();
86 target->installEventFilter(filterObj: this);
87
88 const QObjectList list = target->children();
89 for (int n = 0; n < list.count(); ++n)
90 hook(obj: list[n]);
91 }
92
93 ~TimerFixer() override
94 {
95 if (fixerParent)
96 fixerParent->fixerChildren.removeAll(t: this);
97
98 QList<TimerFixer *> list = fixerChildren;
99 for (int n = 0; n < list.count(); ++n)
100 delete list[n];
101 list.clear();
102
103 updateTimerList(); // do this just to trip debug output
104
105 target->removeEventFilter(obj: this);
106 edunlink();
107#ifdef TIMERFIXER_DEBUG
108 printf("TimerFixer[%p] unpaired with %p (%s)\n", this, target, target->metaObject()->className());
109#endif
110 }
111
112 bool event(QEvent *e) override
113 {
114 switch (e->type()) {
115 case QEvent::ThreadChange: // this happens second
116 // printf("TimerFixer[%p] self changing threads\n", this);
117 edunlink();
118 QMetaObject::invokeMethod(obj: this, member: "fixTimers", c: Qt::QueuedConnection);
119 break;
120 default:
121 break;
122 }
123
124 return QObject::event(event: e);
125 }
126
127 bool eventFilter(QObject *, QEvent *e) override
128 {
129 switch (e->type()) {
130 case QEvent::ChildAdded:
131 hook(obj: ((QChildEvent *)e)->child());
132 break;
133 case QEvent::ChildRemoved:
134 unhook(obj: ((QChildEvent *)e)->child());
135 break;
136 case QEvent::Timer:
137 handleTimerEvent(id: ((QTimerEvent *)e)->timerId());
138 break;
139 case QEvent::ThreadChange: // this happens first
140#ifdef TIMERFIXER_DEBUG
141 printf("TimerFixer[%p] target changing threads\n", this);
142#endif
143 break;
144 default:
145 break;
146 }
147
148 return false;
149 }
150
151private Q_SLOTS:
152 void edlink()
153 {
154 ed = QAbstractEventDispatcher::instance();
155 // printf("TimerFixer[%p] linking to dispatcher %p\n", this, ed);
156 connect(sender: ed, signal: &QAbstractEventDispatcher::aboutToBlock, context: this, slot: &TimerFixer::ed_aboutToBlock);
157 }
158
159 void edunlink()
160 {
161 // printf("TimerFixer[%p] unlinking from dispatcher %p\n", this, ed);
162 if (ed) {
163 disconnect(sender: ed, signal: &QAbstractEventDispatcher::aboutToBlock, receiver: this, slot: &TimerFixer::ed_aboutToBlock);
164 ed = nullptr;
165 }
166 }
167
168 void ed_aboutToBlock()
169 {
170 // printf("TimerFixer[%p] aboutToBlock\n", this);
171 updateTimerList();
172 }
173
174 void fixTimers()
175 {
176 updateTimerList();
177 edlink();
178
179 for (int n = 0; n < timers.count(); ++n) {
180 TimerInfo &info = timers[n];
181
182 QThread *objectThread = target->thread();
183 QAbstractEventDispatcher *ed = QAbstractEventDispatcher::instance(thread: objectThread);
184
185 const int timeLeft = qMax(a: info.interval - static_cast<int>(info.time.elapsed()), b: 0);
186 info.fixInterval = true;
187 ed->unregisterTimer(timerId: info.id);
188 info.id = ed->registerTimer(interval: timeLeft, timerType: Qt::CoarseTimer, object: target);
189
190#ifdef TIMERFIXER_DEBUG
191 printf("TimerFixer[%p] adjusting [%d] to %d\n", this, info.id, timeLeft);
192#endif
193 }
194 }
195
196private:
197 void hook(QObject *obj)
198 {
199 // don't watch a fixer or any object that already has one
200 // SafeTimer has own method to fix timers, skip it too
201 if (obj == this || qobject_cast<TimerFixer *>(object: obj) || haveFixer(obj) || qobject_cast<SafeTimer *>(object: obj))
202 return;
203
204 new TimerFixer(obj, this);
205 }
206
207 void unhook(QObject *obj)
208 {
209 TimerFixer *t = nullptr;
210 for (int n = 0; n < fixerChildren.count(); ++n) {
211 if (fixerChildren[n]->target == obj)
212 t = fixerChildren[n];
213 }
214 delete t;
215 }
216
217 void handleTimerEvent(int id)
218 {
219 bool found = false;
220 int n;
221 for (n = 0; n < timers.count(); ++n) {
222 if (timers[n].id == id) {
223 found = true;
224 break;
225 }
226 }
227 if (!found) {
228 // printf("*** unrecognized timer [%d] activated ***\n", id);
229 return;
230 }
231
232 TimerInfo &info = timers[n];
233#ifdef TIMERFIXER_DEBUG
234 printf("TimerFixer[%p] timer [%d] activated!\n", this, info.id);
235#endif
236
237 if (info.fixInterval) {
238#ifdef TIMERFIXER_DEBUG
239 printf("restoring correct interval (%d)\n", info.interval);
240#endif
241 info.fixInterval = false;
242 ed->unregisterTimer(timerId: info.id);
243 info.id = ed->registerTimer(interval: info.interval, timerType: Qt::CoarseTimer, object: target);
244 }
245
246 info.time.start();
247 }
248
249 void updateTimerList()
250 {
251 QList<QAbstractEventDispatcher::TimerInfo> edtimers;
252 if (ed)
253 edtimers = ed->registeredTimers(object: target);
254
255 // removed?
256 for (int n = 0; n < timers.count(); ++n) {
257 bool found = false;
258 int id = timers[n].id;
259 for (int i = 0; i < edtimers.count(); ++i) {
260 if (edtimers[i].timerId == id) {
261 found = true;
262 break;
263 }
264 }
265
266 if (!found) {
267 timers.removeAt(i: n);
268 --n;
269#ifdef TIMERFIXER_DEBUG
270 printf("TimerFixer[%p] timer [%d] removed\n", this, id);
271#endif
272 }
273 }
274
275 // added?
276 for (int n = 0; n < edtimers.count(); ++n) {
277 int id = edtimers[n].timerId;
278 bool found = false;
279 for (int i = 0; i < timers.count(); ++i) {
280 if (timers[i].id == id) {
281 found = true;
282 break;
283 }
284 }
285
286 if (!found) {
287 TimerInfo info;
288 info.id = id;
289 info.interval = edtimers[n].interval;
290 info.time.start();
291 timers += info;
292#ifdef TIMERFIXER_DEBUG
293 printf("TimerFixer[%p] timer [%d] added (interval=%d)\n", this, info.id, info.interval);
294#endif
295 }
296 }
297 }
298};
299
300//----------------------------------------------------------------------------
301// Synchronizer
302//----------------------------------------------------------------------------
303class SynchronizerAgent : public QObject
304{
305 Q_OBJECT
306public:
307 SynchronizerAgent(QObject *parent = nullptr)
308 : QObject(parent)
309 {
310 QMetaObject::invokeMethod(obj: this, member: "started", c: Qt::QueuedConnection);
311 }
312
313Q_SIGNALS:
314 void started();
315};
316
317class Synchronizer::Private : public QThread
318{
319 Q_OBJECT
320public:
321 Synchronizer *q;
322
323 bool active;
324 bool do_quit;
325 bool cond_met;
326
327 QObject *obj;
328 QEventLoop *loop;
329 SynchronizerAgent *agent;
330 TimerFixer *fixer;
331 QMutex m;
332 QWaitCondition w;
333 QThread *orig_thread;
334
335 Private(QObject *_obj, Synchronizer *_q)
336 : QThread(_q)
337 , q(_q)
338 , active(false)
339 , do_quit(false)
340 , cond_met(false)
341 , obj(_obj)
342 , loop(nullptr)
343 , agent(nullptr)
344 , fixer(nullptr)
345 , m()
346 , w()
347 , orig_thread(nullptr)
348 {
349 // SafeTimer has own method to fix timers, skip it too
350 if (!qobject_cast<SafeTimer *>(object: obj))
351 fixer = new TimerFixer(obj);
352 }
353
354 ~Private() override
355 {
356 stop();
357 delete fixer;
358 }
359
360 void start()
361 {
362 if (active)
363 return;
364
365 m.lock();
366 active = true;
367 do_quit = false;
368 QThread::start();
369 w.wait(lockedMutex: &m);
370 m.unlock();
371 }
372
373 void stop()
374 {
375 if (!active)
376 return;
377
378 m.lock();
379 do_quit = true;
380 w.wakeOne();
381 m.unlock();
382 wait();
383 active = false;
384 }
385
386 bool waitForCondition(int msecs)
387 {
388 unsigned long time = ULONG_MAX;
389 if (msecs != -1)
390 time = msecs;
391
392 // move object to the worker thread
393 cond_met = false;
394 orig_thread = QThread::currentThread();
395 q->setParent(nullptr); // don't follow the object
396 QObject *orig_parent = obj->parent();
397 obj->setParent(nullptr); // unparent the target or the move will fail
398 obj->moveToThread(thread: this);
399
400 // tell the worker thread to start, wait for completion
401 m.lock();
402 w.wakeOne();
403 if (!w.wait(lockedMutex: &m, time)) {
404 if (loop) {
405 // if we timed out, tell the worker to quit
406 QMetaObject::invokeMethod(obj: loop, member: "quit");
407 w.wait(lockedMutex: &m);
408 }
409 }
410
411 // at this point the worker is done. cleanup and return
412 m.unlock();
413
414 // restore parents
415 obj->setParent(orig_parent);
416 q->setParent(obj);
417
418 return cond_met;
419 }
420
421 void conditionMet()
422 {
423 if (!loop)
424 return;
425 loop->quit();
426 cond_met = true;
427 }
428
429protected:
430 void run() override
431 {
432 m.lock();
433 QEventLoop eventLoop;
434
435 while (true) {
436 // thread now sleeps, waiting for work
437 w.wakeOne();
438 w.wait(lockedMutex: &m);
439 if (do_quit) {
440 m.unlock();
441 break;
442 }
443
444 loop = &eventLoop;
445 agent = new SynchronizerAgent;
446 connect(sender: agent, signal: &SynchronizerAgent::started, context: this, slot: &Private::agent_started, type: Qt::DirectConnection);
447
448 // run the event loop
449 eventLoop.exec();
450
451 delete agent;
452 agent = nullptr;
453
454 // eventloop done, flush pending events
455 QCoreApplication::instance()->sendPostedEvents();
456 QCoreApplication::instance()->sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
457
458 // and move the object back
459 obj->moveToThread(thread: orig_thread);
460
461 m.lock();
462 loop = nullptr;
463 w.wakeOne();
464 }
465 }
466
467private Q_SLOTS:
468 void agent_started()
469 {
470 m.unlock();
471 }
472};
473
474Synchronizer::Synchronizer(QObject *parent)
475 : QObject(parent)
476{
477 d = new Private(parent, this);
478}
479
480Synchronizer::~Synchronizer()
481{
482 delete d;
483}
484
485bool Synchronizer::waitForCondition(int msecs)
486{
487 d->start();
488 return d->waitForCondition(msecs);
489}
490
491void Synchronizer::conditionMet()
492{
493 d->conditionMet();
494}
495
496}
497
498#include "synchronizer.moc"
499

source code of qca/src/support/synchronizer.cpp