Broker: a collection can be followed from /start, its start message comes in the event stream

With a DECTRIS detector the broker starts the writers - and so learned the collection's start
message - only when the detector's own start message arrived, after /start had returned. A reader
started right after /start found no collection (404) and had to wait for, and race with, the
detector.

- /start registers the collection in LiveCollection (Expect) as it is accepted, so GET /live/events
  is accepted from then on. The image pusher fills in the start message when it starts the writers
  (Begin) - at once on the FPGA path, when the detector's start message arrives on the DECTRIS path.
- The event stream's first event is now `start`, carrying that start message (CBOR, base64) with
  run number, file prefix, image count and images per file; it replaces `collection`. Until it is
  known the stream only keeps alive. file and end follow as before.
- However the measurement thread ends, it ends a collection the pusher did not end (EndIfOpen), so a
  cancel or a failure before the detector streamed reaches the reader as `end` with an error. The
  wait is event-driven: it ends with the start message or with the measurement.
- GET /live/start.cbor is removed: the stream carries the start message, one route less.
- rugnux takes the start message from the stream (BrokerFeed::WaitForStart), so it can be started
  right after /start; files reported before it attached the reader are replayed to it.

Tests: LiveCollection_ExpectedAtStart; BrokerFeed_StartFromEventStream (an event stream whose start
message comes late, then a file and the end).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SVmAWnzCmRKAXVUCdc4iNi
This commit is contained in:
2026-10-08 17:03:09 +02:00
co-authored by Claude Opus 5.5
parent 72c7409327
commit 2981aedd3e
13 changed files with 315 additions and 164 deletions
+1 -2
View File
@@ -69,7 +69,7 @@ TEST_CASE("JFJochBrokerHttp_BearerTokens", "[broker]") {
for (const char *path: {"/statistics/data_collection", "/result/scan", "/image_buffer/start.cbor",
"/image_buffer/image.cbor?id=0", "/image_buffer/image.tiff?id=0",
"/image_buffer/image.jpeg?id=0", "/preview/plot?type=bkg_estimate",
"/preview/plot.bin?type=bkg_estimate", "/live/start.cbor", "/live/events"}) {
"/preview/plot.bin?type=bkg_estimate", "/live/events"}) {
INFO(path);
auto res = client.Get(path);
REQUIRE(res);
@@ -100,7 +100,6 @@ TEST_CASE("JFJochBrokerHttp_BearerTokens", "[broker]") {
}
SECTION("with no receiver there is no collection to follow") {
REQUIRE(client.Get("/live/start.cbor", good)->status == 409);
REQUIRE(client.Get("/live/events", good)->status == 409);
}