summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichaël Zasso <targos@protonmail.com>2023-04-18 18:05:58 +0200
committerMichaël Zasso <targos@protonmail.com>2023-05-10 14:36:42 +0200
commit38d261ae66022a9a412f39a631f2ba8ce7650ee6 (patch)
treef4e244a12499ef99ffdefdc31075070f05c126b0
parent4b4a0c4d54ba1037d4b016e75b50dc2e560cfd8b (diff)
downloadnode-new-38d261ae66022a9a412f39a631f2ba8ce7650ee6.tar.gz
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: https://github.com/v8/v8/commit/5f025d1ca2ca2fe2f6cee5b6d841907a36fd32b7 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>
-rw-r--r--common.gypi2
-rw-r--r--deps/v8/src/wasm/module-compiler.cc70
2 files changed, 49 insertions, 23 deletions
diff --git a/common.gypi b/common.gypi
index 5cca6b4c59..3161adbe09 100644
--- a/common.gypi
+++ b/common.gypi
@@ -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 #####
diff --git a/deps/v8/src/wasm/module-compiler.cc b/deps/v8/src/wasm/module-compiler.cc
index 7feb697aad..128ca572d3 100644
--- a/deps/v8/src/wasm/module-compiler.cc
+++ b/deps/v8/src/wasm/module-compiler.cc
@@ -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();