ATLAS Offline Software
Loading...
Searching...
No Matches
WebdaqInfoSvc.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
5#include "WebdaqInfoSvc.h"
6
7#include "GaudiKernel/IIncidentSvc.h"
9
10#include "hltinterface/ContainerFactory.h"
11#include "hltinterface/GenericHLTContainer.h"
12#include "webdaq/webdaq.hpp"
13
14#include <nlohmann/json.hpp>
15
16#include <cstdlib>
17#include <utility>
18
25 std::shared_ptr<hltinterface::GenericHLTContainer> cont;
27 std::string fullName;
29 std::string typeName;
31 nlohmann::json snapshot;
33 std::mutex mtx;
36 std::atomic<bool> dirty{false};
37};
38
39/**************************************************************************************/
40
41StatusCode WebdaqInfoSvc::initialize ATLAS_NOT_THREAD_SAFE()
42{
43 //Retrieve environment variables
44 const char* tdaq_partition_cstr = std::getenv("TDAQ_PARTITION");
45 if (tdaq_partition_cstr != nullptr) {
46 m_partition = std::string(tdaq_partition_cstr);
47 ATH_MSG_INFO("Partition: " << m_partition);
48 } else {
49 ATH_MSG_ERROR("TDAQ_PARTITION environment variable not set");
50 return StatusCode::FAILURE;
51 }
52 const char* tdaqWebdaqBase_cstr = std::getenv("TDAQ_WEBDAQ_BASE");
53 if (tdaqWebdaqBase_cstr != nullptr) {
54 m_tdaqWebdaqBase = std::string(tdaqWebdaqBase_cstr);
55 ATH_MSG_INFO("TDAQ_WEBDAQ_BASE value: " << m_tdaqWebdaqBase);
56 } else {
57 ATH_MSG_ERROR("TDAQ_WEBDAQ_BASE environment variable not set! "
58 "Is needed for the IS publication through webdaq");
59 return StatusCode::FAILURE;
60 }
61 const char* tdaq_is_server = std::getenv("TDAQ_IS_SERVER");
62 if (tdaq_is_server != nullptr) {
63 m_tdaqIsServerName = std::string(tdaq_is_server);
64 } else {
65 m_tdaqIsServerName = m_isServerName.value();
66 }
67 ATH_MSG_INFO("TDAQ_IS_SERVER value: " << m_tdaqIsServerName);
68
69 // Install as the IInfoRegister singleton
70 hltinterface::IInfoRegister::setInstance(this, /*force=*/true);
71 ATH_MSG_INFO("Installed WebdaqInfoSvc as the hltinterface::IInfoRegister singleton");
72
73 // Provide a default ContainerFactory to build GenericHLTContainer instances via ContainerFactory::getInstance()
74 m_factory = hltinterface::ContainerFactory::getInstance();
75 if (m_factory) {
76 ATH_MSG_INFO("Reusing the hltinterface::ContainerFactory installed by another component");
77 }
78 else {
79 m_factory = std::make_shared<hltinterface::ContainerFactory>();
80 hltinterface::ContainerFactory::setInstance(m_factory);
81 ATH_MSG_INFO("Installed the default hltinterface::ContainerFactory");
82 }
83
84 // Start publishing thread after fork
85 ServiceHandle<IIncidentSvc> incSvc("IncidentSvc", name());
86 ATH_CHECK( incSvc.retrieve() );
87 incSvc->addListener(this, AthenaInterprocess::UpdateAfterFork::type());
88 return StatusCode::SUCCESS;
89}
90
91/**************************************************************************************/
92
93void WebdaqInfoSvc::handle(const Incident& incident)
94{
95 if (incident.type() == AthenaInterprocess::UpdateAfterFork::type()) {
96 ATH_MSG_INFO("Going to initialize the IS publishing thread (interval: "
97 << m_intervalSeconds.value() << " s)");
98 m_thread = std::thread(&WebdaqInfoSvc::publishingTask, this);
99 }
100}
101
102/**************************************************************************************/
103
105{
106 ATH_MSG_DEBUG("Stopping IS publishing task");
107 m_stopFlag = true;
108 if (m_thread.joinable()) {
109 ATH_MSG_DEBUG("Going to join the IS publishing thread");
110 try {
111 m_thread.join();
112 }
113 catch (const std::exception& e) {
114 ATH_MSG_ERROR("Failed to join the IS publishing thread: " << e.what());
115 return StatusCode::FAILURE;
116 }
117 }
118
119 ATH_MSG_DEBUG("Final flush of registered IS objects");
120 for (const auto& h : holders()) {
121 publishOne(*h);
122 }
123 return StatusCode::SUCCESS;
124}
125
126/**************************************************************************************/
127
129{
130 ATH_MSG_INFO("finalize");
131
132 // Uninstall ourselves.
133 if (hltinterface::IInfoRegister::instance() == this) {
134 hltinterface::IInfoRegister::setInstance(nullptr, /*force=*/true);
135 }
136
137 std::scoped_lock lk(m_objsMtx);
138 m_objs.clear();
139 return StatusCode::SUCCESS;
140}
141
142/**************************************************************************************/
143
144// cppcheck-suppress passedByValue; signature fixed by hltinterface::IInfoRegister
145bool WebdaqInfoSvc::registerObject(const std::string publishPath,
146 std::shared_ptr<hltinterface::GenericHLTContainer> obj)
147{
148 if (!obj) {
149 ATH_MSG_WARNING("registerObject called with null container");
150 return false;
151 }
152
153 // Key on the full IS name: the publish path alone is not unique, producers conventionally
154 // register under the same one (e.g. "/HLTObjects/") and would otherwise collide.
155 const std::string full = publishPath + obj->getObjName();
156
157 std::scoped_lock lk(m_objsMtx);
158 if (m_objs.find(full) != m_objs.end()) {
159 ATH_MSG_WARNING("IS object " << full << " is already registered");
160 return false;
161 }
162
163 auto h = std::make_shared<Holder>();
164 h->cont = obj;
165 h->fullName = full;
166 h->typeName = obj->getTypeName();
167 m_objs.emplace(full, std::move(h));
168
169 ATH_MSG_INFO("Registered IS object " << full
170 << " (type=" << obj->getTypeName() << ")"
171 << " for publication on " << m_tdaqIsServerName
172 << " in partition " << m_partition);
173 return true;
174}
175
176/**************************************************************************************/
177
178// cppcheck-suppress passedByValue; signature fixed by hltinterface::IInfoRegister
179bool WebdaqInfoSvc::releaseObject(const std::string fullName)
180{
181 std::scoped_lock lk(m_objsMtx);
182 auto it = m_objs.find(fullName);
183 if (it == m_objs.end()) {
184 ATH_MSG_WARNING("releaseObject: " << fullName << " is not registered");
185 return false;
186 }
187 m_objs.erase(it);
188 ATH_MSG_DEBUG("Released IS object " << fullName);
189 return true;
190}
191
192/**************************************************************************************/
193
195{
196 std::vector<std::shared_ptr<Holder>> all;
197 std::scoped_lock lk(m_objsMtx);
198 all.reserve(m_objs.size());
199 for (const auto& [_, h] : m_objs) {
200 all.push_back(h);
201 }
202 return all;
203}
204
205/**************************************************************************************/
206
207bool WebdaqInfoSvc::endEvent(const boost::property_tree::ptree&)
208{
209 // The producer has finished mutating the in-memory container.
210 // Serialise each container into its snapshot here (the producer still holds its lock), then mark it dirty so that the
211 // publishing thread can ship the snapshot without touching the container itself.
212 for (const auto& h : holders()) {
213 try {
214 nlohmann::json j = serializeContainer(*h->cont);
215 {
216 std::scoped_lock hlk(h->mtx);
217 h->snapshot = std::move(j);
218 }
219 h->dirty.store(true, std::memory_order_release);
220 }
221 catch (const std::exception& e) {
222 ATH_MSG_WARNING("Failed to serialise IS object " << h->fullName << ": " << e.what());
223 }
224 }
225 return true;
226}
227
228/**************************************************************************************/
229
231{
232 ATH_MSG_INFO("Started webdaq IS publishing task for partition " << m_partition
233 << ", IS server " << m_tdaqIsServerName);
234
235 while (!m_stopFlag) {
236 // Collect the holders that have been touched since the last cycle
237 std::vector<std::shared_ptr<Holder>> dirtyObjs;
238 for (const auto& h : holders()) {
239 if (h->dirty.exchange(false, std::memory_order_acq_rel)) {
240 dirtyObjs.push_back(h);
241 }
242 }
243 for (const auto& h : dirtyObjs) {
244 // Keep the object marked dirty if the publication failed, so that it is retried next cycle
245 if (!publishOne(*h)) {
246 h->dirty.store(true, std::memory_order_release);
247 }
248 if (m_stopFlag) break;
249 }
250 // Contrary to WebdaqHistSvc we don't use a sync to a global period.
251 TrigServices::conditionedSleep(std::chrono::seconds(m_intervalSeconds.value()), m_stopFlag);
252 }
253 ATH_MSG_INFO("Webdaq IS publishing task stopped");
254}
255
256/**************************************************************************************/
257
258nlohmann::json WebdaqInfoSvc::serializeContainer(hltinterface::GenericHLTContainer& cont)
259{
260 using GHC = hltinterface::GenericHLTContainer;
261 nlohmann::json j;
262
263 // Only INT/FLOAT/INTVEC/FLOATVEC are supported: GenericHLTContainer::getFieldNames() throws
264 // std::range_error for STRING/STRINGVEC, so those fields cannot be enumerated at all.
265 auto names = cont.getFieldNames(GHC::INT);
266 for (size_t i = 0; i < names.size(); ++i) j[names[i]] = cont.getIntField(i);
267
268 names = cont.getFieldNames(GHC::FLOAT);
269 for (size_t i = 0; i < names.size(); ++i) j[names[i]] = cont.getFloatField(i);
270
271 names = cont.getFieldNames(GHC::INTVEC);
272 for (size_t i = 0; i < names.size(); ++i) j[names[i]] = cont.getIntVecField(i);
273
274 names = cont.getFieldNames(GHC::FLOATVEC);
275 for (size_t i = 0; i < names.size(); ++i) j[names[i]] = cont.getFloatVecField(i);
276
277 return j;
278}
279
280/**************************************************************************************/
281
283{
284 nlohmann::json j;
285 {
286 std::scoped_lock lk(h.mtx);
287 j = h.snapshot;
288 }
289
290 // Registered but never filled (no endEvent yet): do not overwrite the IS object with a null value
291 if (j.is_null()) {
292 ATH_MSG_DEBUG("No snapshot available for IS object " << h.fullName << ", skipping publication");
293 return true;
294 }
295
296 ATH_MSG_DEBUG("Publishing IS object " << h.fullName
297 << " (type=" << h.typeName << ") to "
298 << m_tdaqIsServerName << "@" << m_partition);
299
300 try {
301 if (!webdaq::is::put(m_partition, m_tdaqIsServerName, h.fullName,
302 h.typeName, j)) {
303 ATH_MSG_WARNING("webdaq::is::put failed for " << h.fullName);
304 return false;
305 }
306 }
307 catch (const std::exception& e) {
308 ATH_MSG_WARNING("webdaq::is::put threw for " << h.fullName << ": " << e.what());
309 return false;
310 }
311 return true;
312}
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_ERROR(x)
#define ATH_MSG_INFO(x)
#define ATH_MSG_WARNING(x)
#define ATH_MSG_DEBUG(x)
StatusCode WebdaqInfoSvc::initialize ATLAS_NOT_THREAD_SAFE()
Install fatal handler with default options.
Header file for AthHistogramAlgorithm.
static const std::string & type()
Incident type.
Definition Incidents.h:49
std::thread m_thread
Background publishing thread + stop flag.
virtual StatusCode finalize() override
bool publishOne(Holder &h) const
Send the pre-built snapshot via webdaq::is::put.
virtual bool endEvent(const boost::property_tree::ptree &) override
std::mutex m_objsMtx
virtual bool registerObject(const std::string publishPath, std::shared_ptr< hltinterface::GenericHLTContainer > obj) override
std::unordered_map< std::string, std::shared_ptr< Holder > > m_objs
Registered IS objects, keyed by full IS name (publishPath + object name).
std::atomic< bool > m_stopFlag
virtual bool releaseObject(const std::string fullName) override
De-register an object.
std::string m_tdaqIsServerName
virtual void handle(const Incident &incident) override
void publishingTask() const
Background loop that periodically publishes containers.
std::string m_partition
virtual StatusCode stop() override
std::vector< std::shared_ptr< Holder > > holders() const
Copy of the registered holders, taken under m_objsMtx.
static nlohmann::json serializeContainer(hltinterface::GenericHLTContainer &cont)
Serialise all supported fields into a JSON object.
Gaudi::Property< unsigned int > m_intervalSeconds
void conditionedSleep(std::chrono::milliseconds duration, const std::atomic< bool > &stopFlag)
Sleep for duration or until stopFlag is set, whichever comes first.
STL namespace.
One entry per registered IS object.
std::string fullName
Name to publish under: publish path + container object name.
std::string typeName
IS type name.
std::mutex mtx
Protects snapshot only. It is written by the event thread in endEvent() and read by the publishing th...
std::shared_ptr< hltinterface::GenericHLTContainer > cont
The producer container. The service only ever reads it.
std::atomic< bool > dirty
Set when a new snapshot is stored, and claimed by the publishing thread so a snapshot is published on...
nlohmann::json snapshot
JSON image of the container, rebuilt on every endEvent() and shipped by the publishing thread.