service/SearchWorkerLane.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QCoreApplication> | ||
| 4 | #include <QFutureWatcher> | ||
| 5 | #include <QPointer> | ||
| 6 | #include <QThreadPool> | ||
| 7 | #include <QtConcurrent> | ||
| 8 | #include <atomic> | ||
| 9 | #include <functional> | ||
| 10 | #include <memory> | ||
| 11 | #include <optional> | ||
| 12 | |||
| 13 | // One background lane of the search reader (Sprint 86, generalized by | ||
| 14 | // Sprint 101 S101.1.3): a single worker thread, at most one pending request | ||
| 15 | // per owner (a new request replaces the owner's previous one and interrupts | ||
| 16 | // it), a bounded queue, and delivery on the GUI thread only while the owner | ||
| 17 | // lives and the request was not cancelled. The search reader runs pages and | ||
| 18 | // summaries on two lanes so a result page never waits for a summary. | ||
| 19 | template <typename Request, typename Result> | ||
| 20 | class SearchWorkerLane { | ||
| 21 | public: | ||
| 22 | using Callback = std::function<void(Result)>; | ||
| 23 | using Executor = std::function<Result(const Request &, const std::atomic_bool &)>; | ||
| 24 | |||
| 25 | 68 | SearchWorkerLane(QObject *context, Executor executor, int maxPending) | |
| 26 |
2/2SearchWorkerLane<MailSearchRequest, MailSearchPage>::SearchWorkerLane(QObject*, std::function<MailSearchPage (MailSearchRequest const&, std::atomic<bool> const&)>, int):
✓ Branch 4 → 5 taken 34 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::SearchWorkerLane(QObject*, std::function<MailSearchSummary (MailSearchRequest const&, std::atomic<bool> const&)>, int):
✓ Branch 4 → 5 taken 34 times.
|
68 | : m_context(context), m_executor(std::move(executor)), m_maxPending(maxPending) { |
| 27 |
2/2SearchWorkerLane<MailSearchRequest, MailSearchPage>::SearchWorkerLane(QObject*, std::function<MailSearchPage (MailSearchRequest const&, std::atomic<bool> const&)>, int):
✓ Branch 7 → 8 taken 34 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::SearchWorkerLane(QObject*, std::function<MailSearchSummary (MailSearchRequest const&, std::atomic<bool> const&)>, int):
✓ Branch 7 → 8 taken 34 times.
|
68 | m_pool.setMaxThreadCount(1); |
| 28 |
2/2SearchWorkerLane<MailSearchRequest, MailSearchPage>::SearchWorkerLane(QObject*, std::function<MailSearchPage (MailSearchRequest const&, std::atomic<bool> const&)>, int):
✓ Branch 8 → 9 taken 34 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::SearchWorkerLane(QObject*, std::function<MailSearchSummary (MailSearchRequest const&, std::atomic<bool> const&)>, int):
✓ Branch 8 → 9 taken 34 times.
|
68 | m_pool.setExpiryTimeout(1000); |
| 29 | 68 | } | |
| 30 | 68 | ~SearchWorkerLane() { stop(); } | |
| 31 | SearchWorkerLane(const SearchWorkerLane &) = delete; | ||
| 32 | SearchWorkerLane &operator=(const SearchWorkerLane &) = delete; | ||
| 33 | |||
| 34 | // False when the queue is full or the lane stopped (the caller answers Busy). | ||
| 35 | 657 | bool enqueue(QObject *owner, Request request, Callback callback) { | |
| 36 |
2/2SearchWorkerLane<MailSearchRequest, MailSearchPage>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchPage)>):
✓ Branch 2 → 3 taken 380 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchSummary)>):
✓ Branch 2 → 3 taken 277 times.
|
657 | cancel(owner); |
| 37 |
10/16SearchWorkerLane<MailSearchRequest, MailSearchPage>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchPage)>):
✓ Branch 3 → 4 taken 380 times.
✗ Branch 3 → 7 not taken.
✓ Branch 4 → 5 taken 380 times.
✗ Branch 4 → 7 not taken.
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 8 taken 378 times.
✓ Branch 9 → 10 taken 2 times.
✓ Branch 9 → 11 taken 378 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchSummary)>):
✓ Branch 3 → 4 taken 277 times.
✗ Branch 3 → 7 not taken.
✓ Branch 4 → 5 taken 277 times.
✗ Branch 4 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 277 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 277 times.
|
657 | if (!owner || m_stopping || m_pending.size() >= m_maxPending) return false; |
| 38 |
6/22SearchWorkerLane<MailSearchRequest, MailSearchPage>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchPage)>):
✓ Branch 11 → 12 taken 378 times.
✓ Branch 16 → 17 taken 378 times.
✓ Branch 17 → 18 taken 378 times.
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 31 not taken.
✗ Branch 33 → 34 not taken.
✗ Branch 33 → 35 not taken.
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 38 not taken.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchSummary)>):
✓ Branch 11 → 12 taken 277 times.
✓ Branch 16 → 17 taken 277 times.
✓ Branch 17 → 18 taken 277 times.
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 31 not taken.
✗ Branch 33 → 34 not taken.
✗ Branch 33 → 35 not taken.
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 38 not taken.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
|
655 | Item item{owner, std::move(request), std::move(callback), std::make_shared<std::atomic_bool>(false), {}}; |
| 39 |
2/2SearchWorkerLane<MailSearchRequest, MailSearchPage>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchPage)>):
✓ Branch 19 → 20 taken 378 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchSummary)>):
✓ Branch 19 → 20 taken 277 times.
|
655 | item.ownerDestroyed = QObject::connect(owner, &QObject::destroyed, m_context, |
| 40 | 1338 | [cancelled = item.cancelled] { cancelled->store(true); }); | |
| 41 |
2/2SearchWorkerLane<MailSearchRequest, MailSearchPage>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchPage)>):
✓ Branch 24 → 25 taken 378 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchSummary)>):
✓ Branch 24 → 25 taken 277 times.
|
655 | m_pending.append(std::move(item)); |
| 42 |
2/2SearchWorkerLane<MailSearchRequest, MailSearchPage>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchPage)>):
✓ Branch 25 → 26 taken 378 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchSummary)>):
✓ Branch 25 → 26 taken 277 times.
|
655 | startNext(); |
| 43 | 655 | return true; | |
| 44 | 655 | } | |
| 45 | |||
| 46 | 5309 | void cancel(QObject *owner) { | |
| 47 |
12/12SearchWorkerLane<MailSearchRequest, MailSearchPage>::cancel(QObject*):
✓ Branch 3 → 4 taken 289 times.
✓ Branch 3 → 8 taken 3726 times.
✓ Branch 6 → 7 taken 146 times.
✓ Branch 6 → 8 taken 143 times.
✓ Branch 9 → 10 taken 146 times.
✓ Branch 9 → 13 taken 3869 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::cancel(QObject*):
✓ Branch 3 → 4 taken 262 times.
✓ Branch 3 → 8 taken 1032 times.
✓ Branch 6 → 7 taken 156 times.
✓ Branch 6 → 8 taken 106 times.
✓ Branch 9 → 10 taken 156 times.
✓ Branch 9 → 13 taken 1138 times.
|
5309 | if (m_active && m_active->owner == owner) m_active->cancelled->store(true); |
| 48 | 6088 | m_pending.removeIf([owner](const Item &item) { | |
| 49 |
5/6✓ Branch 3 → 4 taken 779 times.
✗ Branch 3 → 7 not taken.
✓ Branch 5 → 6 taken 712 times.
✓ Branch 5 → 7 taken 67 times.
✓ Branch 8 → 9 taken 712 times.
✓ Branch 8 → 10 taken 67 times.
|
779 | if (item.owner && item.owner != owner) return false; |
| 50 | 67 | QObject::disconnect(item.ownerDestroyed); | |
| 51 | 67 | return true; | |
| 52 | }); | ||
| 53 | 5309 | } | |
| 54 | |||
| 55 | 138 | void stop() { | |
| 56 | 138 | m_stopping = true; | |
| 57 |
6/10SearchWorkerLane<MailSearchRequest, MailSearchPage>::stop():
✓ Branch 2 → 3 taken 69 times.
✓ Branch 3 → 4 taken 69 times.
✗ Branch 6 → 7 not taken.
✗ Branch 9 → 5 not taken.
✓ Branch 9 → 10 taken 69 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::stop():
✓ Branch 2 → 3 taken 69 times.
✓ Branch 3 → 4 taken 69 times.
✗ Branch 6 → 7 not taken.
✗ Branch 9 → 5 not taken.
✓ Branch 9 → 10 taken 69 times.
|
276 | for (const auto &item : m_pending) QObject::disconnect(item.ownerDestroyed); |
| 58 | 138 | m_pending.clear(); | |
| 59 |
4/4SearchWorkerLane<MailSearchRequest, MailSearchPage>::stop():
✓ Branch 12 → 13 taken 2 times.
✓ Branch 12 → 18 taken 67 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::stop():
✓ Branch 12 → 13 taken 4 times.
✓ Branch 12 → 18 taken 65 times.
|
138 | if (m_active) { |
| 60 | 6 | QObject::disconnect(m_active->ownerDestroyed); | |
| 61 | 6 | m_active->cancelled->store(true); | |
| 62 | } | ||
| 63 |
2/2SearchWorkerLane<MailSearchRequest, MailSearchPage>::stop():
✓ Branch 19 → 20 taken 69 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::stop():
✓ Branch 19 → 20 taken 69 times.
|
138 | m_pool.waitForDone(); |
| 64 | 138 | } | |
| 65 | |||
| 66 | bool stopping() const { return m_stopping; } | ||
| 67 | 4 | int queueDepth() const { return int(m_pending.size()); } | |
| 68 |
2/2✓ Branch 3 → 4 taken 14 times.
✓ Branch 3 → 5 taken 23 times.
|
37 | int activeReads() const { return m_active ? 1 : 0; } |
| 69 | |||
| 70 | private: | ||
| 71 | struct Item { | ||
| 72 | QPointer<QObject> owner; | ||
| 73 | Request request; | ||
| 74 | Callback callback; | ||
| 75 | std::shared_ptr<std::atomic_bool> cancelled; | ||
| 76 | QMetaObject::Connection ownerDestroyed; | ||
| 77 | }; | ||
| 78 | |||
| 79 | 1240 | void startNext() { | |
| 80 |
10/12SearchWorkerLane<MailSearchRequest, MailSearchPage>::startNext():
✓ Branch 2 → 3 taken 702 times.
✗ Branch 2 → 5 not taken.
✓ Branch 4 → 5 taken 105 times.
✓ Branch 4 → 6 taken 597 times.
✓ Branch 7 → 8 taken 105 times.
✓ Branch 7 → 9 taken 597 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::startNext():
✓ Branch 2 → 3 taken 538 times.
✗ Branch 2 → 5 not taken.
✓ Branch 4 → 5 taken 70 times.
✓ Branch 4 → 6 taken 468 times.
✓ Branch 7 → 8 taken 70 times.
✓ Branch 7 → 9 taken 468 times.
|
1717 | if (m_stopping || m_active) return; |
| 81 |
10/14SearchWorkerLane<MailSearchRequest, MailSearchPage>::startNext():
✓ Branch 14 → 15 taken 325 times.
✓ Branch 14 → 19 taken 272 times.
✓ Branch 15 → 16 taken 325 times.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 325 times.
✗ Branch 20 → 10 not taken.
✓ Branch 20 → 21 taken 597 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::startNext():
✓ Branch 14 → 15 taken 263 times.
✓ Branch 14 → 19 taken 205 times.
✓ Branch 15 → 16 taken 263 times.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 263 times.
✗ Branch 20 → 10 not taken.
✓ Branch 20 → 21 taken 468 times.
|
1065 | while (!m_pending.isEmpty() && !m_pending.first().owner) { |
| 82 | ✗ | QObject::disconnect(m_pending.first().ownerDestroyed); | |
| 83 | ✗ | m_pending.removeFirst(); | |
| 84 | } | ||
| 85 |
4/4SearchWorkerLane<MailSearchRequest, MailSearchPage>::startNext():
✓ Branch 22 → 23 taken 272 times.
✓ Branch 22 → 24 taken 325 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::startNext():
✓ Branch 22 → 23 taken 205 times.
✓ Branch 22 → 24 taken 263 times.
|
1065 | if (m_pending.isEmpty()) return; |
| 86 |
2/2SearchWorkerLane<MailSearchRequest, MailSearchPage>::startNext():
✓ Branch 24 → 25 taken 325 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::startNext():
✓ Branch 24 → 25 taken 263 times.
|
588 | m_active = m_pending.takeFirst(); |
| 87 |
6/12SearchWorkerLane<MailSearchRequest, MailSearchPage>::startNext():
✓ Branch 27 → 28 taken 325 times.
✓ Branch 28 → 29 taken 325 times.
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 31 taken 325 times.
✗ Branch 52 → 53 not taken.
✗ Branch 52 → 54 not taken.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::startNext():
✓ Branch 27 → 28 taken 263 times.
✓ Branch 28 → 29 taken 263 times.
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 31 taken 263 times.
✗ Branch 52 → 53 not taken.
✗ Branch 52 → 54 not taken.
|
588 | auto *watcher = new QFutureWatcher<Result>(m_context); |
| 88 |
2/2SearchWorkerLane<MailSearchRequest, MailSearchPage>::startNext():
✓ Branch 31 → 32 taken 325 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::startNext():
✓ Branch 31 → 32 taken 263 times.
|
1173 | QObject::connect(watcher, &QFutureWatcher<Result>::finished, m_context, [this, watcher] { |
| 89 | 585 | auto item = std::move(*m_active); | |
| 90 | 585 | m_active.reset(); | |
| 91 |
1/1✓ Branch 6 → 7 taken 585 times.
|
585 | QObject::disconnect(item.ownerDestroyed); |
| 92 |
1/1✓ Branch 7 → 8 taken 585 times.
|
585 | auto result = watcher->result(); |
| 93 |
1/1✓ Branch 8 → 9 taken 585 times.
|
585 | watcher->deleteLater(); |
| 94 |
1/1✓ Branch 9 → 10 taken 585 times.
|
585 | QPointer<QObject> guard(m_context); |
| 95 |
8/9✓ Branch 10 → 11 taken 585 times.
✗ Branch 10 → 17 not taken.
✓ Branch 12 → 13 taken 559 times.
✓ Branch 12 → 17 taken 26 times.
✓ Branch 15 → 16 taken 489 times.
✓ Branch 15 → 17 taken 70 times.
✓ Branch 18 → 19 taken 489 times.
✓ Branch 18 → 24 taken 96 times.
✓ Branch 21 → 22 taken 489 times.
|
585 | if (!m_stopping && item.owner && !item.cancelled->load()) item.callback(std::move(result)); |
| 96 |
2/3✓ Branch 25 → 26 taken 585 times.
✗ Branch 25 → 27 not taken.
✓ Branch 26 → 27 taken 585 times.
|
585 | if (guard) startNext(); |
| 97 | 585 | }); | |
| 98 |
2/2SearchWorkerLane<MailSearchRequest, MailSearchPage>::startNext():
✓ Branch 34 → 35 taken 325 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::startNext():
✓ Branch 34 → 35 taken 263 times.
|
588 | const auto request = m_active->request; |
| 99 | 588 | const auto cancelled = m_active->cancelled; | |
| 100 |
2/2SearchWorkerLane<MailSearchRequest, MailSearchPage>::startNext():
✓ Branch 37 → 38 taken 325 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::startNext():
✓ Branch 37 → 38 taken 263 times.
|
588 | const auto executor = m_executor; |
| 101 |
8/16SearchWorkerLane<MailSearchRequest, MailSearchPage>::startNext():
✓ Branch 38 → 39 taken 325 times.
✓ Branch 40 → 41 taken 325 times.
✓ Branch 41 → 42 taken 325 times.
✓ Branch 42 → 43 taken 325 times.
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 63 not taken.
✗ Branch 64 → 65 not taken.
✗ Branch 64 → 66 not taken.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::startNext():
✓ Branch 38 → 39 taken 263 times.
✓ Branch 40 → 41 taken 263 times.
✓ Branch 41 → 42 taken 263 times.
✓ Branch 42 → 43 taken 263 times.
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 63 not taken.
✗ Branch 64 → 65 not taken.
✗ Branch 64 → 66 not taken.
|
1176 | watcher->setFuture(QtConcurrent::run(&m_pool, [request, cancelled, executor] { |
| 102 | 588 | return executor(request, *cancelled); | |
| 103 | })); | ||
| 104 | 588 | } | |
| 105 | |||
| 106 | QObject *m_context; | ||
| 107 | Executor m_executor; | ||
| 108 | int m_maxPending; | ||
| 109 | QThreadPool m_pool; | ||
| 110 | QList<Item> m_pending; | ||
| 111 | std::optional<Item> m_active; | ||
| 112 | bool m_stopping = false; | ||
| 113 | }; | ||
| 114 |