Follow-ups to the Idle-vs-Error acquisition failure split: - JFJochServices::Stop: raise the critical detector fault BEFORE the ordinary receiver/writer exception. Both can be set at once (detector not idle, then receiver->Stop() also throws); throwing the ordinary one first masked the detector fault and returned the broker to Idle instead of Error, skipping the required re-initialisation. - SLSDetectorWrapper::Stop/Deactivate: rethrow a JFJochException as-is (as Start already does) so InternalStop's JFJochCriticalException is not downgraded to an ordinary JFJochException by the generic catch. - JFJochServices::Start: wrap the whole best-effort receiver cleanup (Cancel + Stop) in the catch so a throwing Cancel cannot replace the original detector exception before it is re-raised. Also fix the "cannot create indexing pool" failure seen when retrying initialize after a failed acquisition start: a failed start leaves the receiver (and its GPU resources) alive until the next Start, but the retried initialize rebuilds the indexer pool first, so the fresh GPU indexer had to coexist with the stale receiver and its init failed. JFJochReceiverService::Indexing now releases the previous receiver before (re)building the pool (safe: only runs when the receiver is idle), which also removes a dangling pool pointer held by that receiver. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
341 lines
12 KiB
C++
341 lines
12 KiB
C++
// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include "JFJochServices.h"
|
|
#include "../common/JFJochException.h"
|
|
#include "../detector_control/SLSDetectorWrapper.h"
|
|
#include "../detector_control/DectrisDetectorWrapper.h"
|
|
|
|
JFJochServices::JFJochServices(Logger &in_logger) : logger(in_logger) {}
|
|
|
|
void JFJochServices::Start(const DiffractionExperiment& experiment,
|
|
const PixelMask &pixel_mask,
|
|
const JFCalibration *calibration) {
|
|
logger.Info("Measurement start for: {}", experiment.GetFilePrefix());
|
|
|
|
cannot_stop_detector = false;
|
|
|
|
if (receiver == nullptr) {
|
|
logger.Info(" Done!");
|
|
return;
|
|
}
|
|
|
|
logger.Info(" ... receiver start");
|
|
if (image_puller)
|
|
image_puller->ResumeAndClear();
|
|
receiver->Start(experiment, pixel_mask, calibration, image_puller);
|
|
|
|
// From here the receiver is running asynchronously. If starting the detector fails, stop the
|
|
// receiver again so the service returns to idle and the run can be retried without
|
|
// re-initialising. The detector failure is then propagated (a critical detector error stays
|
|
// critical, so the broker still goes to the Error state).
|
|
try {
|
|
std::shared_lock ul(detector_mutex);
|
|
if (detector && !experiment.IsUsingInternalPacketGen()) {
|
|
logger.Info(" ... detector start");
|
|
detector->Start(experiment);
|
|
}
|
|
} catch (const std::exception &e) {
|
|
logger.Error(" ... detector failed to start ({}) - stopping receiver", e.what());
|
|
// Best-effort receiver cleanup - it must not replace the original detector exception (which
|
|
// may be critical), so swallow anything it throws and let the throw below re-raise e.
|
|
try {
|
|
receiver->Cancel(false);
|
|
receiver->Stop();
|
|
} catch (const std::exception &stop_error) {
|
|
logger.Warning("Receiver stop after failed start reported: {}", stop_error.what());
|
|
}
|
|
throw;
|
|
}
|
|
|
|
logger.Info(" Done!");
|
|
}
|
|
|
|
void JFJochServices::Off() {
|
|
image_puller.reset();
|
|
|
|
std::unique_ptr<DetectorWrapper> old_detector;
|
|
{
|
|
std::unique_lock ul(detector_mutex);
|
|
old_detector = std::move(detector);
|
|
}
|
|
if (old_detector) {
|
|
old_detector->Deactivate();
|
|
// destroyed here, outside lock
|
|
}
|
|
}
|
|
|
|
void JFJochServices::On(DiffractionExperiment &x) {
|
|
if (x.IsUsingInternalPacketGen() || (receiver == nullptr)) {
|
|
std::unique_lock ul(detector_mutex);
|
|
detector.reset();
|
|
} else {
|
|
logger.Info("Detector on");
|
|
std::unique_ptr<DetectorWrapper> new_detector;
|
|
switch (x.GetDetectorType()) {
|
|
case DetectorType::EIGER:
|
|
case DetectorType::JUNGFRAU:
|
|
new_detector = std::make_unique<SLSDetectorWrapper>();
|
|
image_puller.reset();
|
|
break;
|
|
case DetectorType::DECTRIS:
|
|
new_detector = std::make_unique<DectrisDetectorWrapper>();
|
|
image_puller = std::make_shared<ZMQImagePuller>(x.GetDetectorSetup().GetDECTRISStream2Addr());
|
|
image_puller->Suspend();
|
|
break;
|
|
}
|
|
new_detector->Initialize(x, receiver->GetNetworkConfig());
|
|
{
|
|
std::unique_lock ul(detector_mutex);
|
|
detector = std::move(new_detector);
|
|
}
|
|
logger.Info(" ... done");
|
|
}
|
|
}
|
|
|
|
JFJochServicesOutput JFJochServices::Stop() {
|
|
JFJochServicesOutput ret;
|
|
|
|
std::unique_ptr<JFJochException> exception;
|
|
|
|
bool detector_error = false;
|
|
|
|
if (receiver != nullptr) {
|
|
try {
|
|
{
|
|
std::shared_lock ul(detector_mutex);
|
|
if (detector) {
|
|
logger.Info("Wait for detector idle");
|
|
DetectorState state = detector->GetState();
|
|
while ((!cannot_stop_detector)
|
|
&& ((state == DetectorState::WAITING) || (state == DetectorState::BUSY))) {
|
|
// check detector state every 5 ms
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
state = detector->GetState();
|
|
}
|
|
if (state == DetectorState::IDLE) {
|
|
logger.Info(" ... detector idle");
|
|
receiver->Cancel(true); // cancel silently
|
|
} else {
|
|
logger.Error(" ... detector in error state");
|
|
receiver->Cancel(false);
|
|
detector_error = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
logger.Info("Wait for receiver done");
|
|
ret.receiver_output = receiver->Stop();
|
|
if (image_puller)
|
|
image_puller->Suspend();
|
|
|
|
logger.Info(" ... Receiver efficiency: {} % Max delay: {} Compression ratio {}x",
|
|
static_cast<int>(ret.receiver_output.efficiency * 100.0),
|
|
ret.receiver_output.status.max_receive_delay.value_or(0),
|
|
static_cast<int>(std::round(ret.receiver_output.status.compressed_ratio.value_or(1))));
|
|
|
|
if (ret.receiver_output.efficiency < 1.0) {
|
|
for (int i = 0; i < ret.receiver_output.received_packets.size(); i++) {
|
|
if (ret.receiver_output.received_packets[i] != ret.receiver_output.expected_packets[i])
|
|
logger.Info(" ... Module: {} Packets received: {} out of {}", i,
|
|
ret.receiver_output.received_packets[i], ret.receiver_output.expected_packets[i]);
|
|
}
|
|
}
|
|
|
|
// A writer that broke mid-run (e.g. a lost connection) leaves a truncated file. This is
|
|
// reported to the caller via receiver_output.writer_err (the state machine surfaces it as
|
|
// an error message) - it is not a detector fault, so it does not need re-initialisation.
|
|
|
|
logger.Info(" ... finished with success");
|
|
} catch (const JFJochException &e) {
|
|
logger.Error(" ... finished with error {}", e.what());
|
|
exception = std::make_unique<JFJochException>(e);
|
|
}
|
|
} else {
|
|
logger.Info("No receiver - sleeping for 30 seconds");
|
|
std::this_thread::sleep_for(std::chrono::seconds(30));
|
|
logger.Info("Sleep done");
|
|
}
|
|
|
|
|
|
// A detector fault must win over an ordinary receiver/writer error: it needs re-initialisation,
|
|
// so raise the critical exception first. If only the receiver failed, the run can be retried from
|
|
// Idle, so that ordinary error is raised second.
|
|
if (detector_error)
|
|
throw JFJochCriticalException("Error in detector operation");
|
|
|
|
if (exception)
|
|
throw JFJochException(*exception);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void JFJochServices::Cancel() {
|
|
std::shared_lock ul(detector_mutex);
|
|
if (detector) {
|
|
// Best effort - if detector cannot be stopped, this is OK, important to still stop receiver
|
|
try {
|
|
detector->Stop();
|
|
} catch (...) {
|
|
cannot_stop_detector = true;
|
|
}
|
|
}
|
|
if (receiver != nullptr)
|
|
receiver->Cancel(false);
|
|
}
|
|
|
|
JFJochServices &JFJochServices::Receiver(JFJochReceiverService *input) {
|
|
receiver = input;
|
|
return *this;
|
|
}
|
|
|
|
std::optional<JFJochReceiverStatus> JFJochServices::GetReceiverStatus() const {
|
|
if (receiver == nullptr)
|
|
return {};
|
|
return receiver->GetStatus();
|
|
}
|
|
|
|
std::optional<float> JFJochServices::GetReceiverProgress() const {
|
|
if (receiver == nullptr)
|
|
return {};
|
|
return receiver->GetProgress();
|
|
}
|
|
|
|
MultiLinePlot JFJochServices::GetPlots(const PlotRequest &request) {
|
|
if (receiver == nullptr)
|
|
return {};
|
|
return receiver->GetDataProcessingPlot(request);
|
|
}
|
|
|
|
void JFJochServices::GetPlotRaw(std::vector<float> &v, PlotType type, const std::string &roi) {
|
|
if (receiver != nullptr)
|
|
receiver->GetPlotRaw(v, type, roi);
|
|
}
|
|
|
|
void JFJochServices::SetSpotFindingSettings(const SpotFindingSettings &settings) {
|
|
if (receiver)
|
|
receiver->SetSpotFindingSettings(settings);
|
|
}
|
|
|
|
void JFJochServices::Trigger() {
|
|
std::shared_lock ul(detector_mutex);
|
|
if (detector && (receiver != nullptr))
|
|
detector->Trigger();
|
|
}
|
|
|
|
std::optional<DetectorStatus> JFJochServices::GetDetectorStatus() const {
|
|
std::shared_lock ul(detector_mutex, std::defer_lock);
|
|
if (ul.try_lock_for(std::chrono::milliseconds(500)) && detector)
|
|
return detector->GetStatus();
|
|
return {};
|
|
}
|
|
|
|
std::string JFJochServices::GetPreviewJPEG(const PreviewImageSettings &settings, int64_t image_number) const {
|
|
if (receiver != nullptr)
|
|
return receiver->GetJPEGFromBuffer(settings, image_number);
|
|
else
|
|
return {};
|
|
}
|
|
|
|
std::string JFJochServices::GetPreviewTIFF(int64_t image_number) const {
|
|
if (receiver != nullptr)
|
|
return receiver->GetTIFFFromBuffer(image_number);
|
|
else
|
|
return "";
|
|
}
|
|
|
|
void JFJochServices::ConfigureDetector(const DiffractionExperiment &experiment) {
|
|
std::unique_lock ul(detector_mutex); // While configuring detector ensure exclusive access (even though pointer is not modified here)
|
|
if (detector)
|
|
detector->Configure(experiment);
|
|
}
|
|
|
|
void JFJochServices::LoadInternalGeneratorImage(const DiffractionExperiment &experiment,
|
|
const std::vector<uint16_t> &image,
|
|
uint64_t image_number) {
|
|
if (receiver)
|
|
receiver->LoadInternalGeneratorImage(experiment, image, image_number);
|
|
}
|
|
|
|
void JFJochServices::GetXFELPulseID(std::vector<uint64_t> &v) const {
|
|
if (receiver)
|
|
receiver->GetXFELPulseID(v);
|
|
}
|
|
|
|
void JFJochServices::GetXFELEventCode(std::vector<uint64_t> &v) const {
|
|
if (receiver)
|
|
receiver->GetXFELEventCode(v);
|
|
}
|
|
|
|
std::vector<DeviceStatus> JFJochServices::GetDeviceStatus() const {
|
|
std::vector<DeviceStatus> ret;
|
|
if (receiver)
|
|
ret = receiver->GetDeviceStatus();
|
|
return ret;
|
|
}
|
|
|
|
ZMQPreviewSettings JFJochServices::GetPreviewSocketSettings() {
|
|
if (receiver)
|
|
return receiver->GetPreviewSocketSettings();
|
|
return {};
|
|
}
|
|
|
|
ZMQMetadataSettings JFJochServices::GetMetadataSocketSettings() {
|
|
if (receiver)
|
|
return receiver->GetMetadataSocketSettings();
|
|
return {};
|
|
}
|
|
|
|
void JFJochServices::SetPreviewSocketSettings(const ZMQPreviewSettings &input) {
|
|
if (receiver)
|
|
receiver->PreviewSocketSettings(input);
|
|
}
|
|
|
|
void JFJochServices::SetMetadataSocketSettings(const ZMQMetadataSettings &input) {
|
|
if (receiver)
|
|
receiver->MetadataSocketSettings(input);
|
|
}
|
|
|
|
void JFJochServices::GetStartMessageFromBuffer(std::vector<uint8_t> &v) {
|
|
if (receiver)
|
|
return receiver->GetStartMessageFromBuffer(v);
|
|
}
|
|
|
|
bool JFJochServices::GetImageFromBuffer(std::vector<uint8_t> &v, int64_t image_number) {
|
|
if (receiver)
|
|
return receiver->GetImageFromBuffer(v, image_number);
|
|
return false;
|
|
}
|
|
|
|
ImageBufferStatus JFJochServices::GetImageBufferStatus() const {
|
|
if (receiver)
|
|
return receiver->GetImageBufferStatus();
|
|
else return ImageBufferStatus{.total_slots = 0, .available_slots = 0};
|
|
}
|
|
|
|
void JFJochServices::ClearImageBuffer() const {
|
|
if (receiver)
|
|
receiver->ClearImageBuffer();
|
|
}
|
|
|
|
void JFJochServices::LoadDetectorPixelMask(PixelMask &mask) {
|
|
std::shared_lock ul(detector_mutex);
|
|
if (detector)
|
|
detector->LoadPixelMask(mask);
|
|
}
|
|
|
|
void JFJochServices::SetupIndexing(const IndexingSettings &input) {
|
|
if (receiver)
|
|
receiver->Indexing(input);
|
|
}
|
|
|
|
ImagePusherStatus JFJochServices::GetImagePusherStatus() const {
|
|
if (receiver)
|
|
return receiver->GetImagePusherStatus();
|
|
return {
|
|
.pusher_type = ImagePusherType::None,
|
|
.address = {},
|
|
.connected_writers = 0
|
|
};
|
|
}
|