// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #pragma once #include #include #include #include #include #include #include #include #include #include #include "../common/JFJochMessages.h" #include "../common/DiffractionSpot.h" #include "../common/DiffractionExperiment.h" #include "Indexer.h" // When a worker builds its indexers. // // Preconstruct (the default, and what the online service needs): every indexer the requested // algorithm could resolve to is built while the pool is being constructed, so it is resident and // planned before the first frame arrives. Building one means a cuFFT plan plus ~0.6 GB of device // allocation, and the first cuFFT call in a process also pays the library's one-time init (0.3 s // measured); jfjoch_broker cannot have any of that land on the live data path, so holding an indexer // the resolved algorithm may never dispatch to is an accepted cost there. // // OnFirstUse (offline batch - rugnux, jfjoch_viewer): nothing is latency-critical, so the indexer is // built on the first frame that needs it. The algorithm is only resolved from the frame's // DiffractionExperiment (rotation -> FFT, stills with a known cell -> FFBIDX), so preconstructing // leaves each worker holding a fully allocated FFTIndexerGPU - 0.6 GB of cuFFT plan and histograms - // that can never be dispatched to. enum class IndexerConstruction { Preconstruct, OnFirstUse }; class IndexerThread { struct TaskInput { const DiffractionExperiment &experiment; const std::vector &recip; }; // Held by value: with IndexerConstruction::OnFirstUse the worker builds its indexer long after // the constructor returned, and pools are routinely built from a temporary - for instance // IndexerThreadPool(experiment.GetIndexingSettings()), which returns by value. const IndexingSettings settings_; const IndexerConstruction construction_; bool stop = false; enum class TaskState {STARTING, IDLE, READY, RUNNING, COMPLETED, ERROR} state = TaskState::STARTING; std::mutex m; std::condition_variable c_running; std::condition_variable c_start; std::condition_variable c_done; std::unique_ptr result = nullptr; std::unique_ptr task_input = nullptr; std::thread worker_thread; void Worker(int threadid); public: IndexerThread(const IndexingSettings& settings, int threadid, IndexerConstruction construction); ~IndexerThread(); std::unique_ptr Run(const DiffractionExperiment &experiment, const std::vector &recip); void Finalize(); }; class IndexerThreadPool { std::mutex m; std::condition_variable c; std::vector worker_busy; size_t worker_free_count; std::vector> tasks; const int64_t viable_cell_min_spots; const bool blocking; int GetFreeWorker(); public: IndexerThreadPool(const IndexingSettings& settings, IndexerConstruction construction = IndexerConstruction::Preconstruct); IndexerResult Run(const DiffractionExperiment& experiment, const std::vector& recip); };