Skip to content

Commit e2af4b4

Browse files
committed
revise by reviewers' comments
1 parent 565a6c8 commit e2af4b4

File tree

3 files changed

+60
-22
lines changed

3 files changed

+60
-22
lines changed

llvm/include/llvm/Support/Jobserver.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,13 @@
5353
// Before exiting, it must write the 3 tokens it read back to the pipe.
5454
//
5555
// For more context, see:
56+
// - GNU Make manual on job slots:
57+
// https://www.gnu.org/software/make/manual/html_node/Job-Slots.html
5658
// - LLVM RFC discussion on jobserver support:
5759
// https://discourse.llvm.org/t/rfc-adding-gnu-make-jobserver-
5860
// support-to-llvm-for-coordinated-parallelism/87034
5961
// - Ninja’s jobserver support PR:
60-
// https://github.com/ninja-build/ninja/pull/2260//
62+
// https://github.com/ninja-build/ninja/pull/2506
6163
//
6264
//===----------------------------------------------------------------------===//
6365

llvm/lib/Support/Parallel.cpp

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "llvm/Support/Parallel.h"
1010
#include "llvm/ADT/ScopeExit.h"
1111
#include "llvm/Config/llvm-config.h"
12+
#include "llvm/Support/ExponentialBackoff.h"
1213
#include "llvm/Support/Jobserver.h"
1314
#include "llvm/Support/ManagedStatic.h"
1415
#include "llvm/Support/Threading.h"
@@ -122,32 +123,62 @@ class ThreadPoolExecutor : public Executor {
122123
void work(ThreadPoolStrategy S, unsigned ThreadID) {
123124
threadIndex = ThreadID;
124125
S.apply_thread_strategy(ThreadID);
125-
while (true) {
126-
std::unique_lock<std::mutex> Lock(Mutex);
127-
Cond.wait(Lock, [&] { return Stop || !WorkStack.empty(); });
128-
if (Stop)
129-
break;
130-
auto Task = std::move(WorkStack.back());
131-
WorkStack.pop_back();
132-
Lock.unlock();
126+
// Note on jobserver deadlock avoidance:
127+
// GNU Make grants each invoked process one implicit job slot. Our
128+
// JobserverClient models this by returning an implicit JobSlot on the
129+
// first successful tryAcquire() in a process. This guarantees forward
130+
// progress without requiring a dedicated "always-on" thread here.
133131

132+
static thread_local std::unique_ptr<ExponentialBackoff> Backoff;
133+
134+
while (true) {
134135
if (TheJobserver) {
135-
JobSlot Slot = TheJobserver->tryAcquire();
136-
if (Slot.isValid()) {
136+
// Jobserver-mode scheduling:
137+
// - Acquire one job slot (with exponential backoff to avoid busy-wait).
138+
// - While holding the slot, drain and run tasks from the local queue.
139+
// - Release the slot when the queue is empty or when shutting down.
140+
// Rationale: Holding a slot amortizes acquire/release overhead over
141+
// multiple tasks and avoids requeue/yield churn, while still enforcing
142+
// the jobserver’s global concurrency limit. With K available slots,
143+
// up to K workers run tasks in parallel; within each worker tasks run
144+
// sequentially until the local queue is empty.
145+
ExponentialBackoff Backoff(std::chrono::hours(24));
146+
JobSlot Slot;
147+
do {
148+
if (Stop)
149+
return;
150+
Slot = TheJobserver->tryAcquire();
151+
if (Slot.isValid())
152+
break;
153+
} while (Backoff.waitForNextAttempt());
154+
155+
auto SlotReleaser = llvm::make_scope_exit(
156+
[&] { TheJobserver->release(std::move(Slot)); });
157+
158+
while (true) {
159+
std::function<void()> Task;
160+
{
161+
std::unique_lock<std::mutex> Lock(Mutex);
162+
Cond.wait(Lock, [&] { return Stop || !WorkStack.empty(); });
163+
if (Stop && WorkStack.empty())
164+
return;
165+
if (WorkStack.empty())
166+
break;
167+
Task = std::move(WorkStack.back());
168+
WorkStack.pop_back();
169+
}
137170
Task();
138-
TheJobserver->release(std::move(Slot));
139-
} else {
140-
// The task could not be run because no job slot was
141-
// available. Re-queue the task so that another thread can try
142-
// to run it later.
143-
std::lock_guard<std::mutex> RequeueLock(Mutex);
144-
WorkStack.push_back(std::move(Task));
145-
Cond.notify_one();
146-
// Yield to give another thread a chance to release a token.
147-
std::this_thread::yield();
148171
}
149-
} else
172+
} else {
173+
std::unique_lock<std::mutex> Lock(Mutex);
174+
Cond.wait(Lock, [&] { return Stop || !WorkStack.empty(); });
175+
if (Stop)
176+
break;
177+
auto Task = std::move(WorkStack.back());
178+
WorkStack.pop_back();
179+
Lock.unlock();
150180
Task();
181+
}
151182
}
152183
}
153184

llvm/lib/Support/ThreadPool.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ void StdThreadPool::grow(int requested) {
5151
Threads.emplace_back([this, ThreadID] {
5252
set_thread_name(formatv("llvm-worker-{0}", ThreadID));
5353
Strategy.apply_thread_strategy(ThreadID);
54+
// Note on jobserver deadlock avoidance:
55+
// GNU Make grants each invoked process one implicit job slot.
56+
// JobserverClient::tryAcquire() returns that implicit slot on the first
57+
// successful call in a process, ensuring forward progress without a
58+
// dedicated "always-on" thread.
5459
if (TheJobserver)
5560
processTasksWithJobserver();
5661
else

0 commit comments

Comments
 (0)