Skip to content

Commit 84da938

Browse files
Address SonarCloud PR 193 issues: code fixes and scoped suppressions
Fix the two BUG-severity destructor-throw findings (S1048/M23_201) by marking Group::release_token, drain_one_to_pool, and notify_slow_path noexcept. Apply stylistic refactors: remove continue/break from lock-free retry loops in TaskQueue/MPSCQueue and Group; make Pool::drain_queues const; rename Binder ctor param to avoid shadowing; drop redundant BLOCK_SIZE casts. Add C++14 odr-definitions for template static constexpr BLOCK_SIZE members used from Pool.cpp/Group.cpp instantiations. Add sonar-project.properties to scope-ignore S8417 (memory_order) under src/threading/** and src/extension/**, and S5025/S3630/S3432 (placement-new queue idioms) under **/scheduler/queue/*.hpp. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 105cefe commit 84da938

8 files changed

Lines changed: 103 additions & 64 deletions

File tree

sonar-project.properties

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# SonarCloud issue suppressions for deliberate lock-free / placement-new code.
2+
# projectKey, organization, sources, tests and coverage settings are passed on
3+
# the scanner CLI in .github/workflows/sonarcloud.yaml; only the ignore rules
4+
# below are configured here.
5+
6+
sonar.issue.ignore.multicriteria=e1,e2,e3,e4,e5
7+
8+
# S8417: explicit memory_order arguments are intentional in this concurrency
9+
# framework; the carefully chosen relaxed/acquire/release/acq_rel orderings are
10+
# required for performance and must not be forced to seq_cst.
11+
sonar.issue.ignore.multicriteria.e1.ruleKey=cpp:S8417
12+
sonar.issue.ignore.multicriteria.e1.resourceKey=src/threading/**
13+
sonar.issue.ignore.multicriteria.e2.ruleKey=cpp:S8417
14+
sonar.issue.ignore.multicriteria.e2.resourceKey=src/extension/**
15+
16+
# S5025 (manual new/delete), S3630 (reinterpret_cast) and S3432 (explicit
17+
# destructor call) are unavoidable in the lock-free queues: manual Block
18+
# lifetime is dictated by the graveyard reclamation scheme and the
19+
# reinterpret_cast + explicit ~T() are the placement-new idiom for the aligned
20+
# slot storage. Scope these to the queue files only.
21+
sonar.issue.ignore.multicriteria.e3.ruleKey=cpp:S5025
22+
sonar.issue.ignore.multicriteria.e3.resourceKey=**/scheduler/queue/*.hpp
23+
sonar.issue.ignore.multicriteria.e4.ruleKey=cpp:S3630
24+
sonar.issue.ignore.multicriteria.e4.resourceKey=**/scheduler/queue/*.hpp
25+
sonar.issue.ignore.multicriteria.e5.ruleKey=cpp:S3432
26+
sonar.issue.ignore.multicriteria.e5.resourceKey=**/scheduler/queue/*.hpp

src/Reactor.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ class Reactor {
390390

391391
public:
392392
template <typename... Args>
393-
Binder(Reactor& r, Args&&... args) : reactor(r), args(std::forward<Args>(args)...) {}
393+
Binder(Reactor& r, Args&&... args_) : reactor(r), args(std::forward<Args>(args_)...) {}
394394

395395
template <typename Label, typename Function>
396396
auto then(Label&& label, Function&& callback) {

src/threading/scheduler/Group.cpp

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -107,18 +107,19 @@ namespace threading {
107107
// Otherwise: no fast waiter was directly entitled. If slow_pending is now 0 and a
108108
// token is available, give it to any fast waiter we have so they don't get stranded.
109109
if (was_locked && group.slow_pending.load(std::memory_order_acquire) == 0) {
110-
while (true) {
111-
int expected = group.tokens.load(std::memory_order_acquire);
112-
if (expected <= 0) {
113-
break;
114-
}
110+
bool claimed = false;
111+
int expected = group.tokens.load(std::memory_order_acquire);
112+
while (!claimed && expected > 0) {
115113
if (group.tokens.compare_exchange_weak(expected,
116114
expected - 1,
117115
std::memory_order_acq_rel)) {
118116
if (!group.drain_one_to_pool()) {
119117
group.tokens.fetch_add(1, std::memory_order_release);
120118
}
121-
break;
119+
claimed = true;
120+
}
121+
else {
122+
expected = group.tokens.load(std::memory_order_acquire);
122123
}
123124
}
124125
}
@@ -194,15 +195,18 @@ namespace threading {
194195
// Don't jump ahead of multi-group waiters; if any exist, queue ourselves.
195196
if (slow_pending.load(std::memory_order_acquire) == 0) {
196197
int expected = tokens.load(std::memory_order_acquire);
197-
while (expected > 0) {
198+
bool done = false;
199+
while (!done && expected > 0) {
198200
if (tokens.compare_exchange_weak(expected, expected - 1, std::memory_order_acq_rel)) {
199201
if (slow_pending.load(std::memory_order_acquire) > 0) {
200202
// Restore the token and fall through to enqueueing.
201203
release_token();
202-
break;
204+
done = true;
205+
}
206+
else {
207+
pool->submit({std::move(task), make_running_lock()}, clear_idle);
208+
return true;
203209
}
204-
pool->submit({std::move(task), make_running_lock()}, clear_idle);
205-
return true;
206210
}
207211
}
208212
}
@@ -234,7 +238,7 @@ namespace threading {
234238
return false;
235239
}
236240

237-
void Group::release_token() {
241+
void Group::release_token() noexcept {
238242
const int prev = tokens.fetch_add(1, std::memory_order_acq_rel);
239243

240244
// If a slow-path waiter exists give them first chance.
@@ -249,7 +253,7 @@ namespace threading {
249253
}
250254
}
251255

252-
void Group::notify_slow_path() {
256+
void Group::notify_slow_path() noexcept {
253257
std::vector<std::shared_ptr<LockHandle>> to_notify;
254258
/*mutex scope*/ {
255259
const std::lock_guard<std::mutex> lock(mutex);
@@ -267,7 +271,7 @@ namespace threading {
267271
}
268272
}
269273

