ATLAS Offline Software
Loading...
Searching...
No Matches
MPIHiveEventLoopMgr.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
5
6// Gaudi includes
7#include "GaudiKernel/AppReturnCode.h"
8
9// Utilities
12
13// Standard Library
14#include <chrono>
15#include <fstream>
16#include <string>
17#include <print>
18
19using Clock = std::chrono::high_resolution_clock;
20
21// Standard Constructor
23 ISvcLocator* svcLoc)
24 : AthenaHiveEventLoopMgr(name, svcLoc) {}
25
26// Standard Destructor
28
32 // Initialize cluster svc
33 ATH_CHECK(m_clusterSvc.retrieve());
35}
36
42
45StatusCode MPIHiveEventLoopMgr::nextEvent(int maxevt) {
46 // make nextEvent(0) a dummy call
47 if (0 == maxevt) {
48 return StatusCode::SUCCESS;
49 }
50
51 // Reset the application return code.
52 Gaudi::setAppReturnCode(m_appMgrProperty, Gaudi::ReturnCode::Success, true)
53 .ignore();
54 ATH_MSG_INFO("Starting loop on events");
55
56 if (m_clusterSvc->rank() == 0) {
57 return masterEventLoop(maxevt);
58 }
59 return workerEventLoop();
60}
61
64 ATH_MSG_INFO("Running with " << m_clusterSvc->numRanks() << " ranks");
65 // Determine number of events to process
66 int skipEvts = int(m_firstEventIndex.value());
67 if (m_evtSelector != nullptr) {
68 int evt = size();
69 if (evt == -1) {
70 m_clusterSvc->abort();
71 return StatusCode::FAILURE;
72 }
73 if (maxEvt < 0 || (skipEvts + maxEvt) > evt) {
74 maxEvt = evt - skipEvts;
75 }
76 ATH_MSG_INFO("Will be processing " << maxEvt << " events");
77 }
78
79 // Setup worker status DB (Spare one at start)
80 std::vector<bool> workers_done(m_clusterSvc->numRanks(), false);
81 workers_done[0] =
82 true; // Set 0 to true because it doesn't correspond to a worker
83 int num_workers_done = 1; // Init to 1 so we can compare to numRanks
84 std::vector<ClusterMessage::WorkerStatus> statuses(m_clusterSvc->numRanks());
85
86 // Entering event loop
87 m_clusterSvc->barrier();
88 // Note: no ++evt. This is because this is really a message loop, and we don't
89 // want to increment evt if we haven't provided an event
90 auto start = Clock::now();
91 for (int evt = skipEvts; evt < skipEvts + maxEvt;) {
92 ClusterMessage msg = m_clusterSvc->waitReceiveMessage();
93 // Messages we can get are RequestEvent, FinalWorkerStatus, or WorkerError
94 if (msg.messageType == ClusterMessageType::RequestEvent) {
95 ATH_MSG_DEBUG("Starting event " << evt << " on " << msg.source);
96 m_clusterSvc->sendMessage(
97 msg.source, ClusterMessage(ClusterMessageType::ProvideEvent, evt));
98 ++evt;
99 continue;
100 }
101
102 if (msg.messageType == ClusterMessageType::WorkerError) {
103 ATH_MSG_ERROR("Received WorkerError message from " << msg.source);
104 statuses.at(msg.source) = get<ClusterMessage::WorkerStatus>(msg.payload);
105 workers_done.at(msg.source) =
106 true; // If a worker hits an error, it's done
107 ++num_workers_done;
108 for (int i = 1; i < m_clusterSvc->numRanks(); ++i) {
109 if (!workers_done.at(i)) {
110 // Tell workers that aren't done to emergency stop
111 m_clusterSvc->sendMessage(
112 i, ClusterMessage(ClusterMessageType::EmergencyStop));
113 workers_done[i] = true;
114 ++num_workers_done;
115 }
116 }
117 break;
118 }
119
120 if (msg.messageType == ClusterMessageType::FinalWorkerStatus) {
121 ATH_MSG_INFO("Received FinalWorkerStatus from " << msg.source);
122 statuses.at(msg.source) = get<ClusterMessage::WorkerStatus>(msg.payload);
123 workers_done.at(msg.source) = true; // Worker hit end of stream
124 ++num_workers_done;
125 continue;
126 }
127
128 // Other message types are an error
129 ATH_MSG_ERROR("Received unexpected message {} from {}",
130 msg.messageType, msg.source);
131 }
132 auto all_provided = Clock::now() - start;
133 ATH_MSG_INFO("Provided all events to workers, waiting for them to complete.");
134 // Event loop done, tell remaining workers
135 while (num_workers_done < m_clusterSvc->numRanks()) {
136 ClusterMessage msg = m_clusterSvc->waitReceiveMessage();
137 // Messages we can get are RequestEvent, FinalWorkerStatus, or WorkerError
138 if (msg.messageType == ClusterMessageType::RequestEvent) {
139 m_clusterSvc->sendMessage(msg.source,
140 ClusterMessage(ClusterMessageType::EventsDone));
141 continue;
142 }
143
144 if (msg.messageType == ClusterMessageType::WorkerError) {
145 ATH_MSG_ERROR("Received WorkerError message from {}", msg.source);
146 statuses.at(msg.source) = get<ClusterMessage::WorkerStatus>(msg.payload);
147 workers_done.at(msg.source) =
148 true; // If a worker hits an error, it's done
149 ++num_workers_done;
150 for (int i = 1; i < m_clusterSvc->numRanks(); ++i) {
151 if (!workers_done.at(i)) {
152 // Tell workers that aren't done to emergency stop
153 m_clusterSvc->sendMessage(
154 i, ClusterMessage(ClusterMessageType::EmergencyStop));
155 workers_done[i] = true;
156 ++num_workers_done;
157 }
158 }
159 break;
160 }
161
162 if (msg.messageType == ClusterMessageType::FinalWorkerStatus) {
163 ATH_MSG_INFO("Received FinalWorkerStatus from {}", msg.source);
164 statuses.at(msg.source) = get<ClusterMessage::WorkerStatus>(msg.payload);
165 workers_done.at(msg.source) = true; // Told worker we're done
166 ++num_workers_done;
167 continue;
168 }
169
170 // Other message types are an error
171 ATH_MSG_ERROR("Received unexpected message {} from {}",
172 msg.messageType, msg.source);
173 }
174 auto all_done = Clock::now() - start;
175 // Collate status
176 int n_created = 0;
177 int n_skipped = 0;
178 int n_finished = 0;
179
180 StatusCode sc = StatusCode::SUCCESS;
181 int worker_idx = 0;
182 for (const auto& worker_status : statuses) {
183 if (worker_status.status.isFailure() &&
184 worker_status.status != StatusCode(9999)) {
185 sc = worker_status.status;
186 }
187 n_created += worker_status.createdEvents;
188 n_skipped += worker_status.skippedEvents;
189 n_finished += worker_status.finishedEvents;
190
191 if ((worker_idx++) != 0) {
192 ATH_MSG_INFO("Worker " << worker_idx << ": SC " << worker_status.status
193 << ", created " << worker_status.createdEvents
194 << ", skipped " << worker_status.skippedEvents
195 << ", finished " << worker_status.finishedEvents);
196 }
197 }
198
199 ATH_MSG_INFO("Overall: SC " << sc << ", created " << n_created << ", skipped "
200 << n_skipped << ", finished " << n_finished);
201 ATH_MSG_INFO("MASTER: Took {} to provide all events.",
202 std::chrono::hh_mm_ss(all_provided));
203 ATH_MSG_INFO("MASTER: Took {} to complete all events.",
204 std::chrono::hh_mm_ss(all_done));
205 return sc;
206}
207
210 bool end_of_stream = false;
211 // barrier so all ranks enter message loop together
212 m_clusterSvc->barrier();
213 auto start = Clock::now();
215 while (true) {
216 // Drain the scheduler (wait for at least one event to complete, then free
217 // up completed slots) in two circumstances
218 // 1. Have created exactly one event, so the first event runs to completion
219 // before any more are scheduled
220 // 2. There are no free slots left
221 bool haveFreeSlots =
222 m_schedulerSvc->freeSlots() > 0 && m_whiteboard->freeSlots() > 0;
223 if (!haveFreeSlots || m_nLocalCreatedEvts == 1) {
224 StatusCode sc = drainLocalScheduler();
225 if (sc.isFailure()) {
227 status.status = sc;
228 status.createdEvents = m_nLocalCreatedEvts;
229 status.skippedEvents = m_nLocalSkippedEvts;
230 status.finishedEvents = m_nLocalFinishedEvts;
231 m_clusterSvc->sendMessage(
232 0, ClusterMessage(ClusterMessageType::WorkerError, status));
233 return sc;
234 }
235 }
236
237 auto start_time = Clock::now();
238 m_clusterSvc->sendMessage(0,
239 ClusterMessage(ClusterMessageType::RequestEvent));
240 ClusterMessage msg = m_clusterSvc->waitReceiveMessage();
241 auto request_time = Clock::now() - start_time;
242 if (msg.messageType == ClusterMessageType::EmergencyStop) {
243 // Emergency stop, return FAILURE after fully draining the scheduler to
244 // prevent segfault
245 std::size_t numSlots = m_whiteboard->getNumberOfStores();
246 while (m_schedulerSvc->freeSlots() < numSlots) {
247 // Ignore StatusCode, going to return FAILURE anyway
248 (void)(drainLocalScheduler());
249 }
250 ATH_MSG_ERROR("Received EmergencyStop message!");
251 return StatusCode::FAILURE;
252 }
253
254 if (msg.messageType == ClusterMessageType::EventsDone) {
255 auto loop_time = Clock::now() - start;
256 ATH_MSG_INFO("Worker {} DONE. Loop took {} to process {} events.",
257 m_clusterSvc->rank(),
258 std::chrono::hh_mm_ss(loop_time),
260 // Been told we've reached end
261 // Provide status to master
263 // At end of stream, we need to *fully* drain the scheduler
264 StatusCode sc = StatusCode::SUCCESS;
265 std::size_t numSlots = m_whiteboard->getNumberOfStores();
266 while (sc.isSuccess() && m_schedulerSvc->freeSlots() < numSlots) {
268 }
269 status.status = sc;
270 status.createdEvents = m_nLocalCreatedEvts;
271 status.skippedEvents = m_nLocalSkippedEvts;
272 status.finishedEvents = m_nLocalFinishedEvts;
273 m_clusterSvc->sendMessage(
274 0, ClusterMessage(ClusterMessageType::FinalWorkerStatus, status));
275 return sc;
276 }
277
278 // Any other message other than ProvideEvent would now be an error
279 if (msg.messageType != ClusterMessageType::ProvideEvent ||
280 msg.source != 0) {
281 ATH_MSG_ERROR("Received unexpected message {} from {}",
282 msg.messageType, msg.source);
284 status.status = StatusCode::FAILURE;
285 status.createdEvents = m_nLocalCreatedEvts;
286 status.skippedEvents = m_nLocalSkippedEvts;
287 status.finishedEvents = m_nLocalFinishedEvts;
288 m_clusterSvc->sendMessage(
289 0, ClusterMessage(ClusterMessageType::WorkerError, status));
290 return StatusCode::FAILURE;
291 }
292
293 int evt = get<int>(msg.payload);
294 ATH_MSG_INFO("Starting event {}", evt);
295 StatusCode sc = insertEvent(
296 evt, end_of_stream,
297 std::chrono::duration_cast<std::chrono::nanoseconds>(request_time)
298 .count());
299 if (sc.isFailure() && !sc.isRecoverable()) {
301 status.status = sc;
302 status.createdEvents = m_nLocalCreatedEvts;
303 status.skippedEvents = m_nLocalSkippedEvts;
304 status.finishedEvents = m_nLocalFinishedEvts;
305 m_clusterSvc->sendMessage(
306 0, ClusterMessage(ClusterMessageType::WorkerError, status));
307 return sc;
308 }
309 if (end_of_stream || m_terminateLoop) {
310 auto loop_time = Clock::now() - start;
311 ATH_MSG_INFO("Worker {} DONE. Loop took to process events.",
312 m_clusterSvc->rank(),
313 std::chrono::hh_mm_ss(loop_time),
315 // reached end of stream, drain scheduler
317 // At end of stream, we need to *fully* drain the scheduler
318 StatusCode sc = StatusCode::SUCCESS;
319 std::size_t numSlots = m_whiteboard->getNumberOfStores();
320 while (sc.isSuccess() && m_schedulerSvc->freeSlots() < numSlots) {
322 }
323 status.status = sc;
324 status.createdEvents = m_nLocalCreatedEvts;
325 status.skippedEvents = m_nLocalSkippedEvts;
326 status.finishedEvents = m_nLocalFinishedEvts;
327 m_clusterSvc->sendMessage(
328 0, ClusterMessage(ClusterMessageType::FinalWorkerStatus, status));
329 return sc;
330 }
331 }
332}
333
335StatusCode MPIHiveEventLoopMgr::insertEvent(int eventIdx, bool& endOfStream,
336 std::int64_t requestTime_ns) {
337 // fast-forward to event
338 // Create the event context now so next writes into the next slot when
339 // skipping, not the one that's being used
340 endOfStream = false;
341 auto ctx = createEventContext();
342 Gaudi::Hive::setCurrentContext(ctx);
343 ctx.setEvt(
344 eventIdx); // Make the event numbers in the log actually make sense
345 if (!ctx.valid()) {
346 endOfStream = true; // BUG: Doesn't actually mean end of stream. Remove
347 // after making sure!
348 return StatusCode::FAILURE;
349 }
350
351 const std::size_t slot = ctx.slot(); // Need this for later
352 // Codex (GPT 5.6-sol) identified use of seek should be reverted since that
353 // function isn't implemented properly
354 if (m_evtSelector != nullptr) {
355 const int nToJump = (eventIdx - 1) - m_evtSelectorCurrentPos;
356 if (nToJump < 0) {
357 ATH_MSG_ERROR("Cannot jump backwards by {} events", nToJump);
358 return StatusCode::FAILURE;
359 }
360 if (nToJump > 0) {
361 ATH_CHECK(m_evtSelector->next(*m_evtContext, nToJump));
362 }
363 m_evtSelectorCurrentPos = eventIdx;
364 }
365 // execute event
366 StatusCode sc = executeEvent(std::move(ctx));
367 const auto evtID =
368 m_lastEventContext.eventID(); // Set in AthenaHiveEventLoopMgr
369 m_clusterSvc->log_addEvent(eventIdx, evtID.run_number(), evtID.event_number(),
370 requestTime_ns, slot);
371
372 if (sc.isRecoverable()) {
374 } else if (sc.isSuccess()) {
376 }
377 return sc;
378}
379
383
384 StatusCode sc(StatusCode::SUCCESS);
385
386 // maybe we can do better
387 std::vector<std::unique_ptr<EventContext>> finishedEvtContexts;
388
389 EventContext* finishedEvtContext(nullptr);
390
391 // Here we wait not to loose cpu resources
392 ATH_MSG_DEBUG("drainLocalScheduler: [{}] Waiting for a context",
394 sc = m_schedulerSvc->popFinishedEvent(finishedEvtContext);
395
396 // We got past it: cache the pointer
397 if (sc.isSuccess()) {
398 ATH_MSG_DEBUG("drainLocalScheduler: scheduler not empty: Context "
399 << finishedEvtContext);
400 finishedEvtContexts.emplace_back(finishedEvtContext);
401 } else {
402 // no more events left in scheduler to be drained
403 ATH_MSG_DEBUG("drainLocalScheduler: scheduler empty");
404 return StatusCode::SUCCESS;
405 }
406
407 // Let's see if we can pop other event contexts
408 while (m_schedulerSvc->tryPopFinishedEvent(finishedEvtContext).isSuccess()) {
409 finishedEvtContexts.emplace_back(finishedEvtContext);
410 }
411
412 // Now we flush them
413 StatusCode fail(StatusCode::SUCCESS);
414 for (auto& thisFinishedEvtContext : finishedEvtContexts) {
415 if (!thisFinishedEvtContext) {
416 ATH_MSG_FATAL("Detected nullptr ctxt while clearing WB!");
417 fail = StatusCode::FAILURE;
418 continue;
419 }
420
421 // Update event log
422 m_clusterSvc->log_completeEvent(
423 thisFinishedEvtContext->evt(),
424 thisFinishedEvtContext->eventID().run_number(),
425 thisFinishedEvtContext->eventID().event_number(),
426 m_aess->eventStatus(*thisFinishedEvtContext));
427
428 if (m_aess->eventStatus(*thisFinishedEvtContext) != EventStatus::Success) {
429 ATH_MSG_ERROR("Failed event detected on "
430 << thisFinishedEvtContext << " w/ fail mode: "
431 << m_aess->eventStatus(*thisFinishedEvtContext));
434 if (m_contiguousFailedEvts >= 3 || m_totalFailedEvts >= 10) {
435 // If we have 3 contiguous failed events or 10 total, end the job
436 fail = StatusCode::FAILURE;
437 continue;
438 }
439 } else {
440 // Event succeeded, reset contiguous failed events
442 }
443
444 EventID::number_type n_run(0);
445 EventID::event_number_t n_evt(0);
446
447 if (m_whiteboard->selectStore(thisFinishedEvtContext->slot()).isSuccess()) {
448 n_run = thisFinishedEvtContext->eventID().run_number();
449 n_evt = thisFinishedEvtContext->eventID().event_number();
450 } else {
451 ATH_MSG_ERROR("DrainSched: unable to select store {}",
452 thisFinishedEvtContext->slot());
453 thisFinishedEvtContext.reset();
454 fail = StatusCode::FAILURE;
455 continue;
456 }
457
458 // Some code still needs global context in addition to that passed in the
459 // incident
460 Gaudi::Hive::setCurrentContext(*thisFinishedEvtContext);
461 m_incidentSvc->fireIncident(
462 Incident(name(), IncidentType::EndProcessing, *thisFinishedEvtContext));
463
464 ATH_MSG_DEBUG("Clearing slot {} (event {}) of the whiteboard",
465 thisFinishedEvtContext->slot(),
466 thisFinishedEvtContext->evt());
467
468 StatusCode sc = clearWBSlot(thisFinishedEvtContext->slot());
469 if (!sc.isSuccess()) {
470 ATH_MSG_ERROR("Whiteboard slot {} could not be properly cleared",
471 thisFinishedEvtContext->slot());
472 if (fail != StatusCode::FAILURE) {
473 fail = sc;
474 }
475 thisFinishedEvtContext.reset();
476 continue;
477 }
478
480
481 writeHistograms().ignore();
482 ++m_proc;
483
484 if (m_doEvtHeartbeat) {
485 if (!m_useTools) {
486 ATH_MSG_INFO(" ===>>> done processing event #{}, run #{} on slot {}, {} events processed so far <<<===",
487 n_evt, n_run, thisFinishedEvtContext->slot(), m_proc);
488 } else {
489 ATH_MSG_INFO(" ===>>> done processing event #{}, run #{} on slot {}, {} events read and {} events processed so far <<<===",
490 n_evt, n_run, thisFinishedEvtContext->slot(), m_nev, m_proc);
491
492 }
493 std::ofstream outfile("eventLoopHeartBeat.txt");
494 if (!outfile) {
495 ATH_MSG_ERROR(" unable to open: eventLoopHeartBeat.txt");
496 fail = StatusCode::FAILURE;
497 thisFinishedEvtContext.reset();
498 continue;
499 }
500 std::println (outfile, " done processing event #{}, run #{} {} events read so far <<<===",
501 n_evt, n_run, m_nev);
502 outfile.close();
503 }
504
505 ATH_MSG_DEBUG("drainLocalScheduler thisFinishedEvtContext: "
506 << thisFinishedEvtContext);
507
508 thisFinishedEvtContext.reset();
509 }
510
511 return fail;
512}
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_DEBUG(x,...)
#define ATH_MSG_ERROR(x,...)
#define ATH_MSG_INFO(x,...)
#define ATH_MSG_FATAL(x,...)
static Double_t sc
std::chrono::high_resolution_clock Clock
The MPI event loop manager.
IEvtSelector * m_evtSelector
Reference to the Event Selector.
virtual StatusCode initialize() override
implementation of IAppMgrUI::initalize
SmartIF< IHiveWhiteBoard > m_whiteboard
Reference to the Whiteboard interface.
virtual int size() override
Return the size of the collection.
SmartIF< IAlgExecStateSvc > m_aess
Reference to the Algorithm Execution State Svc.
virtual const std::string & name() const override
unsigned int m_nev
events processed
IIncidentSvc_t m_incidentSvc
Reference to the incident service.
SmartIF< IProperty > m_appMgrProperty
Property interface of ApplicationMgr.
MsgStream & msg() const
The standard message stream.
EvtContext * m_evtContext
Gaudi event selector Context (may be used as a cursor by the evt selector).
AthenaHiveEventLoopMgr(const std::string &nam, ISvcLocator *svcLoc)
Standard Constructor.
virtual StatusCode finalize() override
implementation of IAppMgrUI::finalize
virtual StatusCode executeEvent(EventContext &&ctx) override
implementation of IEventProcessor::executeEvent(void* par)
virtual EventContext createEventContext() override
Create event context.
StatusCode clearWBSlot(int evtSlot)
Clear a slot in the WB.
virtual StatusCode writeHistograms(bool force=false)
Dump out histograms as needed.
SmartIF< IScheduler > m_schedulerSvc
A shortcut for the scheduler.
EventIDBase::number_type number_type
Definition EventID.h:36
StatusCode workerEventLoop()
Worker event loop (runs on worker, requests events over MPI).
StatusCode drainLocalScheduler()
Drain the local scheduler of any (at least one) completed events.
ServiceHandle< IMPIClusterSvc > m_clusterSvc
Reference to the MPIClusterSvc.
MPIHiveEventLoopMgr(const std::string &name, ISvcLocator *svcLoc)
Standard Constructor.
virtual StatusCode finalize() override
implementation of IAppMgrUI::finalize
virtual ~MPIHiveEventLoopMgr()
Standard Destructor.
virtual StatusCode nextEvent(int maxevt) override
implementation of IAppMgrUI::nextEvent. maxevt==0 returns immediately
StatusCode masterEventLoop(int maxEvt)
Master event loop (runs on master, provides events over MPI).
virtual StatusCode initialize() override
implementation of IAppMgrUI::initalize
StatusCode insertEvent(int eventIdx, bool &endOfStream, std::int64_t requestTime_ns)
Insert an event into the local scheduler.
UnsignedIntegerProperty m_firstEventIndex
T * get(TKey *tobj)
get a TObject* from a TKey* (why can't a TObject be a TKey?)
Definition hcg.cxx:132
int count(std::string s, const std::string &regx)
count how many occurances of a regx are in a string
Definition hcg.cxx:148
A class describing a message sent between nodes in a cluster.