Skip to content

Commit b09028a

Browse files
committed
Thread/Scheduler: Fix task ordering and synchronization
1 parent f971d9f commit b09028a

1 file changed

Lines changed: 66 additions & 54 deletions

File tree

modules/Thread/Scheduler.mpp

Lines changed: 66 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export module CppUtils.Thread.Scheduler;
22

33
import std;
44
import CppUtils.Chrono.Concept;
5+
import CppUtils.Execution.ScopeGuard;
56
import CppUtils.Thread.UniqueLocker;
67
import CppUtils.Thread.SharedLocker;
78
import CppUtils.Thread.ThreadLoop;
@@ -31,17 +32,20 @@ export namespace CppUtils::Thread
3132
inline auto operator()(const std::shared_ptr<Item>& a,
3233
const std::shared_ptr<Item>& b) const noexcept -> bool
3334
{
34-
return a->time < b->time;
35+
if (a->time != b->time)
36+
return a->time < b->time;
37+
return a->id < b->id;
3538
}
3639
};
3740

3841
struct Items final
3942
{
40-
using Set = std::multiset<std::shared_ptr<Item>, Compare>;
43+
using Set = std::set<std::shared_ptr<Item>, Compare>;
4144
using Map = std::unordered_map<std::size_t, std::shared_ptr<Item>>;
4245

4346
Set set;
4447
Map map;
48+
std::size_t processingTasks = 0;
4549
};
4650

4751
public:
@@ -52,37 +56,43 @@ export namespace CppUtils::Thread
5256
std::function<void()> finally = nullptr):
5357
m_step{step},
5458
m_finally{std::move(finally)},
55-
m_threadLoop{
56-
[this] { runLoop(); },
57-
[this] { m_condition.notify_all(); },
58-
onError},
5959
m_threadPool{
6060
numberThreads,
6161
onError,
6262
[this] {
63+
auto _ = Execution::ScopeGuard{[&] {
64+
auto accessor = m_items.access();
65+
if (--accessor.value().processingTasks == 0)
66+
m_finishedCondition.notify_all();
67+
}};
6368
if (m_finally)
6469
m_finally();
65-
if (--m_processingTasks == 0)
66-
m_condition.notify_all();
67-
}}
70+
}},
71+
m_threadLoop{
72+
[this] { runLoop(); },
73+
[this] {
74+
m_workCondition.notify_all();
75+
m_finishedCondition.notify_all();
76+
},
77+
onError}
6878
{
6979
m_threadLoop.start();
7080
}
7181

7282
inline ~Scheduler() noexcept
7383
{
74-
cancelAll();
7584
m_threadLoop.requestStop();
85+
cancelAll();
7686
}
7787

7888
inline auto schedule(Task task, TimePoint when) -> std::size_t
7989
{
8090
auto item = std::make_shared<Item>(m_nextId.fetch_add(1, std::memory_order_relaxed), when, std::move(task));
8191
auto accessor = m_items.access();
82-
auto& [set, map] = accessor.value();
92+
auto& [set, map, processingTasks] = accessor.value();
8393
set.insert(item);
8494
map.emplace(item->id, item);
85-
m_condition.notify_one();
95+
m_workCondition.notify_all();
8696
return item->id;
8797
}
8898

