From 3fe043651bd7f5dba52b0911b0e57b59e8c1b217 Mon Sep 17 00:00:00 2001 From: Ian Chua Date: Wed, 9 Sep 2026 18:40:01 +0800 Subject: [PATCH] fix: moonraker printer agent hang on printer power cut --- src/slic3r/Utils/MoonrakerPrinterAgent.cpp | 28 ++++++++++++++++++++++ src/slic3r/Utils/MoonrakerPrinterAgent.hpp | 7 ++++++ 2 files changed, 35 insertions(+) diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index d2c8781fbe..0defe014f8 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -3,6 +3,7 @@ #include "IPrinterAgent.hpp" #include "libslic3r/Preset.hpp" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Utils.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/DeviceCore/DevFilaSystem.h" #include "slic3r/GUI/DeviceCore/DevManager.h" @@ -2077,6 +2078,14 @@ void MoonrakerPrinterAgent::start_status_stream(const std::string& dev_id, const void MoonrakerPrinterAgent::stop_status_stream() { ws_stop.store(true); + { + // Wake a blocked synchronous ws.read()/ws.write() in run_status_stream(); + // ws_stop by itself is only observed between reads. + std::lock_guard lock(ws_abort_mutex); + if (ws_abort_io) { + ws_abort_io(); + } + } if (ws_thread.joinable()) { ws_thread.join(); } @@ -2113,6 +2122,25 @@ void MoonrakerPrinterAgent::run_status_stream(std::string dev_id, std::string ba stream.connect(results); websocket::stream ws{std::move(stream)}; + + // Allow stop_status_stream() to force this socket shut so a blocked + // synchronous ws.read()/ws.write() returns with an error (Beast's + // expires_after() does not bound synchronous operations). Declared + // after `ws` so the hook is cleared before `ws` is destroyed on every + // exit path (fallthrough, break, exception); ws_abort_mutex keeps the + // hook from running against a half-destroyed `ws`. + ScopeGuard ws_abort_guard([this] { + std::lock_guard lock(ws_abort_mutex); + ws_abort_io = nullptr; + }); + { + std::lock_guard lock(ws_abort_mutex); + ws_abort_io = [&ws] { + beast::error_code ec; + ws.next_layer().socket().shutdown(tcp::socket::shutdown_both, ec); + }; + } + ws.set_option(websocket::stream_base::decorator([&](websocket::request_type& req) { req.set(http::field::user_agent, "OrcaSlicer"); if (!api_key.empty()) { diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp index 8750085181..a662c096df 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp @@ -240,6 +240,13 @@ private: std::atomic ws_last_emit_ms{0}; std::thread ws_thread; + // stop_status_stream() invokes ws_abort_io to wake a blocked synchronous + // ws.read()/ws.write()/handshake in run_status_stream(): ws_stop is only + // observed between reads, and Beast's expires_after() does not bound + // synchronous operations. + std::mutex ws_abort_mutex; + std::function ws_abort_io; // guarded by ws_abort_mutex + // AMS/filament refresh cadence, independent of telemetry dispatch so a steady // stream of status updates can't starve it (ws_last_emit_ms is reset by those). static constexpr uint64_t AMS_REFRESH_INTERVAL_MS = 10000;