Skip to content

Commit 21bd3be

Browse files
DinoVmeta-codesync[bot]
authored andcommitted
Safer background compile shutdown
Summary: Shutting down background compile is tricky. We can start a shutdown and then preload and re-start the worker thread. And there's re-entrancy concerns too. Reviewed By: alexmalyshev Differential Revision: D115228331 fbshipit-source-id: 14b164afca42573ba8c79e479d55bb8944c0a1fe
1 parent 3cb7c53 commit 21bd3be

2 files changed

Lines changed: 25 additions & 3 deletions

File tree

cinderx/Jit/pyjit.cpp

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3885,6 +3885,14 @@ void scheduleBackgroundCompile(BorrowedRef<PyFunctionObject> func) {
38853885
// can't be started, release the task's Python references under the guard we
38863886
// already hold and leave the function interpreted.
38873887
std::lock_guard<std::mutex> lock(reg.mutex);
3888+
// Re-check for shutdown: preloading above ran without the registry lock, so a
3889+
// drain could have completed in the meantime. Starting a worker now would
3890+
// resurrect the thread that drain just joined.
3891+
if (reg.shutdown) {
3892+
reg.in_flight.erase(code.get());
3893+
reg.drain_cv.notify_all();
3894+
return;
3895+
}
38883896
if (!reg.worker_started && !startBackgroundWorkerThread(jit_ctx, reg)) {
38893897
reg.in_flight.erase(code.get());
38903898
reg.drain_cv.notify_all();
@@ -4051,12 +4059,20 @@ void cancelBackgroundCompiles() {
40514059
worker_to_join.join();
40524060
}
40534061
}
4062+
// Take the remaining work out of the registry, but destroy it further down
4063+
// with the lock released. Dropping a task's references can run a __del__,
4064+
// which calls back into jitVectorcall() and deadlocks on reg.mutex.
4065+
std::deque<std::unique_ptr<BackgroundCompileTask>> abandoned;
40544066
{
4055-
// Clear out any remaining work
4067+
// `shutdown` deliberately stays set: this only runs while the interpreter
4068+
// is going away, and re-enabling background compilation here would let the
4069+
// Python code that runs during the rest of shutdown start a fresh worker.
4070+
// Once the runtime marks itself finalizing that worker hangs forever in
4071+
// PyThread_hang_thread() the moment it re-acquires the GIL, and the join()
4072+
// above would never return.
40564073
std::unique_lock<std::mutex> lock(reg.mutex);
40574074
reg.in_flight.clear();
4058-
reg.queue.clear();
4059-
reg.shutdown = false;
4075+
abandoned.swap(reg.queue);
40604076
}
40614077
}
40624078

cinderx/Jit/pyjit.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ void finalize();
3838
/*
3939
* Stop the background-compilation worker (if any) and cancel all in-flight
4040
* background compiles to finish.
41+
*
42+
* Background compilation stays disabled afterwards; this is only meant to be
43+
* called on the way out of the interpreter, and a worker started after the
44+
* drain can no longer be joined once the runtime is finalizing. Background
45+
* compilation comes back when the JIT is re-initialized, which builds a fresh
46+
* registry.
4147
*/
4248
void cancelBackgroundCompiles();
4349

0 commit comments

Comments
 (0)