修复webserver死锁
This commit is contained in:
@@ -10,3 +10,4 @@
|
||||
/output/
|
||||
/webapp_gallery/.playwright-cli/
|
||||
/logs/
|
||||
/deadlock_capture/
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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."
|
||||
@@ -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 (...) {
|
||||
|
||||
Reference in New Issue
Block a user