修复webserver死锁

This commit is contained in:
2026-08-17 19:43:37 +08:00
parent 3881beea69
commit 47cf1af7a5
4 changed files with 326 additions and 4 deletions
+1
View File
@@ -10,3 +10,4 @@
/output/
/webapp_gallery/.playwright-cli/
/logs/
/deadlock_capture/
+155 -3
View File
@@ -3,12 +3,17 @@
#include "Scene_Base.inl"
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <iostream>
#include <limits>
#include <memory_resource>
#include <queue>
#include <semaphore>
#include <stdexcept>
#include <string>
#include <string_view>
#include <syncstream>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <utility>
@@ -58,6 +63,33 @@ Scene_Render_Result scene_render_error(
}
::renderive::error::unexpected<std::logic_error>("unknown render graph execution error");
}
bool scene_render_trace_enabled() {
static const bool enabled = [] {
const char* value = std::getenv("RENDERIVE_SCENE_RENDER_TRACE");
return value && std::string_view(value) == "1";
}();
return enabled;
}
template <class Writer>
void scene_render_trace(Writer&& writer) {
if (!scene_render_trace_enabled()) return;
const auto now = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count();
const auto& runtime = renderive::scheduling::detail::OneTBB_Runtime::instance();
const auto worker_id = runtime.current_worker_id();
const auto statistics = runtime.statistics();
std::osyncstream output(std::cerr);
output << "[renderive.scene.trace] time_ns=" << now
<< " thread=" << std::this_thread::get_id()
<< " arena_index=";
if (worker_id == std::numeric_limits<std::uint32_t>::max()) output << "external";
else output << worker_id;
output << " active_workers=" << statistics.active_workers
<< " active_external_threads=" << statistics.active_external_threads
<< " concurrency=" << statistics.concurrency;
writer(output);
output << '\n';
}
}
class Scene_Base::Impl::Execution_Context {
public:
@@ -93,6 +125,11 @@ public:
const std::size_t queued = queued_.fetch_add(1, std::memory_order_relaxed) + 1;
update_peak(peak_queued_, queued);
if (!operations_.try_push(std::move(operation))) ::renderive::error::unexpected<std::logic_error>("scene execution queue rejected admitted operation");
scene_render_trace([this, queued](auto& output) {
output << " event=execution_submit"
<< " scene=" << &scene_.scene()
<< " queued=" << queued;
});
schedule();
}
void wait() {
@@ -137,9 +174,19 @@ private:
return;
continue;
}
queued_.fetch_sub(1, std::memory_order_relaxed);
const std::size_t queued = queued_.fetch_sub(1, std::memory_order_relaxed) - 1;
slots_.release();
scene_render_trace([this, queued](auto& output) {
output << " event=execution_operation_begin"
<< " scene=" << &scene_.scene()
<< " queued=" << queued;
});
operation();
scene_render_trace([this, queued](auto& output) {
output << " event=execution_operation_end"
<< " scene=" << &scene_.scene()
<< " queued=" << queued;
});
}
}
static constexpr std::ptrdiff_t default_capacity = 64;
@@ -653,7 +700,17 @@ Scene_Render_Result Scene_Base::Impl::submit_render(Abstract_Frame* frame) {
task->snapshot, task->compiled_plan->plan
};
++pending_operations_;
const std::size_t pending_operations = pending_operations_;
task_lock.unlock();
scene_render_trace([this, &task, pending_operations](auto& output) {
output << " event=render_submit"
<< " scene=" << owner_
<< " completion=" << task->completion.get()
<< " sequence=" << task->snapshot->render_sequence
<< " pending_operations=" << pending_operations
<< " active_execution_scene=" << active_execution_scene_
<< " active_observer_scene=" << active_submitted_observer_scene_;
});
Scene_Base* previous_observer_scene =
std::exchange(active_submitted_observer_scene_, owner_);
try {
@@ -677,6 +734,13 @@ Scene_Render_Result Scene_Base::Impl::submit_render(Abstract_Frame* frame) {
}
active_submitted_observer_scene_ = previous_observer_scene;
try {
scene_render_trace([this, &task](auto& output) {
output << " event=render_task_queue"
<< " scene=" << owner_
<< " completion=" << task->completion.get()
<< " sequence=" << task->snapshot->render_sequence
<< " active_execution_scene=" << active_execution_scene_;
});
execution_context_->submit([this, task = std::move(task)] {
execute_render_task(task);
complete_pending_operation();
@@ -707,15 +771,40 @@ Scene_Render_Result Scene_Base::Impl::submit_render(Abstract_Frame* frame) {
Scene_Render_Result Scene_Base::wait_for_render() {
auto& implementation = d_func();
if (implementation.is_render_execution_context() ||
Impl::active_submitted_observer_scene_ == this)
Impl::active_submitted_observer_scene_ == this) {
scene_render_trace([this](auto& output) {
output << " event=wait_bypass"
<< " scene=" << this
<< " active_execution_scene=" << Impl::active_execution_scene_
<< " active_observer_scene=" << Impl::active_submitted_observer_scene_;
});
return Scene_Render_Result::none;
}
std::unique_lock<std::recursive_mutex> lock(implementation.task_mutex_);
const auto completion = implementation.current_completion_;
if (!completion) return Scene_Render_Result::none;
scene_render_trace([this, &implementation, &completion](auto& output) {
output << " event=wait_begin"
<< " scene=" << this
<< " completion=" << completion.get()
<< " sequence=" << implementation.render_sequence_
<< " completed=" << completion->completed
<< " pending_operations=" << implementation.pending_operations_
<< " active_execution_scene=" << Impl::active_execution_scene_
<< " active_observer_scene=" << Impl::active_submitted_observer_scene_;
});
implementation.render_completed_.wait(
lock, [&completion] {
return completion->completed;
});
scene_render_trace([this, &implementation, &completion](auto& output) {
output << " event=wait_end"
<< " scene=" << this
<< " completion=" << completion.get()
<< " sequence=" << implementation.render_sequence_
<< " pending_operations=" << implementation.pending_operations_
<< " active_execution_scene=" << Impl::active_execution_scene_;
});
std::exception_ptr exception;
if (implementation.pending_exception_) exception = std::exchange(implementation.pending_exception_, {});
else {
@@ -765,10 +854,23 @@ Scene_Base::Edit_Operation Scene_Base::Impl::enqueue_renderable_edit(
return operation;
}
void Scene_Base::Impl::complete_pending_operation() {
std::size_t pending_operations;
const void* completion;
std::uint64_t render_sequence;
{
std::lock_guard<std::recursive_mutex> lock(task_mutex_);
--pending_operations_;
pending_operations = --pending_operations_;
completion = current_completion_.get();
render_sequence = render_sequence_;
}
scene_render_trace([this, pending_operations, completion, render_sequence](auto& output) {
output << " event=pending_operation_complete"
<< " scene=" << owner_
<< " completion=" << completion
<< " sequence=" << render_sequence
<< " pending_operations=" << pending_operations
<< " active_execution_scene=" << active_execution_scene_;
});
render_completed_.notify_all();
}
void Scene_Base::Impl::record_pending_exception(std::exception_ptr exception) {
@@ -1158,9 +1260,30 @@ std::unique_lock<std::recursive_mutex> Scene_Base::Impl::lock_render_idle() {
if (is_render_execution_context()) ::renderive::error::unexpected<std::logic_error>("render-idle operation is not allowed during render execution");
if (active_submitted_observer_scene_ == owner_) ::renderive::error::unexpected<std::logic_error>("render-idle operation is not allowed during render submission observation");
std::unique_lock<std::recursive_mutex> lock(task_mutex_);
const bool waited = pending_operations_ != 0;
if (waited) {
scene_render_trace([this](auto& output) {
output << " event=idle_wait_begin"
<< " scene=" << owner_
<< " completion=" << current_completion_.get()
<< " sequence=" << render_sequence_
<< " pending_operations=" << pending_operations_
<< " active_execution_scene=" << active_execution_scene_;
});
}
render_completed_.wait(lock, [this] {
return pending_operations_ == 0;
});
if (waited) {
scene_render_trace([this](auto& output) {
output << " event=idle_wait_end"
<< " scene=" << owner_
<< " completion=" << current_completion_.get()
<< " sequence=" << render_sequence_
<< " pending_operations=" << pending_operations_
<< " active_execution_scene=" << active_execution_scene_;
});
}
return lock;
}
bool Scene_Base::Impl::is_render_execution_context() const noexcept {
@@ -1220,8 +1343,16 @@ Scene_Edit_Error Scene_Base::Impl::execute_renderable_edit(
}
}
void Scene_Base::Impl::execute_render_task(std::shared_ptr<Render_Task> task) {
Scene_Base* previous_execution_scene = active_execution_scene_;
Render_Execution_Scope scope(*this);
const auto& snapshot = *task->snapshot;
scene_render_trace([this, &task, &snapshot, previous_execution_scene](auto& output) {
output << " event=render_task_begin"
<< " scene=" << owner_
<< " completion=" << task->completion.get()
<< " sequence=" << snapshot.render_sequence
<< " parent_execution_scene=" << previous_execution_scene;
});
Scene_Render_Result error{};
std::exception_ptr exception;
bool render_graph_entered{};
@@ -1232,7 +1363,19 @@ void Scene_Base::Impl::execute_render_task(std::shared_ptr<Render_Task> task) {
task->snapshot, task->compiled_plan->plan
});
render_graph_entered = true;
scene_render_trace([this, &task, &snapshot](auto& output) {
output << " event=render_graph_begin"
<< " scene=" << owner_
<< " completion=" << task->completion.get()
<< " sequence=" << snapshot.render_sequence;
});
error = execute_render_graph(*task);
scene_render_trace([this, &task, &snapshot](auto& output) {
output << " event=render_graph_end"
<< " scene=" << owner_
<< " completion=" << task->completion.get()
<< " sequence=" << snapshot.render_sequence;
});
const auto event = error == Scene_Render_Result::none
? Observation_Event::render_completed
: Observation_Event::render_cancelled;
@@ -1259,12 +1402,21 @@ void Scene_Base::Impl::execute_render_task(std::shared_ptr<Render_Task> task) {
std::move(exception), std::current_exception());
}
}
std::size_t pending_operations;
{
std::lock_guard lock(task_mutex_);
task->completion->error = error;
task->completion->exception = exception;
task->completion->completed = true;
pending_operations = pending_operations_;
}
scene_render_trace([this, &task, &snapshot, pending_operations](auto& output) {
output << " event=render_task_complete"
<< " scene=" << owner_
<< " completion=" << task->completion.get()
<< " sequence=" << snapshot.render_sequence
<< " pending_operations=" << pending_operations;
});
render_completed_.notify_all();
}
std::shared_ptr<Scene_Base::Impl::Compiled_Render_Plan> Scene_Base::Impl::compile_render_plan(
+113
View File
@@ -0,0 +1,113 @@
$ErrorActionPreference = "Stop"
$targetExe = "D:\ae\proj\Renderive\cmake-build-vs2022_debug\web_server\Renderive_Web_Server.exe"
$cdb = "D:\ae\ewdk\EWDK_22621_230929-1800\Program Files\Windows Kits\10\Debuggers\x64\cdb.exe"
$outputRoot = "D:\ae\proj\Renderive\deadlock_capture"
if (-not (Test-Path -LiteralPath $cdb))
{
throw "CDB not found: $cdb"
}
if (-not (Test-Path -LiteralPath $targetExe))
{
throw "Target executable not found: $targetExe"
}
$processes = @(
Get-CimInstance Win32_Process |
Where-Object { $_.ExecutablePath -eq $targetExe }
)
if ($processes.Count -eq 0)
{
throw "Renderive_Web_Server.exe is not running: $targetExe"
}
if ($processes.Count -ne 1)
{
$processes |
Select-Object ProcessId, Name, ExecutablePath |
Format-Table -AutoSize
throw "Multiple matching processes found. Stop the extra processes first."
}
$pidValue = [int]$processes[0].ProcessId
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$outputDir = Join-Path $outputRoot $timestamp
$logPath = Join-Path $outputDir "Renderive_deadlock_$timestamp.txt"
$dumpPath = Join-Path $outputDir "Renderive_deadlock_$timestamp.dmp"
$commandPath = Join-Path $outputDir "cdb_commands.txt"
New-Item -ItemType Directory -Force -Path $outputDir | Out-Null
$cdbCommands = @"
.echo ============================================================
.echo Renderive deadlock capture
.echo ============================================================
.echo === PROCESS ===
|
.echo === ALL THREAD STACKS ===
~* kb
.echo === THREAD CPU TIME ===
!runaway 3
.echo === LOCKS ===
!locks
.echo === HANG ANALYSIS ===
!analyze -hang
.echo === FULL MEMORY DUMP ===
.dump /ma $dumpPath
.echo === CAPTURE FINISHED ===
q
"@
Set-Content -LiteralPath $commandPath -Value $cdbCommands -Encoding ASCII
Write-Host ""
Write-Host "Target:"
Write-Host " $targetExe"
Write-Host ""
Write-Host "PID:"
Write-Host " $pidValue"
Write-Host ""
Write-Host "Output:"
Write-Host " $outputDir"
Write-Host ""
Write-Host "Attaching CDB non-invasively..."
Write-Host ""
$cdbArgs = @(
"-pv"
"-p"
$pidValue.ToString()
"-logo"
$logPath
"-cf"
$commandPath
)
& $cdb @cdbArgs
$cdbExitCode = $LASTEXITCODE
Write-Host ""
Write-Host "CDB exit code: $cdbExitCode"
Write-Host ""
if (Test-Path -LiteralPath $logPath)
{
Write-Host "Log:"
Write-Host " $logPath"
}
if (Test-Path -LiteralPath $dumpPath)
{
$dump = Get-Item -LiteralPath $dumpPath
Write-Host ""
Write-Host "Dump:"
Write-Host " $dumpPath"
Write-Host " Size: $([math]::Round($dump.Length / 1MB, 2) ) MB"
}
Write-Host ""
Write-Host "Capture complete."
+57 -1
View File
@@ -10,15 +10,21 @@
#include <algorithm>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <deque>
#include <exception>
#include <functional>
#include <mutex>
#include <optional>
#include <string>
#include <string_view>
#include <thread>
#include <type_traits>
#include <utility>
#include <vector>
namespace renderive::web {
namespace {
Gallery_View_Input gallery_input(const Event& event) {
@@ -133,6 +139,56 @@ trantor::EventLoop* automatic_render_loop() {
return thread.getLoop();
}
}
namespace detail {
class Automatic_Render_Executor final {
public:
Automatic_Render_Executor() {
const std::size_t worker_count = renderive::scheduling::scheduler_concurrency();
workers_.reserve(worker_count);
for (std::size_t index = 0; index < worker_count; ++index) {
workers_.emplace_back([this](std::stop_token stop) {
run(stop);
});
}
}
~Automatic_Render_Executor() {
for (auto& worker : workers_)
worker.request_stop();
condition_.notify_all();
}
void enqueue(std::function<void()> task) {
{
std::lock_guard lock(mutex_);
tasks_.push_back(std::move(task));
}
condition_.notify_one();
}
private:
void run(std::stop_token stop) {
for (;;) {
std::function<void()> task;
{
std::unique_lock lock(mutex_);
if (!condition_.wait(lock, stop, [this] {
return !tasks_.empty();
}))
return;
task = std::move(tasks_.front());
tasks_.pop_front();
}
task();
}
}
std::mutex mutex_;
std::condition_variable_any condition_;
std::deque<std::function<void()>> tasks_;
std::vector<std::jthread> workers_;
};
Automatic_Render_Executor& automatic_render_executor() {
static Automatic_Render_Executor executor;
return executor;
}
}
struct Gallery_Plot_Session::Impl : std::enable_shared_from_this<Gallery_Plot_Session::Impl> {
using Clock = std::chrono::steady_clock;
explicit Impl(bool enable_automatic_low_latency)
@@ -247,7 +303,7 @@ struct Gallery_Plot_Session::Impl : std::enable_shared_from_this<Gallery_Plot_Se
}
auto self = shared_from_this();
try {
renderive::scheduling::enqueue_task([self = std::move(self), generation] {
detail::automatic_render_executor().enqueue([self = std::move(self), generation] {
self->run_automatic_render(generation);
});
} catch (...) {