修复webserver死锁
This commit is contained in:
@@ -1,30 +1,19 @@
|
||||
#include <renderive/error/Error_Policy.hpp>
|
||||
#include "Gallery_Plot_Session.h"
|
||||
#include "Gallery_Protocol.h"
|
||||
#include "common/Gallery_Scene_Interface.h"
|
||||
#include "render_2D/Gallery_Scene2D.h"
|
||||
#include "render_3D/Gallery_Scene3D.h"
|
||||
#include <renderive/scheduling/Scheduler.hpp>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#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) {
|
||||
@@ -124,78 +113,14 @@ Gallery_Key_Input gallery_input(const Key_Event& event) {
|
||||
input.auto_repeat = event.auto_repeat;
|
||||
return input;
|
||||
}
|
||||
std::unique_ptr<Gallery_Scene_Interface> make_gallery_scene(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode, bool automatic_low_latency) {
|
||||
std::unique_ptr<Gallery_Scene_Interface> make_gallery_scene(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode) {
|
||||
if (case_id.starts_with("datoviz_"))
|
||||
return make_gallery_scene_3d(session_id, std::move(case_id), mode, automatic_low_latency);
|
||||
return make_gallery_scene_2d(session_id, std::move(case_id), mode, automatic_low_latency);
|
||||
}
|
||||
trantor::EventLoop* automatic_render_loop() {
|
||||
static trantor::EventLoopThread thread("RenderiveAutomaticRender");
|
||||
static const bool started = [] {
|
||||
thread.run();
|
||||
return true;
|
||||
}();
|
||||
static_cast<void>(started);
|
||||
return thread.getLoop();
|
||||
return make_gallery_scene_3d(session_id, std::move(case_id), mode);
|
||||
return make_gallery_scene_2d(session_id, std::move(case_id), mode);
|
||||
}
|
||||
}
|
||||
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)
|
||||
: automatic_low_latency(enable_automatic_low_latency), session_id(next_session_id()) {}
|
||||
~Impl() {
|
||||
disarm_automatic_render();
|
||||
}
|
||||
struct Gallery_Plot_Session::Impl {
|
||||
Impl() : session_id(next_session_id()) {}
|
||||
static std::uint64_t next_session_id() noexcept {
|
||||
static std::atomic<std::uint64_t> next{1};
|
||||
return next.fetch_add(1, std::memory_order_relaxed);
|
||||
@@ -263,105 +188,9 @@ struct Gallery_Plot_Session::Impl : std::enable_shared_from_this<Gallery_Plot_Se
|
||||
[[nodiscard]] std::unique_lock<std::mutex> acquire_foreground_lock() {
|
||||
return std::unique_lock<std::mutex>(mutex);
|
||||
}
|
||||
void disarm_automatic_render() {
|
||||
++automatic_timer_revision;
|
||||
if (automatic_timer == trantor::InvalidTimerId)
|
||||
return;
|
||||
automatic_render_loop()->invalidateTimer(automatic_timer);
|
||||
automatic_timer = trantor::InvalidTimerId;
|
||||
}
|
||||
void arm_automatic_render_locked() {
|
||||
if (!automatic_low_latency)
|
||||
return;
|
||||
disarm_automatic_render();
|
||||
if (!scene || !scene->can_render_automatically() || render_task_pending)
|
||||
return;
|
||||
const auto interval = std::chrono::nanoseconds(
|
||||
std::max<std::uint64_t>(1, scene->kernel_refresh_interval_ns()));
|
||||
const auto deadline = last_render_started ? *last_render_started + interval : Clock::now();
|
||||
const auto delay = std::max(Clock::duration::zero(), deadline - Clock::now());
|
||||
const std::uint64_t revision = automatic_timer_revision;
|
||||
automatic_timer = automatic_render_loop()->runAfter(
|
||||
std::chrono::duration<double>(delay).count(),
|
||||
[session = weak_from_this(), revision] {
|
||||
if (auto value = session.lock())
|
||||
value->automatic_render_due(revision);
|
||||
});
|
||||
}
|
||||
void automatic_render_due(std::uint64_t revision) {
|
||||
std::uint64_t generation{};
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
if (revision != automatic_timer_revision)
|
||||
return;
|
||||
automatic_timer = trantor::InvalidTimerId;
|
||||
++automatic_timer_revision;
|
||||
if (!automatic_low_latency || !scene || !scene->can_render_automatically() || render_task_pending)
|
||||
return;
|
||||
render_task_pending = true;
|
||||
generation = scene_generation;
|
||||
}
|
||||
auto self = shared_from_this();
|
||||
try {
|
||||
detail::automatic_render_executor().enqueue([self = std::move(self), generation] {
|
||||
self->run_automatic_render(generation);
|
||||
});
|
||||
} catch (...) {
|
||||
std::lock_guard lock(mutex);
|
||||
render_task_pending = false;
|
||||
automatic_render_exception = ::renderive::error::capture(
|
||||
"arming automatic gallery render", std::current_exception());
|
||||
disarm_automatic_render();
|
||||
}
|
||||
}
|
||||
void run_automatic_render(std::uint64_t generation) {
|
||||
const auto started = Clock::now();
|
||||
try {
|
||||
std::lock_guard lock(mutex);
|
||||
if (generation == scene_generation && scene && scene->can_render_automatically()) {
|
||||
const auto error = scene->render_latest_frame();
|
||||
if (error == Gallery_Render_Result::none)
|
||||
last_render_started = started;
|
||||
}
|
||||
render_task_pending = false;
|
||||
arm_automatic_render_locked();
|
||||
} catch (...) {
|
||||
std::lock_guard lock(mutex);
|
||||
render_task_pending = false;
|
||||
automatic_render_exception = ::renderive::error::capture(
|
||||
"running automatic gallery render", std::current_exception());
|
||||
disarm_automatic_render();
|
||||
}
|
||||
}
|
||||
static bool affects_render_schedule(const Web_Event& event) {
|
||||
return std::visit(
|
||||
[](const auto& value) {
|
||||
using T = std::decay_t<decltype(value)>;
|
||||
if constexpr (std::is_same_v<T, Frame_Request>) {
|
||||
return false;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Gallery_Request>) {
|
||||
return value.kind != Gallery_Request_Kind::Catalog &&
|
||||
value.kind != Gallery_Request_Kind::Observe &&
|
||||
value.kind != Gallery_Request_Kind::Refresh &&
|
||||
value.kind != Gallery_Request_Kind::Reset_Monitoring;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
event);
|
||||
}
|
||||
std::unique_ptr<Gallery_Scene_Interface> scene;
|
||||
std::mutex mutex;
|
||||
std::optional<Clock::time_point> last_render_started;
|
||||
std::uint64_t scene_generation{};
|
||||
bool automatic_low_latency{};
|
||||
bool render_task_pending{};
|
||||
std::exception_ptr automatic_render_exception;
|
||||
std::uint64_t session_id{};
|
||||
trantor::TimerId automatic_timer{trantor::InvalidTimerId};
|
||||
std::uint64_t automatic_timer_revision{};
|
||||
std::optional<Web_Response> handle_gallery(const Gallery_Request& request) {
|
||||
if (request.kind == Gallery_Request_Kind::Catalog)
|
||||
return Web_Response{Web_Response_Type::Json, Gallery_Protocol::catalog_json()};
|
||||
@@ -372,10 +201,7 @@ struct Gallery_Plot_Session::Impl : std::enable_shared_from_this<Gallery_Plot_Se
|
||||
Web_Response_Type::Json,
|
||||
Gallery_Protocol::error_json("未知的 gallery case 或 frame_mode", "case")
|
||||
};
|
||||
scene = make_gallery_scene(session_id, open->case_id, open->frame_mode, automatic_low_latency);
|
||||
++scene_generation;
|
||||
last_render_started.reset();
|
||||
arm_automatic_render_locked();
|
||||
scene = make_gallery_scene(session_id, open->case_id, open->frame_mode);
|
||||
return Web_Response{
|
||||
Web_Response_Type::Json,
|
||||
Gallery_Protocol::case_json_from_controls(scene->case_id(), scene->controls().dump(),
|
||||
@@ -390,8 +216,7 @@ struct Gallery_Plot_Session::Impl : std::enable_shared_from_this<Gallery_Plot_Se
|
||||
Gallery_Protocol::error_json("请先发送 gallery_open")
|
||||
};
|
||||
if (request.kind == Gallery_Request_Kind::Observe) {
|
||||
if (update_client_metrics(request.message))
|
||||
arm_automatic_render_locked();
|
||||
static_cast<void>(update_client_metrics(request.message));
|
||||
return Web_Response{
|
||||
Web_Response_Type::Json,
|
||||
Gallery_Protocol::observer_json(
|
||||
@@ -399,8 +224,7 @@ struct Gallery_Plot_Session::Impl : std::enable_shared_from_this<Gallery_Plot_Se
|
||||
};
|
||||
}
|
||||
if (request.kind == Gallery_Request_Kind::Refresh) {
|
||||
if (update_client_metrics(request.message))
|
||||
arm_automatic_render_locked();
|
||||
static_cast<void>(update_client_metrics(request.message));
|
||||
return Web_Response{
|
||||
Web_Response_Type::Json,
|
||||
Gallery_Protocol::case_json_from_controls(
|
||||
@@ -443,8 +267,7 @@ struct Gallery_Plot_Session::Impl : std::enable_shared_from_this<Gallery_Plot_Se
|
||||
Web_Response_Type::Json,
|
||||
Gallery_Protocol::error_json("gallery_action 格式无效")
|
||||
};
|
||||
if (!Gallery_Protocol::action_available(scene->case_id(), scene->frame_mode(),
|
||||
action->id))
|
||||
if (!Gallery_Protocol::action_available(scene->case_id(), action->id))
|
||||
return Web_Response{
|
||||
Web_Response_Type::Json,
|
||||
Gallery_Protocol::error_json(
|
||||
@@ -453,10 +276,7 @@ struct Gallery_Plot_Session::Impl : std::enable_shared_from_this<Gallery_Plot_Se
|
||||
if (action->id == "reset") {
|
||||
const std::string id = scene->case_id();
|
||||
const auto mode = scene->frame_mode();
|
||||
scene = make_gallery_scene(session_id, id, mode, automatic_low_latency);
|
||||
++scene_generation;
|
||||
last_render_started.reset();
|
||||
arm_automatic_render_locked();
|
||||
scene = make_gallery_scene(session_id, id, mode);
|
||||
return Web_Response{
|
||||
Web_Response_Type::Json,
|
||||
Gallery_Protocol::case_json_from_controls(id, scene->controls().dump(),
|
||||
@@ -485,7 +305,7 @@ struct Gallery_Plot_Session::Impl : std::enable_shared_from_this<Gallery_Plot_Se
|
||||
std::optional<std::string> pixels;
|
||||
{
|
||||
auto lock = acquire_foreground_lock();
|
||||
if (!scene)
|
||||
if (!scene || !scene->request_frame())
|
||||
return std::nullopt;
|
||||
const auto encode_started = std::chrono::steady_clock::now();
|
||||
pixels = scene->encode_latest_pixels();
|
||||
@@ -499,55 +319,36 @@ struct Gallery_Plot_Session::Impl : std::enable_shared_from_this<Gallery_Plot_Se
|
||||
return Web_Response{Web_Response_Type::Pixels, std::move(*pixels)};
|
||||
}
|
||||
std::optional<Web_Response> handle(const Web_Event& event) {
|
||||
{
|
||||
std::exception_ptr exception;
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
exception = std::exchange(automatic_render_exception, {});
|
||||
}
|
||||
if (exception)
|
||||
::renderive::error::unexpected(
|
||||
"automatic gallery render", exception);
|
||||
}
|
||||
if (std::holds_alternative<Frame_Request>(event))
|
||||
return handle_frame_request();
|
||||
const bool reschedule = affects_render_schedule(event);
|
||||
std::optional<Web_Response> response;
|
||||
{
|
||||
auto lock = acquire_foreground_lock();
|
||||
response = std::visit(
|
||||
[this](const auto& value) -> std::optional<Web_Response> {
|
||||
using T = std::decay_t<decltype(value)>;
|
||||
if constexpr (std::is_same_v<T, Gallery_Request>) {
|
||||
return handle_gallery(value);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Frame_Request>) {
|
||||
return std::nullopt;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Viewport_Resize>) {
|
||||
if (scene)
|
||||
scene->resize(value.size.width, value.size.height);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Event>) {
|
||||
if (scene)
|
||||
scene->dispatch(Gallery_Input_Event{gallery_input(value)});
|
||||
}
|
||||
else if constexpr (Event_Object<T>) {
|
||||
if (scene)
|
||||
scene->dispatch(Gallery_Input_Event{gallery_input(value)});
|
||||
}
|
||||
auto lock = acquire_foreground_lock();
|
||||
return std::visit(
|
||||
[this](const auto& value) -> std::optional<Web_Response> {
|
||||
using T = std::decay_t<decltype(value)>;
|
||||
if constexpr (std::is_same_v<T, Gallery_Request>) {
|
||||
return handle_gallery(value);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Frame_Request>) {
|
||||
return std::nullopt;
|
||||
},
|
||||
event);
|
||||
if (reschedule)
|
||||
arm_automatic_render_locked();
|
||||
}
|
||||
return response;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Viewport_Resize>) {
|
||||
if (scene)
|
||||
scene->resize(value.size.width, value.size.height);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Event>) {
|
||||
if (scene)
|
||||
scene->dispatch(Gallery_Input_Event{gallery_input(value)});
|
||||
}
|
||||
else if constexpr (Event_Object<T>) {
|
||||
if (scene)
|
||||
scene->dispatch(Gallery_Input_Event{gallery_input(value)});
|
||||
}
|
||||
return std::nullopt;
|
||||
},
|
||||
event);
|
||||
}
|
||||
};
|
||||
Gallery_Plot_Session::Gallery_Plot_Session() : Gallery_Plot_Session(false) {}
|
||||
Gallery_Plot_Session::Gallery_Plot_Session(bool automatic_low_latency)
|
||||
: impl_(std::make_shared<Impl>(automatic_low_latency)) {}
|
||||
Gallery_Plot_Session::Gallery_Plot_Session() : impl_(std::make_shared<Impl>()) {}
|
||||
Gallery_Plot_Session::~Gallery_Plot_Session() = default;
|
||||
std::optional<Web_Response> Gallery_Plot_Session::handle(const Web_Event& event) {
|
||||
return impl_->handle(event);
|
||||
|
||||
Reference in New Issue
Block a user