270-
bool Group::drain_one_to_pool() {
274+
bool Group::drain_one_to_pool() noexcept {
271275
WaitEntry entry;
272276
for (std::size_t bucket = 0; bucket < queue::PRIORITY_BUCKETS; ++bucket) {
273277
if (wait_buckets[bucket].try_dequeue(entry)) {

src/threading/scheduler/Group.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,9 @@ namespace threading {
236236
const std::shared_ptr<const util::GroupDescriptor> descriptor;
237237

238238
private:
239-
void release_token();
240-
void notify_slow_path();
241-
bool drain_one_to_pool();
239+
void release_token() noexcept;
240+
void notify_slow_path() noexcept;
241+
bool drain_one_to_pool() noexcept;
242242
std::unique_ptr<Lock> make_running_lock();
243243

244244
/// Available group tokens (signed when waiters are queued on the fast path)

src/threading/scheduler/Pool.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,10 +242,10 @@ namespace threading {
242242
return false;
243243
}
244244

245-
void Pool::drain_queues() {
245+
void Pool::drain_queues() const {
246246
Task discarded;
247-
for (auto& bucket : buckets) {
248-
while (bucket->try_dequeue(discarded)) {
247+
for (const auto& bucket : buckets) {
248+
while (bucket->try_dequeue(discarded)) { /* discard all queued tasks */
249249
}
250250
}
251251
}

src/threading/scheduler/Pool.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ namespace threading {
231231
/**
232232
* Drain all tasks from the priority buckets.
233233
*/
234-
void drain_queues();
234+
void drain_queues() const;
235235

236236
/**
237237
* Get an idle task to execute or hold.

src/threading/scheduler/queue/MPSCQueue.hpp

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ namespace threading {
195195
bool try_dequeue(T& out) override {
196196
while (true) {
197197
const std::size_t write_observed = head_block->write.load(std::memory_order_acquire);
198-
const std::size_t published = std::min(write_observed, static_cast<std::size_t>(BLOCK_SIZE));
198+
const std::size_t published = std::min(write_observed, BLOCK_SIZE);
199199

200200
if (head_block->read < published) {
201201
Slot& slot = head_block->slots[head_block->read];
@@ -218,21 +218,26 @@ namespace threading {
218218
// mid-way through linking the next block; wait briefly for it to appear.
219219
if (write_observed > BLOCK_SIZE) {
220220
std::this_thread::yield();
221-
continue;
222221
}
223-
return false;
222+
else {
223+
return false;
224+
}
225+
}
226+
else {
227+
// We're the sole consumer so advancing head_block is a plain store. The old
228+
// block goes to the graveyard so any producer that still holds a pointer to
229+
// it (e.g. one mid-way through link_next_block) doesn't touch freed memory.
230+
Block* old = head_block;
231+
head_block = next;
232+
retire_block(old);
224233
}
225-
226-
// We're the sole consumer so advancing head_block is a plain store. The old
227-
// block goes to the graveyard so any producer that still holds a pointer to
228-
// it (e.g. one mid-way through link_next_block) doesn't touch freed memory.
229-
Block* old = head_block;
230-
head_block = next;
231-
retire_block(old);
232234
}
233235
}
234236
};
235237

238+
template <typename T>
239+
constexpr std::size_t MPSCQueue<T>::BLOCK_SIZE;
240+
236241
} // namespace queue
237242
} // namespace scheduler
238243
} // namespace threading

src/threading/scheduler/queue/TaskQueue.hpp

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -209,58 +209,59 @@ namespace threading {
209209
Block* block = head.load(std::memory_order_acquire);
210210

211211
const std::size_t published =
212-
std::min(block->write.load(std::memory_order_acquire),
213-
static_cast<std::size_t>(BLOCK_SIZE));
212+
std::min(block->write.load(std::memory_order_acquire), BLOCK_SIZE);
214213
std::size_t read_index = block->read.load(std::memory_order_relaxed);
215214

216215
if (read_index >= published) {
217216
if (block->consumed.load(std::memory_order_acquire) < published) {
217+
// Consumers are still finishing slots in this block; let them progress.
218218
std::this_thread::yield();
219-
continue;
220219
}
221-
222-
Block* next = block->next.load(std::memory_order_acquire);
223-
if (next == nullptr) {
224-
// Producer may still be writing the first slot of an empty-looking block.
225-
if (published == 0 && block->write.load(std::memory_order_acquire) > 0) {
226-
std::this_thread::yield();
227-
continue;
220+
else {
221+
Block* next = block->next.load(std::memory_order_acquire);
222+
if (next == nullptr) {
223+
// Producer may still be writing the first slot of an empty-looking block.
224+
if (published == 0 && block->write.load(std::memory_order_acquire) > 0) {
225+
std::this_thread::yield();
226+
}
227+
else {
228+
return false;
229+
}
230+
}
231+
else {
232+
head.compare_exchange_strong(block,
233+
next,
234+
std::memory_order_release,
235+
std::memory_order_relaxed);
228236
}
229-
return false;
230237
}
231-
232-
head.compare_exchange_strong(block, next, std::memory_order_release, std::memory_order_relaxed);
233-
continue;
234-
}
235-
236-
if (!block->read.compare_exchange_weak(read_index,
237-
read_index + 1,
238-
std::memory_order_acq_rel,
239-
std::memory_order_relaxed)) {
240-
continue;
241238
}
239+
else if (block->read.compare_exchange_weak(read_index,
240+
read_index + 1,
241+
std::memory_order_acq_rel,
242+
std::memory_order_relaxed)) {
243+
Slot& slot = block->slots[read_index];
244+
while (!slot.committed.load(std::memory_order_acquire)) {
245+
std::this_thread::yield();
246+
}
242247

243-
Slot& slot = block->slots[read_index];
244-
while (!slot.committed.load(std::memory_order_acquire)) {
245-
std::this_thread::yield();
246-
}
248+
out = std::move(*slot_ptr(slot));
249+
destroy_slot(slot);
247250

248-
out = std::move(*slot_ptr(slot));
249-
destroy_slot(slot);
251+
if (block->consumed.fetch_add(1, std::memory_order_acq_rel) + 1 == BLOCK_SIZE) {
252+
try_reclaim_block(block);
253+
}
250254

251-
if (block->consumed.fetch_add(1, std::memory_order_acq_rel) + 1 == BLOCK_SIZE) {
252-
try_reclaim_block(block);
255+
return true;
253256
}
254-
255-
return true;
256257
}
257258
}
258259

259260
bool empty() const {
260261
Block* block = head.load(std::memory_order_acquire);
261262
while (block != nullptr) {
262-
const std::size_t published = std::min(block->write.load(std::memory_order_acquire),
263-
static_cast<std::size_t>(BLOCK_SIZE));
263+
const std::size_t published =
264+
std::min(block->write.load(std::memory_order_acquire), BLOCK_SIZE);
264265
if (block->read.load(std::memory_order_relaxed) < published) {
265266
return false;
266267
}
@@ -270,6 +271,9 @@ namespace threading {
270271
}
271272
};
272273

274+
template <typename T>
275+
constexpr std::size_t TaskQueue<T>::BLOCK_SIZE;
276+
273277
} // namespace queue
274278
} // namespace scheduler
275279
} // namespace threading

0 commit comments

Comments
 (0)