Broker: log why a DECTRIS run ends in a detector error, reuse one SIMPLON connection
"Error in detector operation" was raised whenever the wait-for-idle loop in JFJochServices::Stop() ended in any state other than idle. For DECTRIS that includes a single failed status request (connect/read timeout, non-200), which GetState() silently turned into NOT_CONNECTED, and a cancel whose disarm failed - none of them distinguishable in the log. - DectrisDetectorWrapper::GetState() logs the failure reason before returning NOT_CONNECTED. - Stop() logs the state that ended the loop, whether the stop command failed, and what the detector reports (DetectorWrapper::GetErrorDescription(); for DECTRIS the SIMPLON state and the detector status "error" list). - Cancel() logs why the stop command failed. - A non-200 GET now reports the HTTP status and body (it printed "Success"). - DectrisSimplonClient keeps one keep-alive httplib::Client, serialised by a mutex, instead of opening a new TCP connection per request. - The wait-for-idle loop polls every 100 ms instead of every 5 ms. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include <catch2/catch_all.hpp>
|
||||
#include <httplib.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
|
||||
#include "../detector_control/DectrisSimplonClient.h"
|
||||
|
||||
// A minimal SIMPLON server: the detector state and error list, and a disarm command that fails.
|
||||
TEST_CASE("DectrisSimplonClient_MockServer", "[detector]") {
|
||||
httplib::Server server;
|
||||
std::mutex m;
|
||||
std::set<int> client_ports;
|
||||
std::string state = "idle";
|
||||
|
||||
server.Get("/detector/api/1.8.0/status/state", [&](const httplib::Request &req, httplib::Response &res) {
|
||||
std::lock_guard lg(m);
|
||||
client_ports.insert(req.remote_port);
|
||||
if (state == "unavailable") {
|
||||
res.status = 503;
|
||||
res.set_content("DCU busy", "text/plain");
|
||||
} else
|
||||
res.set_content(nlohmann::json{{"value", state}}.dump(), "application/json");
|
||||
});
|
||||
server.Get("/detector/api/1.8.0/status/error", [&](const httplib::Request &, httplib::Response &res) {
|
||||
res.set_content(nlohmann::json{{"value", {"overheating"}}}.dump(), "application/json");
|
||||
});
|
||||
server.Post("/detector/api/1.8.0/command/disarm", [&](const httplib::Request &, httplib::Response &res) {
|
||||
res.status = 500;
|
||||
res.set_content("disarm refused", "text/plain");
|
||||
});
|
||||
|
||||
const int port = server.bind_to_any_port("127.0.0.1");
|
||||
REQUIRE(port > 0);
|
||||
struct ServerThread {
|
||||
httplib::Server &server;
|
||||
std::thread thread;
|
||||
explicit ServerThread(httplib::Server &s) : server(s), thread([&s] { s.listen_after_bind(); }) {}
|
||||
~ServerThread() { server.stop(); thread.join(); }
|
||||
} server_thread(server);
|
||||
server.wait_until_ready();
|
||||
|
||||
DectrisSimplonClient client("127.0.0.1", port);
|
||||
|
||||
// Repeated polling reuses one connection instead of opening a new one per request
|
||||
for (int i = 0; i < 10; i++)
|
||||
REQUIRE(client.GetState() == SimplonState::Idle);
|
||||
CHECK(client_ports.size() == 1);
|
||||
|
||||
CHECK(client.GetErrorStatus() == R"(["overheating"])");
|
||||
|
||||
// Failures carry the HTTP status and the body the DCU sent
|
||||
{
|
||||
std::lock_guard lg(m);
|
||||
state = "unavailable";
|
||||
}
|
||||
REQUIRE_THROWS_WITH(client.GetState(), Catch::Matchers::ContainsSubstring("HTTP503")
|
||||
&& Catch::Matchers::ContainsSubstring("DCU busy"));
|
||||
REQUIRE_THROWS_WITH(client.EndAcquisitionFinished(), Catch::Matchers::ContainsSubstring("HTTP500")
|
||||
&& Catch::Matchers::ContainsSubstring("disarm refused"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user