Files
Jungfraujoch/tests/JFJochImageBufferReaderTest.cpp
T
leonarski_fandClaude Opus 5.5 d356cc5da5 ImageBuffer reader: one waiting thread at a time looks at the buffer
Every rugnux worker waiting for an image called ImageBuffer::GetImage every
millisecond, and GetStatus every 100 polls. Each call takes the buffer's mutex
and scans every slot, so sixteen waiting workers contended with the forward
threads for the lock they put the images in under (GetImageSlot, ReadyToSend).

Now one waiting thread at a time asks the buffer, at most every 2 ms, with a
single GetStatus, and notes the images it holds in a per-reader bitmap; the
other waiters sleep on a condition variable and are woken when it is
refreshed or the collection ends. GetImage is called once, for an image known
to be there (or given up on). MAX_IMAGE_LAG and Ended() mean what they did.
ImageBuffer itself is unchanged.

The test gains eight readers waiting at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:13:03 +02:00

112 lines
4.4 KiB
C++

// 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 <cstring>
#include <future>
#include <thread>
#include "../reader/JFJochImageBufferReader.h"
#include "../common/ZeroCopyReturnValue.h"
#include "../frame_serialize/CBORStream2Serializer.h"
// The receiver's way of putting an image in the buffer: serialize the message into a slot, hand it out.
static void PutImage(ImageBuffer &buffer, const DiffractionExperiment &experiment, int64_t number,
const std::vector<int32_t> &pixels) {
DataMessage msg{};
msg.number = number;
msg.image = CompressedImage(pixels, experiment.GetXPixelsNum(), experiment.GetYPixelsNum());
auto loc = buffer.GetImageSlot();
REQUIRE(loc != nullptr);
CBORStream2Serializer serializer((uint8_t *) loc->GetImage(), experiment.GetImageBufferLocationSize());
serializer.SerializeImage(msg);
loc->SetImageNumber(number);
loc->SetImageSize(serializer.GetBufferSize());
loc->SetIndexed(false);
loc->ReadyToSend();
loc->release();
}
TEST_CASE("JFJochImageBufferReader", "[JFJochReader]") {
DiffractionExperiment experiment(DetJF(1));
experiment.ImagesPerTrigger(200);
ImageBuffer buffer(16 * experiment.GetImageBufferLocationSize());
buffer.StartMeasurement(experiment);
StartMessage start{};
experiment.FillMessage(start);
REQUIRE(start.number_of_images == 200);
JFJochImageBufferReader reader(buffer);
reader.Open(start);
REQUIRE(reader.GetNumberOfImages() == 200);
REQUIRE(reader.GetDataset());
const size_t npixel = experiment.GetXPixelsNum() * experiment.GetYPixelsNum();
std::vector<int32_t> pixels(npixel, 7);
pixels[123] = 4567;
PutImage(buffer, experiment, 0, pixels);
JFJochReaderRawImage raw;
REQUIRE(reader.ReadRawImage(0, raw));
REQUIRE(raw.image.GetWidth() == experiment.GetXPixelsNum());
REQUIRE(raw.image.GetHeight() == experiment.GetYPixelsNum());
REQUIRE(raw.image_buffer.size() == npixel * sizeof(int32_t));
int32_t value;
memcpy(&value, raw.image.GetCompressed() + 123 * sizeof(int32_t), sizeof(int32_t));
REQUIRE(value == 4567);
auto image = reader.LoadImage(0);
REQUIRE(image);
REQUIRE(image->ImageData().number == 0);
REQUIRE_THROWS(reader.ReadRawImage(200, raw));
// An image not there yet is waited for: the read returns once the receiver puts it in.
auto pending = std::async(std::launch::async, [&] {
JFJochReaderRawImage r;
return reader.ReadRawImage(1, r);
});
REQUIRE(pending.wait_for(std::chrono::milliseconds(50)) == std::future_status::timeout);
PutImage(buffer, experiment, 1, pixels);
REQUIRE(pending.wait_for(std::chrono::seconds(5)) == std::future_status::ready);
REQUIRE(pending.get());
// Many readers waiting at once, as rugnux's workers do: each gets its image as it comes.
std::vector<std::future<bool>> waiting;
for (int64_t i = 2; i < 10; i++)
waiting.push_back(std::async(std::launch::async, [&reader, i] {
JFJochReaderRawImage r;
return reader.ReadRawImage(i, r) && r.image.GetWidth() > 0;
}));
for (int64_t i = 2; i < 10; i++)
PutImage(buffer, experiment, i, pixels);
for (auto &w: waiting) {
REQUIRE(w.wait_for(std::chrono::seconds(5)) == std::future_status::ready);
REQUIRE(w.get());
}
// Images arrive in order, give or take a few: one still missing when an image MAX_IMAGE_LAG past
// it is there is lost, while one nearer may still come.
PutImage(buffer, experiment, 150, pixels);
auto lost = std::async(std::launch::async, [&] {
JFJochReaderRawImage r;
return reader.ReadRawImage(150 - JFJochImageBufferReader::MAX_IMAGE_LAG, r);
});
REQUIRE(lost.wait_for(std::chrono::seconds(5)) == std::future_status::ready);
REQUIRE(!lost.get());
// After the end, an image that never came is not waited for.
auto missing = std::async(std::launch::async, [&] {
JFJochReaderRawImage r;
return reader.ReadRawImage(150 - JFJochImageBufferReader::MAX_IMAGE_LAG + 1, r);
});
REQUIRE(missing.wait_for(std::chrono::milliseconds(300)) == std::future_status::timeout);
reader.Ended();
REQUIRE(missing.wait_for(std::chrono::seconds(5)) == std::future_status::ready);
REQUIRE(!missing.get());
REQUIRE(buffer.Finalize(std::chrono::microseconds(1)));
}