@@ -94,101 +104,103 @@ export namespace CppUtils::Thread
94104
inline auto waitUntilFinished() -> void
95105
{
96106
auto accessor = m_items.access();
97-
m_condition.wait(accessor.getLockGuard(), [&] {
98-
return std::empty(accessor.value().set) and m_processingTasks == 0;
107+
m_finishedCondition.wait(accessor.getLockGuard(), [&] {
108+
return std::empty(accessor.value().set) and accessor.value().processingTasks == 0;
99109
});
100110
}
101111

102112
inline auto cancel(std::size_t id) -> void
103113
{
104114
auto accessor = m_items.access();
105-
auto& [set, map] = accessor.value();
115+
auto& [set, map, processingTasks] = accessor.value();
106116
if (auto it = map.find(id); it != std::end(map))
107117
{
108118
it->second->cancelled = true;
119+
set.erase(it->second);
109120
map.erase(it);
121+
m_workCondition.notify_all();
122+
m_finishedCondition.notify_all();
110123
}
111124
}
112125

113126
inline auto cancelAll() -> void
114127
{
115128
auto accessor = m_items.access();
116-
auto& [set, map] = accessor.value();
129+
auto& [set, map, processingTasks] = accessor.value();
117130
for (auto& [_, item] : map)
118131
item->cancelled = true;
119132
map.clear();
120133
set.clear();
121-
m_condition.notify_all();
134+
m_workCondition.notify_all();
135+
m_finishedCondition.notify_all();
122136
}
123137

124138
private:
125139
inline auto runLoop() -> void
126140
{
127-
auto tasksToRun = std::vector<Task>{};
141+
auto tasksToRun = std::vector<std::shared_ptr<Item>>{};
128142
{
129143
auto accessor = m_items.access();
130-
auto& [set, map] = accessor.value();
144+
auto& [set, map, processingTasks] = accessor.value();
131145

132146
if (std::empty(set))
133-
{
134-
m_condition.wait(accessor.getLockGuard(), [this, &accessor] {
135-
return not std::empty(accessor.value().set) or m_threadLoop.isStopRequested();
147+
m_workCondition.wait(accessor.getLockGuard(), [this, &set] {
148+
return not std::empty(set) or m_threadLoop.isStopRequested();
136149
});
137-
if (m_threadLoop.isStopRequested())
138-
return;
139-
if (std::empty(set))
140-
{
141-
m_condition.notify_all();
142-
return;
143-
}
144-
}
145-
146-
auto first = *std::begin(set);
147-
148-
if (first->time > Clock::now())
149-
{
150-
m_condition.wait_until(accessor.getLockGuard(), first->time, [&, first] {
151-
return m_threadLoop.isStopRequested() or first->cancelled or std::empty(set) or (*std::begin(set) != first) or first->time <= Clock::now();
150+
else if (auto first = *std::begin(set); first->time > Clock::now())
151+
m_workCondition.wait_until(accessor.getLockGuard(), first->time, [this, &set, first] {
152+
return m_threadLoop.isStopRequested() or std::empty(set) or (*std::begin(set) != first) or ((*std::begin(set))->time <= Clock::now());
152153
});
154+
155+
if (m_threadLoop.isStopRequested() or std::empty(set) or (*std::begin(set))->time > Clock::now())
153156
return;
154-
}
155157

158+
auto first = *std::begin(set);
156159
const auto endTime = (m_step == Clock::duration::zero()) ? first->time : first->time + m_step;
157160

158161
for (auto it = std::begin(set); it != std::end(set);)
159-
{
160162
if ((*it)->time <= endTime)
161163
{
162-
if (not(*it)->cancelled)
163-
tasksToRun.push_back(std::move((*it)->task));
164+
tasksToRun.push_back(*it);
164165
map.erase((*it)->id);
165166
it = set.erase(it);
167+
++accessor.value().processingTasks;
166168
}
167169
else
168-
++it;
169-
}
170-
}
170+
break;
171171

172-
if (std::empty(tasksToRun))
173-
return;
172+
if (std::empty(set))
173+
m_finishedCondition.notify_all();
174+
}
174175

175-
m_processingTasks += std::size(tasksToRun);
176-
for (auto&& task : tasksToRun)
177-
m_threadPool.call([task = std::move(task)] {
178-
std::invoke(task);
179-
});
176+
for (auto&& item : tasksToRun)
177+
{
178+
try
179+
{
180+
m_threadPool.call([item] {
181+
if (not item->cancelled)
182+
std::invoke(item->task);
183+
});
184+
}
185+
catch (...)
186+
{
187+
auto accessor = m_items.access();
188+
if (--accessor.value().processingTasks == 0)
189+
m_finishedCondition.notify_all();
190+
}
191+
}
180192
}
181193

182194
private:
183195
std::atomic_size_t m_nextId = 0;
184196
Clock::duration m_step;
185197
UniqueLocker<Items> m_items;
186-
std::atomic_size_t m_processingTasks = 0;
187198

188199
std::function<void()> m_finally;
189200

190-
std::condition_variable m_condition;
191-
ThreadLoop m_threadLoop;
201+
std::condition_variable m_workCondition;
202+
std::condition_variable m_finishedCondition;
192203
ThreadPool m_threadPool;
204+
ThreadLoop m_threadLoop;
193205
};
194206
}

0 commit comments

Comments
 (0)