mirror of https://github.com/nodejs/node.git
deps: V8: cherry-pick 5f025d1ca2ca
Original commit message:
[wasm] Fix deadlock in async wrapper compilation
If compilation is cancelled while wrapper compilation is running, the
tasks spawned for the {AsyncCompileJSToWasmWrapperJob} will return
immediately, but {GetMaxConcurrency} will still return a positive
value. Hence {Join()} will spawn another task, resulting in a
livelock.
We could fix this by checking for cancellation in {GetMaxConcurrency},
but that requires taking the compilation state lock.
So this CL fixes the issue by dropping the number of outstanding
compilation units by to (basically) zero. We can't unconditionally drop
to zero because another thread might concurrently execute a wrapper
compilation and still call {CompleteUnit} afterwards. Hence only drop
outstanding units by the amount of not-yet-started units.
R=jkummerow@chromium.org
Bug: v8:13858
Change-Id: I5398ef370da2e7f212ca772fd1f87f659929dd6d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4437531
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/main@{#87143}
Refs: 5f025d1ca2
PR-URL: https://github.com/nodejs/node/pull/47610
Refs: https://github.com/nodejs/node/issues/47297
Reviewed-By: Richard Lau <rlau@redhat.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
pull/47364/head
parent
4b4a0c4d54
commit
38d261ae66
|
@ -36,7 +36,7 @@
|
|||
|
||||
# Reset this number to 0 on major V8 upgrades.
|
||||
# Increment by one for each non-official patch applied to deps/v8.
|
||||
'v8_embedder_string': '-node.8',
|
||||
'v8_embedder_string': '-node.9',
|
||||
|
||||
##### V8 defaults for Node.js #####
|
||||
|
||||
|
|
|
@ -1826,7 +1826,8 @@ void CompileNativeModule(Isolate* isolate,
|
|||
class BaseCompileJSToWasmWrapperJob : public JobTask {
|
||||
public:
|
||||
explicit BaseCompileJSToWasmWrapperJob(size_t compilation_units)
|
||||
: outstanding_units_(compilation_units) {}
|
||||
: outstanding_units_(compilation_units),
|
||||
total_units_(compilation_units) {}
|
||||
|
||||
size_t GetMaxConcurrency(size_t worker_count) const override {
|
||||
size_t flag_limit = static_cast<size_t>(
|
||||
|
@ -1839,12 +1840,20 @@ class BaseCompileJSToWasmWrapperJob : public JobTask {
|
|||
}
|
||||
|
||||
protected:
|
||||
// Returns the index of the next unit to process.
|
||||
size_t GetNextUnitIndex() {
|
||||
// |unit_index_| may exceeed |compilation_units|, but only by the number of
|
||||
// workers at worst, thus it can't exceed 2 * |compilation_units| and
|
||||
// overflow shouldn't happen.
|
||||
return unit_index_.fetch_add(1, std::memory_order_relaxed);
|
||||
// Returns {true} and places the index of the next unit to process in
|
||||
// {index_out} if there are still units to be processed. Returns {false}
|
||||
// otherwise.
|
||||
bool GetNextUnitIndex(size_t* index_out) {
|
||||
size_t next_index = unit_index_.fetch_add(1, std::memory_order_relaxed);
|
||||
if (next_index >= total_units_) {
|
||||
// {unit_index_} may exceeed {total_units_}, but only by the number of
|
||||
// workers at worst, thus it can't exceed 2 * {total_units_} and overflow
|
||||
// shouldn't happen.
|
||||
DCHECK_GE(2 * total_units_, next_index);
|
||||
return false;
|
||||
}
|
||||
*index_out = next_index;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns true if the last unit was completed.
|
||||
|
@ -1855,9 +1864,29 @@ class BaseCompileJSToWasmWrapperJob : public JobTask {
|
|||
return outstanding_units == 1;
|
||||
}
|
||||
|
||||
// When external cancellation is detected, call this method to bump
|
||||
// {unit_index_} and reset {outstanding_units_} such that no more tasks are
|
||||
// being scheduled for this job and all tasks exit as soon as possible.
|
||||
void FlushRemainingUnits() {
|
||||
// After being cancelled, make sure to reduce outstanding_units_ to
|
||||
// *basically* zero, but leave the count positive if other workers are still
|
||||
// running, to avoid underflow in {CompleteUnit}.
|
||||
size_t next_undone_unit =
|
||||
unit_index_.exchange(total_units_, std::memory_order_relaxed);
|
||||
size_t undone_units =
|
||||
next_undone_unit >= total_units_ ? 0 : total_units_ - next_undone_unit;
|
||||
// Note that the caller requested one unit that we also still need to remove
|
||||
// from {outstanding_units_}.
|
||||
++undone_units;
|
||||
size_t previous_outstanding_units =
|
||||
outstanding_units_.fetch_sub(undone_units, std::memory_order_relaxed);
|
||||
CHECK_LE(undone_units, previous_outstanding_units);
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<size_t> unit_index_{0};
|
||||
std::atomic<size_t> outstanding_units_;
|
||||
const size_t total_units_;
|
||||
};
|
||||
|
||||
class AsyncCompileJSToWasmWrapperJob final
|
||||
|
@ -1867,8 +1896,7 @@ class AsyncCompileJSToWasmWrapperJob final
|
|||
std::weak_ptr<NativeModule> native_module, size_t compilation_units)
|
||||
: BaseCompileJSToWasmWrapperJob(compilation_units),
|
||||
native_module_(std::move(native_module)),
|
||||
engine_barrier_(GetWasmEngine()->GetBarrierForBackgroundCompile()),
|
||||
compilation_units_size_(compilation_units) {}
|
||||
engine_barrier_(GetWasmEngine()->GetBarrierForBackgroundCompile()) {}
|
||||
|
||||
void Run(JobDelegate* delegate) override {
|
||||
auto engine_scope = engine_barrier_->TryLock();
|
||||
|
@ -1878,18 +1906,18 @@ class AsyncCompileJSToWasmWrapperJob final
|
|||
OperationsBarrier::Token wrapper_compilation_token;
|
||||
Isolate* isolate;
|
||||
|
||||
size_t index = GetNextUnitIndex();
|
||||
if (index >= compilation_units_size_) return;
|
||||
size_t index;
|
||||
if (!GetNextUnitIndex(&index)) return;
|
||||
{
|
||||
BackgroundCompileScope compile_scope(native_module_);
|
||||
if (compile_scope.cancelled()) return;
|
||||
if (compile_scope.cancelled()) return FlushRemainingUnits();
|
||||
wrapper_unit =
|
||||
compile_scope.compilation_state()->GetJSToWasmWrapperCompilationUnit(
|
||||
index);
|
||||
isolate = wrapper_unit->isolate();
|
||||
wrapper_compilation_token =
|
||||
wasm::GetWasmEngine()->StartWrapperCompilation(isolate);
|
||||
if (!wrapper_compilation_token) return;
|
||||
if (!wrapper_compilation_token) return FlushRemainingUnits();
|
||||
}
|
||||
|
||||
TRACE_EVENT0("v8.wasm", "wasm.JSToWasmWrapperCompilation");
|
||||
|
@ -1902,11 +1930,11 @@ class AsyncCompileJSToWasmWrapperJob final
|
|||
|
||||
BackgroundCompileScope compile_scope(native_module_);
|
||||
if (compile_scope.cancelled()) return;
|
||||
if (complete_last_unit)
|
||||
if (complete_last_unit) {
|
||||
compile_scope.compilation_state()->OnFinishedJSToWasmWrapperUnits();
|
||||
}
|
||||
if (yield) return;
|
||||
size_t index = GetNextUnitIndex();
|
||||
if (index >= compilation_units_size_) return;
|
||||
if (!GetNextUnitIndex(&index)) return;
|
||||
wrapper_unit =
|
||||
compile_scope.compilation_state()->GetJSToWasmWrapperCompilationUnit(
|
||||
index);
|
||||
|
@ -1916,8 +1944,6 @@ class AsyncCompileJSToWasmWrapperJob final
|
|||
private:
|
||||
std::weak_ptr<NativeModule> native_module_;
|
||||
std::shared_ptr<OperationsBarrier> engine_barrier_;
|
||||
// Number of wrappers to be compiled.
|
||||
const size_t compilation_units_size_;
|
||||
};
|
||||
|
||||
class BackgroundCompileJob final : public JobTask {
|
||||
|
@ -3723,8 +3749,9 @@ void CompilationStateImpl::WaitForCompilationEvent(
|
|||
// Waiting on other CompilationEvent doesn't make sense.
|
||||
UNREACHABLE();
|
||||
}
|
||||
if (js_to_wasm_wrapper_job_ && js_to_wasm_wrapper_job_->IsValid())
|
||||
if (js_to_wasm_wrapper_job_ && js_to_wasm_wrapper_job_->IsValid()) {
|
||||
js_to_wasm_wrapper_job_->Join();
|
||||
}
|
||||
#ifdef DEBUG
|
||||
base::EnumSet<CompilationEvent> events{expect_event,
|
||||
CompilationEvent::kFailedCompilation};
|
||||
|
@ -3786,9 +3813,8 @@ class CompileJSToWasmWrapperJob final : public BaseCompileJSToWasmWrapperJob {
|
|||
compilation_units_(compilation_units) {}
|
||||
|
||||
void Run(JobDelegate* delegate) override {
|
||||
while (true) {
|
||||
size_t index = GetNextUnitIndex();
|
||||
if (index >= compilation_units_->size()) return;
|
||||
size_t index;
|
||||
while (GetNextUnitIndex(&index)) {
|
||||
JSToWasmWrapperCompilationUnit* unit =
|
||||
(*compilation_units_)[index].second.get();
|
||||
unit->Execute();
|
||||
|
|
Loading…
Reference in New Issue