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
18using Clock = std::chrono::high_resolution_clock;
19
20// Standard Constructor
22 ISvcLocator* svcLoc)
23 : AthenaHiveEventLoopMgr(name, svcLoc) {}
24
25// Standard Destructor
27
31 // Initialize cluster svc
32 ATH_CHECK(m_clusterSvc.retrieve());
34}
35
41
44StatusCode MPIHiveEventLoopMgr::nextEvent(int maxevt) {
45 // make nextEvent(0) a dummy call
46 if (0 == maxevt) {
47 return StatusCode::SUCCESS;
48 }
49
50 // Reset the application return code.
51 Gaudi::setAppReturnCode(m_appMgrProperty, Gaudi::ReturnCode::Success, true)
52 .ignore();
53 ATH_MSG_INFO("Starting loop on events");
54
55 if (m_clusterSvc->rank() == 0) {
56 return masterEventLoop(maxevt);
57 }
58 return workerEventLoop();
59}
60
63 ATH_MSG_INFO("Running with " << m_clusterSvc->numRanks() << " ranks");
64 // Determine number of events to process
65 int skipEvts = int(m_firstEventIndex.value());
66 if (m_evtSelector != nullptr) {
67 int evt = size();
68 if (evt == -1) {
69 m_clusterSvc->abort();
70 return StatusCode::FAILURE;
71 }
72 if (maxEvt < 0 || (skipEvts + maxEvt) > evt) {
73 maxEvt = evt - skipEvts;
74 }
75 ATH_MSG_INFO("Will be processing " << maxEvt << " events");
76 }
77
78 // Setup worker status DB (Spare one at start)
79 std::vector<bool> workers_done(m_clusterSvc->numRanks(), false);
80 workers_done[0] =
81 true; // Set 0 to true because it doesn't correspond to a worker
82 int num_workers_done = 1; // Init to 1 so we can compare to numRanks
83 std::vector<ClusterMessage::WorkerStatus> statuses(m_clusterSvc->numRanks());
84
85 // Entering event loop
86 m_clusterSvc->barrier();
87 // Note: no ++evt. This is because this is really a message loop, and we don't
88 // want to increment evt if we haven't provided an event
89 auto start = Clock::now();
90 for (int evt = skipEvts; evt < skipEvts + maxEvt;) {
91 ClusterMessage msg = m_clusterSvc->waitReceiveMessage();
92 // Messages we can get are RequestEvent, FinalWorkerStatus, or WorkerError
93 if (msg.messageType == ClusterMessageType::RequestEvent) {
94 ATH_MSG_DEBUG("Starting event " << evt << " on " << msg.source);
95 m_clusterSvc->sendMessage(
96 msg.source, ClusterMessage(ClusterMessageType::ProvideEvent, evt));
97 ++evt;
98 continue;
99 }
100
101 if (msg.messageType == ClusterMessageType::WorkerError) {
102 ATH_MSG_ERROR("Received WorkerError message from " << msg.source);
103 statuses.at(msg.source) = get<ClusterMessage::WorkerStatus>(msg.payload);
104 workers_done.at(msg.source) =
105 true; // If a worker hits an error, it's done
106 ++num_workers_done;
107 for (int i = 1; i < m_clusterSvc->numRanks(); ++i) {
108 if (!workers_done.at(i)) {
109 // Tell workers that aren't done to emergency stop
110 m_clusterSvc->sendMessage(
111 i, ClusterMessage(ClusterMessageType::EmergencyStop));
112 workers_done[i] = true;
113 ++num_workers_done;
114 }
115 }
116 break;
117 }
118
119 if (msg.messageType == ClusterMessageType::FinalWorkerStatus) {
120 ATH_MSG_INFO("Received FinalWorkerStatus from " << msg.source);
121 statuses.at(msg.source) = get<ClusterMessage::WorkerStatus>(msg.payload);
122 workers_done.at(msg.source) = true; // Worker hit end of stream
123 ++num_workers_done;
124 continue;
125 }
126
127 // Other message types are an error
128 ATH_MSG_ERROR("Received unexpected message "
129 << std::format("{}", msg.messageType) << " from "
130 << 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 "
172 << std::format("{}", msg.messageType) << " from "
173 << msg.source);
174 }
175 auto all_done = Clock::now() - start;
176 // Collate status
177 int n_created = 0;
178 int n_skipped = 0;
179 int n_finished = 0;
180
181 StatusCode sc = StatusCode::SUCCESS;
182 int worker_idx = 0;
183 for (const auto& worker_status : statuses) {
184 if (worker_status.status.isFailure() &&
185 worker_status.status != StatusCode(9999)) {
186 sc = worker_status.status;
187 }
188 n_created += worker_status.createdEvents;
189 n_skipped += worker_status.skippedEvents;
190 n_finished += worker_status.finishedEvents;
191
192 if ((worker_idx++) != 0) {
193 ATH_MSG_INFO("Worker " << worker_idx << ": SC " << worker_status.status
194 << ", created " << worker_status.createdEvents
195 << ", skipped " << worker_status.skippedEvents
196 << ", finished " << worker_status.finishedEvents);
197 }
198 }
199
200 ATH_MSG_INFO("Overall: SC " << sc << ", created " << n_created << ", skipped "
201 << n_skipped << ", finished " << n_finished);
202 ATH_MSG_INFO("MASTER: Took " << std::chrono::hh_mm_ss(all_provided)
203 << " to provide all events.");
204 ATH_MSG_INFO("MASTER: Took " << std::chrono::hh_mm_ss(all_done)
205 << " to complete all events.");
206 return sc;
207}
208
211 bool end_of_stream = false;
212 // barrier so all ranks enter message loop together
213 m_clusterSvc->barrier();
214 auto start = Clock::now();
216 while (true) {
217 // Drain the scheduler (wait for at least one event to complete, then free
218 // up completed slots) in two circumstances
219 // 1. Have created exactly one event, so the first event runs to completion
220 // before any more are scheduled
221 // 2. There are no free slots left
222 bool haveFreeSlots =
223 m_schedulerSvc->freeSlots() > 0 && m_whiteboard->freeSlots() > 0;
224 if (!haveFreeSlots || m_nLocalCreatedEvts == 1) {
225 StatusCode sc = drainLocalScheduler();
226 if (sc.isFailure()) {
228 status.status = sc;
229 status.createdEvents = m_nLocalCreatedEvts;
230 status.skippedEvents = m_nLocalSkippedEvts;
231 status.finishedEvents = m_nLocalFinishedEvts;
232 m_clusterSvc->sendMessage(
233 0, ClusterMessage(ClusterMessageType::WorkerError, status));
234 return sc;
235 }
236 }
237
238 auto start_time = Clock::now();
239 m_clusterSvc->sendMessage(0,
240 ClusterMessage(ClusterMessageType::RequestEvent));
241 ClusterMessage msg = m_clusterSvc->waitReceiveMessage();
242 auto request_time = Clock::now() - start_time;
243 if (msg.messageType == ClusterMessageType::EmergencyStop) {
244 // Emergency stop, return FAILURE after fully draining the scheduler to
245 // prevent segfault
246 std::size_t numSlots = m_whiteboard->getNumberOfStores();
247 while (m_schedulerSvc->freeSlots() < numSlots) {
248 // Ignore StatusCode, going to return FAILURE anyway
249 (void)(drainLocalScheduler());
250 }
251 ATH_MSG_ERROR("Received EmergencyStop message!");
252 return StatusCode::FAILURE;
253 }
254
255 if (msg.messageType == ClusterMessageType::EventsDone) {
256 auto loop_time = Clock::now() - start;
257 ATH_MSG_INFO("Worker " << m_clusterSvc->rank() << " DONE. Loop took "
258 << std::chrono::hh_mm_ss(loop_time)
259 << " to process " << m_nLocalCreatedEvts
260 << " events.");
261 // Been told we've reached end
262 // Provide status to master
264 // At end of stream, we need to *fully* drain the scheduler
265 StatusCode sc = StatusCode::SUCCESS;
266 std::size_t numSlots = m_whiteboard->getNumberOfStores();
267 while (sc.isSuccess() && m_schedulerSvc->freeSlots() < numSlots) {
269 }
270 status.status = sc;
271 status.createdEvents = m_nLocalCreatedEvts;
272 status.skippedEvents = m_nLocalSkippedEvts;
273 status.finishedEvents = m_nLocalFinishedEvts;
274 m_clusterSvc->sendMessage(
275 0, ClusterMessage(ClusterMessageType::FinalWorkerStatus, status));
276 return sc;
277 }
278
279 // Any other message other than ProvideEvent would now be an error
280 if (msg.messageType != ClusterMessageType::ProvideEvent ||
281 msg.source != 0) {
282 ATH_MSG_ERROR("Received unexpected message "
283 << std::format("{}", msg.messageType) << " from "
284 << msg.source);
286 status.status = StatusCode::FAILURE;
287 status.createdEvents = m_nLocalCreatedEvts;
288 status.skippedEvents = m_nLocalSkippedEvts;
289 status.finishedEvents = m_nLocalFinishedEvts;
290 m_clusterSvc->sendMessage(
291 0, ClusterMessage(ClusterMessageType::WorkerError, status));
292 return StatusCode::FAILURE;
293 }
294
295 int evt = get<int>(msg.payload);
296 ATH_MSG_INFO("Starting event " << evt);
297 StatusCode sc = insertEvent(
298 evt, end_of_stream,
299 std::chrono::duration_cast<std::chrono::nanoseconds>(request_time)
300 .count());
301 if (sc.isFailure() && !sc.isRecoverable()) {
303 status.status = sc;
304 status.createdEvents = m_nLocalCreatedEvts;
305 status.skippedEvents = m_nLocalSkippedEvts;
306 status.finishedEvents = m_nLocalFinishedEvts;
307 m_clusterSvc->sendMessage(
308 0, ClusterMessage(ClusterMessageType::WorkerError, status));
309 return sc;
310 }
311 if (end_of_stream || m_terminateLoop) {
312 auto loop_time = Clock::now() - start;
313 ATH_MSG_INFO("Worker " << m_clusterSvc->rank() << " DONE. Loop took "
314 << std::chrono::hh_mm_ss(loop_time)
315 << " to process " << m_nLocalCreatedEvts
316 << " events.");
317 // reached end of stream, drain scheduler
319 // At end of stream, we need to *fully* drain the scheduler
320 StatusCode sc = StatusCode::SUCCESS;
321 std::size_t numSlots = m_whiteboard->getNumberOfStores();
322 while (sc.isSuccess() && m_schedulerSvc->freeSlots() < numSlots) {
324 }
325 status.status = sc;
326 status.createdEvents = m_nLocalCreatedEvts;
327 status.skippedEvents = m_nLocalSkippedEvts;
328 status.finishedEvents = m_nLocalFinishedEvts;
329 m_clusterSvc->sendMessage(
330 0, ClusterMessage(ClusterMessageType::FinalWorkerStatus, status));
331 return sc;
332 }
333 }
334}
335
337StatusCode MPIHiveEventLoopMgr::insertEvent(int eventIdx, bool& endOfStream,
338 std::int64_t requestTime_ns) {
339 // fast-forward to event
340 // Create the event context now so next writes into the next slot when
341 // skipping, not the one that's being used
342 endOfStream = false;
343 auto ctx = createEventContext();
344 Gaudi::Hive::setCurrentContext(ctx);
345 ctx.setEvt(
346 eventIdx); // Make the event numbers in the log actually make sense
347 if (!ctx.valid()) {
348 endOfStream = true; // BUG: Doesn't actually mean end of stream. Remove
349 // after making sure!
350 return StatusCode::FAILURE;
351 }
352
353 const std::size_t slot = ctx.slot(); // Need this for later
354 // Codex (GPT 5.6-sol) identified use of seek should be reverted since that
355 // function isn't implemented properly
356 if (m_evtSelector != nullptr) {
357 const int nToJump = (eventIdx - 1) - m_evtSelectorCurrentPos;
358 if (nToJump < 0) {
359 ATH_MSG_ERROR("Cannot jump backwards by " << nToJump << " events");
360 return StatusCode::FAILURE;
361 }
362 if (nToJump > 0) {
363 ATH_CHECK(m_evtSelector->next(*m_evtContext, nToJump));
364 }
365 m_evtSelectorCurrentPos = eventIdx;
366 }
367 // execute event
368 StatusCode sc = executeEvent(std::move(ctx));
369 const auto evtID =
370 m_lastEventContext.eventID(); // Set in AthenaHiveEventLoopMgr
371 m_clusterSvc->log_addEvent(eventIdx, evtID.run_number(), evtID.event_number(),
372 requestTime_ns, slot);
373
374 if (sc.isRecoverable()) {
376 } else if (sc.isSuccess()) {
378 }
379 return sc;
380}
381
385
386 StatusCode sc(StatusCode::SUCCESS);
387
388 // maybe we can do better
389 std::vector<std::unique_ptr<EventContext>> finishedEvtContexts;
390
391 EventContext* finishedEvtContext(nullptr);
392
393 // Here we wait not to loose cpu resources
394 ATH_MSG_DEBUG("drainLocalScheduler: [" << m_nLocalFinishedEvts
395 << "] Waiting for a context");
396 sc = m_schedulerSvc->popFinishedEvent(finishedEvtContext);
397
398 // We got past it: cache the pointer
399 if (sc.isSuccess()) {
400 ATH_MSG_DEBUG("drainLocalScheduler: scheduler not empty: Context "
401 << finishedEvtContext);
402 finishedEvtContexts.emplace_back(finishedEvtContext);
403 } else {
404 // no more events left in scheduler to be drained
405 ATH_MSG_DEBUG("drainLocalScheduler: scheduler empty");
406 return StatusCode::SUCCESS;
407 }
408
409 // Let's see if we can pop other event contexts
410 while (m_schedulerSvc->tryPopFinishedEvent(finishedEvtContext).isSuccess()) {
411 finishedEvtContexts.emplace_back(finishedEvtContext);
412 }
413
414 // Now we flush them
415 StatusCode fail(StatusCode::SUCCESS);
416 for (auto& thisFinishedEvtContext : finishedEvtContexts) {
417 if (!thisFinishedEvtContext) {
418 ATH_MSG_FATAL("Detected nullptr ctxt while clearing WB!");
419 fail = StatusCode::FAILURE;
420 continue;
421 }
422
423 // Update event log
424 m_clusterSvc->log_completeEvent(
425 thisFinishedEvtContext->evt(),
426 thisFinishedEvtContext->eventID().run_number(),
427 thisFinishedEvtContext->eventID().event_number(),
428 m_aess->eventStatus(*thisFinishedEvtContext));
429
430 if (m_aess->eventStatus(*thisFinishedEvtContext) != EventStatus::Success) {
431 ATH_MSG_ERROR("Failed event detected on "
432 << thisFinishedEvtContext << " w/ fail mode: "
433 << m_aess->eventStatus(*thisFinishedEvtContext));
436 if (m_contiguousFailedEvts >= 3 || m_totalFailedEvts >= 10) {
437 // If we have 3 contiguous failed events or 10 total, end the job
438 fail = StatusCode::FAILURE;
439 continue;
440 }
441 } else {
442 // Event succeeded, reset contiguous failed events
444 }
445
446 EventID::number_type n_run(0);
447 EventID::event_number_t n_evt(0);
448
449 if (m_whiteboard->selectStore(thisFinishedEvtContext->slot()).isSuccess()) {
450 n_run = thisFinishedEvtContext->eventID().run_number();
451 n_evt = thisFinishedEvtContext->eventID().event_number();
452 } else {
453 ATH_MSG_ERROR("DrainSched: unable to select store "
454 << thisFinishedEvtContext->slot());
455 thisFinishedEvtContext.reset();
456 fail = StatusCode::FAILURE;
457 continue;
458 }
459
460 // Some code still needs global context in addition to that passed in the
461 // incident
462 Gaudi::Hive::setCurrentContext(*thisFinishedEvtContext);
463 m_incidentSvc->fireIncident(
464 Incident(name(), IncidentType::EndProcessing, *thisFinishedEvtContext));
465
466 ATH_MSG_DEBUG("Clearing slot "
467 << thisFinishedEvtContext->slot() << " (event "
468 << thisFinishedEvtContext->evt() << ") of the whiteboard");
469
470 StatusCode sc = clearWBSlot(thisFinishedEvtContext->slot());
471 if (!sc.isSuccess()) {
472 ATH_MSG_ERROR("Whiteboard slot " << thisFinishedEvtContext->slot()
473 << " could not be properly cleared");
474 if (fail != StatusCode::FAILURE) {
475 fail = sc;
476 }
477 thisFinishedEvtContext.reset();
478 continue;
479 }
480
482
483 writeHistograms().ignore();
484 ++m_proc;
485
486 if (m_doEvtHeartbeat) {
487 if (!m_useTools) {
488 ATH_MSG_INFO(" ===>>> done processing event #"
489 << n_evt << ", run #" << n_run << " on slot "
490 << thisFinishedEvtContext->slot() << ", " << m_proc
491 << " events processed so far <<<===");
492 } else {
493 ATH_MSG_INFO(" ===>>> done processing event #"
494 << n_evt << ", run #" << n_run << " on slot "
495 << thisFinishedEvtContext->slot() << ", " << m_nev
496 << " events read and " << m_proc
497 << " events processed so far <<<===");
498 }
499 std::ofstream outfile("eventLoopHeartBeat.txt");
500 if (!outfile) {
501 ATH_MSG_ERROR(" unable to open: eventLoopHeartBeat.txt");
502 fail = StatusCode::FAILURE;
503 thisFinishedEvtContext.reset();
504 continue;
505 }
506 outfile << " done processing event #" << n_evt << ", run #" << n_run
507 << " " << m_nev << " events read so far <<<===" << std::endl;
508 outfile.close();
509 }
510
511 ATH_MSG_DEBUG("drainLocalScheduler thisFinishedEvtContext: "
512 << thisFinishedEvtContext);
513
514 thisFinishedEvtContext.reset();
515 }
516
517 return fail;
518}
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_ERROR(x)
#define ATH_MSG_FATAL(x)
#define ATH_MSG_INFO(x)
#define ATH_MSG_DEBUG(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:37
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.