Project
Loading...
Searching...
No Matches
DataProcessingDevice.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
13#include <atomic>
33#include "Framework/InputSpan.h"
34#if defined(__APPLE__) || defined(NDEBUG)
35#define O2_SIGNPOST_IMPLEMENTATION
36#endif
37#include "Framework/Signpost.h"
50
51#include "DecongestionService.h"
54#include "DataRelayerHelpers.h"
55#include "Headers/DataHeader.h"
57
58#include <Framework/Tracing.h>
59
60#include <fairmq/Parts.h>
61#include <fairmq/Socket.h>
62#include <fairmq/ProgOptions.h>
63#include <fairmq/shmem/Message.h>
64#include <Configuration/ConfigurationInterface.h>
65#include <Configuration/ConfigurationFactory.h>
66#include <Monitoring/Monitoring.h>
67#include <TMessage.h>
68#include <TClonesArray.h>
69
70#include <fmt/ostream.h>
71#include <algorithm>
72#include <vector>
73#include <numeric>
74#include <memory>
75#include <uv.h>
76#include <execinfo.h>
77#include <sstream>
78#include <boost/property_tree/json_parser.hpp>
79
80// Formatter to avoid having to rewrite the ostream operator for the enum
81namespace fmt
82{
83template <>
86} // namespace fmt
87
88// A log to use for general device logging
90// A log to use for general device logging
92// Special log to keep track of the lifetime of the parts
94// Stream which keeps track of the calibration lifetime logic
96// Special log to track the async queue behavior
98// Special log to track the forwarding requests
100// Special log to track CCDB related requests
102// Special log to track task scheduling
104
105using namespace o2::framework;
106using ConfigurationInterface = o2::configuration::ConfigurationInterface;
108
109constexpr int DEFAULT_MAX_CHANNEL_AHEAD = 128;
110
111namespace o2::framework
112{
113
114template <>
118
122{
123 auto* state = (DeviceState*)handle->data;
124 state->loopReason |= DeviceState::TIMER_EXPIRED;
125}
126
128{
129 auto* state = (DeviceState*)s->data;
131}
132
133DeviceSpec const& getRunningDevice(RunningDeviceRef const& running, ServiceRegistryRef const& services)
134{
135 auto& devices = services.get<o2::framework::RunningWorkflowInfo const>().devices;
136 return devices[running.index];
137}
138
144
146 : mRunningDevice{running},
147 mConfigRegistry{nullptr},
148 mServiceRegistry{registry}
149{
150 GetConfig()->Subscribe<std::string>("dpl", [&registry = mServiceRegistry](const std::string& key, std::string value) {
151 if (key == "cleanup") {
153 auto& deviceState = ref.get<DeviceState>();
154 int64_t cleanupCount = deviceState.cleanupCount.load();
155 int64_t newCleanupCount = std::stoll(value);
156 if (newCleanupCount <= cleanupCount) {
157 return;
158 }
159 deviceState.cleanupCount.store(newCleanupCount);
160 for (auto& info : deviceState.inputChannelInfos) {
161 fair::mq::Parts parts;
162 while (info.channel->Receive(parts, 0)) {
163 LOGP(debug, "Dropping {} parts", parts.Size());
164 if (parts.Size() == 0) {
165 break;
166 }
167 }
168 }
169 }
170 });
171
172 std::function<void(const fair::mq::State)> stateWatcher = [this, &registry = mServiceRegistry](const fair::mq::State state) -> void {
174 auto& deviceState = ref.get<DeviceState>();
175 auto& control = ref.get<ControlService>();
176 auto& callbacks = ref.get<CallbackService>();
177 control.notifyDeviceState(fair::mq::GetStateName(state));
179
180 if (deviceState.nextFairMQState.empty() == false) {
181 auto state = deviceState.nextFairMQState.back();
182 (void)this->ChangeState(state);
183 deviceState.nextFairMQState.pop_back();
184 }
185 };
186
187 // 99 is to execute DPL callbacks last
188 this->SubscribeToStateChange("99-dpl", stateWatcher);
189
190 auto* poolSizeEnv = getenv("DPL_THREADPOOL_SIZE");
191 // 0 (or unset): synchronous execution on the main thread.
192 // N > 0: N concurrent async streams; I/O runs on the main thread while
193 // computation runs on N pool threads.
194 size_t numStreams = poolSizeEnv ? std::max(0, std::atoi(poolSizeEnv)) : 0;
195 mStreams.resize(std::max(numStreams, 1UL));
196 mHandles.resize(std::max(numStreams, 1UL));
197
198 ServiceRegistryRef ref{mServiceRegistry};
199
200 mAwakeHandle = (uv_async_t*)malloc(sizeof(uv_async_t));
201 auto& state = ref.get<DeviceState>();
202 assert(state.loop);
203 int res = uv_async_init(state.loop, mAwakeHandle, on_communication_requested);
204 mAwakeHandle->data = &state;
205 if (res < 0) {
206 LOG(error) << "Unable to initialise subscription";
207 }
208
210 SubscribeToNewTransition("dpl", [wakeHandle = mAwakeHandle](fair::mq::Transition t) {
211 int res = uv_async_send(wakeHandle);
212 if (res < 0) {
213 LOG(error) << "Unable to notify subscription";
214 }
215 LOG(debug) << "State transition requested";
216 });
217}
218
219// Callback to execute the processing. Receives and relays data (doPrepare)
220// happens on the main thread before this is queued, so we only dispatch here.
221void run_callback(uv_work_t* handle)
222{
223 auto* task = (TaskStreamInfo*)handle->data;
224 auto ref = ServiceRegistryRef{*task->registry, ServiceRegistry::globalStreamSalt(task->id.index + 1)};
225 // We create a new signpost interval for this specific data processor. Same id, same data processor.
226 auto& dataProcessorContext = ref.get<DataProcessorContext>();
227 O2_SIGNPOST_ID_FROM_POINTER(sid, device, &dataProcessorContext);
228 O2_SIGNPOST_START(device, sid, "run_callback", "Starting run callback on stream %d", task->id.index);
230 O2_SIGNPOST_END(device, sid, "run_callback", "Done processing data for stream %d", task->id.index);
231}
232
233// Once the processing in a thread is done, this is executed on the main thread.
234void run_completion(uv_work_t* handle, int status)
235{
236 auto* task = (TaskStreamInfo*)handle->data;
237 // Notice that the completion, while running on the main thread, still
238 // has a salt which is associated to the actual stream which was doing the computation
239 auto ref = ServiceRegistryRef{*task->registry, ServiceRegistry::globalStreamSalt(task->id.index + 1)};
240 auto& state = ref.get<DeviceState>();
241 auto& quotaEvaluator = ref.get<ComputingQuotaEvaluator>();
242
243 using o2::monitoring::Metric;
244 using o2::monitoring::Monitoring;
245 using o2::monitoring::tags::Key;
246 using o2::monitoring::tags::Value;
247
248 static std::function<void(ComputingQuotaOffer const&, ComputingQuotaStats&)> reportConsumedOffer = [ref](ComputingQuotaOffer const& accumulatedConsumed, ComputingQuotaStats& stats) {
249 auto& dpStats = ref.get<DataProcessingStats>();
250 stats.totalConsumedBytes += accumulatedConsumed.sharedMemory;
251 // For now we give back the offer if we did not use it completely.
252 // In principle we should try to run until the offer is fully consumed.
253 stats.totalConsumedTimeslices += std::min<int64_t>(accumulatedConsumed.timeslices, 1);
254
255 dpStats.updateStats({static_cast<short>(ProcessingStatsId::SHM_OFFER_BYTES_CONSUMED), DataProcessingStats::Op::Set, stats.totalConsumedBytes});
256 dpStats.updateStats({static_cast<short>(ProcessingStatsId::TIMESLICE_OFFER_NUMBER_CONSUMED), DataProcessingStats::Op::Set, stats.totalConsumedTimeslices});
257 dpStats.processCommandQueue();
258 assert(stats.totalConsumedBytes == dpStats.metrics[(short)ProcessingStatsId::SHM_OFFER_BYTES_CONSUMED]);
259 assert(stats.totalConsumedTimeslices == dpStats.metrics[(short)ProcessingStatsId::TIMESLICE_OFFER_NUMBER_CONSUMED]);
260 };
261
262 static std::function<void(ComputingQuotaOffer const&, ComputingQuotaStats const&)> reportExpiredOffer = [ref](ComputingQuotaOffer const& offer, ComputingQuotaStats const& stats) {
263 auto& dpStats = ref.get<DataProcessingStats>();
264 dpStats.updateStats({static_cast<short>(ProcessingStatsId::RESOURCE_OFFER_EXPIRED), DataProcessingStats::Op::Set, stats.totalExpiredOffers});
265 dpStats.updateStats({static_cast<short>(ProcessingStatsId::ARROW_BYTES_EXPIRED), DataProcessingStats::Op::Set, stats.totalExpiredBytes});
266 dpStats.updateStats({static_cast<short>(ProcessingStatsId::TIMESLICE_NUMBER_EXPIRED), DataProcessingStats::Op::Set, stats.totalExpiredTimeslices});
267 dpStats.processCommandQueue();
268 };
269
270 for (auto& consumer : state.offerConsumers) {
271 quotaEvaluator.consume(task->id.index, consumer, reportConsumedOffer);
272 }
273 state.offerConsumers.clear();
274 quotaEvaluator.handleExpired(reportExpiredOffer);
275 quotaEvaluator.dispose(task->id.index);
276 task->running = false;
277}
278
279// Context for polling
281 enum struct PollerState : char { Stopped,
283 Connected,
284 Suspended };
285 char const* name = nullptr;
286 uv_loop_t* loop = nullptr;
288 DeviceState* state = nullptr;
289 fair::mq::Socket* socket = nullptr;
291 int fd = -1;
292 bool read = true;
294};
295
296void on_socket_polled(uv_poll_t* poller, int status, int events)
297{
298 auto* context = (PollerContext*)poller->data;
299 assert(context);
300 O2_SIGNPOST_ID_FROM_POINTER(sid, sockets, poller);
301 context->state->loopReason |= DeviceState::DATA_SOCKET_POLLED;
302 switch (events) {
303 case UV_READABLE: {
304 O2_SIGNPOST_EVENT_EMIT(sockets, sid, "socket_state", "Data pending on socket for channel %{public}s", context->name);
305 context->state->loopReason |= DeviceState::DATA_INCOMING;
306 } break;
307 case UV_WRITABLE: {
308 O2_SIGNPOST_END(sockets, sid, "socket_state", "Socket connected for channel %{public}s", context->name);
309 if (context->read) {
310 O2_SIGNPOST_START(sockets, sid, "socket_state", "Socket connected for read in context %{public}s", context->name);
311 uv_poll_start(poller, UV_READABLE | UV_DISCONNECT | UV_PRIORITIZED, &on_socket_polled);
312 context->state->loopReason |= DeviceState::DATA_CONNECTED;
313 } else {
314 O2_SIGNPOST_START(sockets, sid, "socket_state", "Socket connected for write for channel %{public}s", context->name);
315 context->state->loopReason |= DeviceState::DATA_OUTGOING;
316 // If the socket is writable, fairmq will handle the rest, so we can stop polling and
317 // just wait for the disconnect.
318 uv_poll_start(poller, UV_DISCONNECT | UV_PRIORITIZED, &on_socket_polled);
319 }
320 context->pollerState = PollerContext::PollerState::Connected;
321 } break;
322 case UV_DISCONNECT: {
323 O2_SIGNPOST_END(sockets, sid, "socket_state", "Socket disconnected in context %{public}s", context->name);
324 } break;
325 case UV_PRIORITIZED: {
326 O2_SIGNPOST_EVENT_EMIT(sockets, sid, "socket_state", "Socket prioritized for context %{public}s", context->name);
327 } break;
328 }
329 // We do nothing, all the logic for now stays in DataProcessingDevice::doRun()
330}
331
332void on_out_of_band_polled(uv_poll_t* poller, int status, int events)
333{
334 O2_SIGNPOST_ID_FROM_POINTER(sid, sockets, poller);
335 auto* context = (PollerContext*)poller->data;
336 context->state->loopReason |= DeviceState::OOB_ACTIVITY;
337 if (status < 0) {
338 LOGP(fatal, "Error while polling {}: {}", context->name, status);
339 uv_poll_start(poller, UV_WRITABLE, &on_out_of_band_polled);
340 }
341 switch (events) {
342 case UV_READABLE: {
343 O2_SIGNPOST_EVENT_EMIT(sockets, sid, "socket_state", "Data pending on socket for channel %{public}s", context->name);
344 context->state->loopReason |= DeviceState::DATA_INCOMING;
345 assert(context->channelInfo);
346 context->channelInfo->readPolled = true;
347 } break;
348 case UV_WRITABLE: {
349 O2_SIGNPOST_END(sockets, sid, "socket_state", "OOB socket connected for channel %{public}s", context->name);
350 if (context->read) {
351 O2_SIGNPOST_START(sockets, sid, "socket_state", "OOB socket connected for read in context %{public}s", context->name);
352 uv_poll_start(poller, UV_READABLE | UV_DISCONNECT | UV_PRIORITIZED, &on_out_of_band_polled);
353 } else {
354 O2_SIGNPOST_START(sockets, sid, "socket_state", "OOB socket connected for write for channel %{public}s", context->name);
355 context->state->loopReason |= DeviceState::DATA_OUTGOING;
356 }
357 } break;
358 case UV_DISCONNECT: {
359 O2_SIGNPOST_END(sockets, sid, "socket_state", "OOB socket disconnected in context %{public}s", context->name);
360 uv_poll_start(poller, UV_WRITABLE, &on_out_of_band_polled);
361 } break;
362 case UV_PRIORITIZED: {
363 O2_SIGNPOST_EVENT_EMIT(sockets, sid, "socket_state", "OOB socket prioritized for context %{public}s", context->name);
364 } break;
365 }
366 // We do nothing, all the logic for now stays in DataProcessingDevice::doRun()
367}
368
377{
378 auto ref = ServiceRegistryRef{mServiceRegistry};
379 auto& context = ref.get<DataProcessorContext>();
380 auto& spec = getRunningDevice(mRunningDevice, ref);
381
382 O2_SIGNPOST_ID_FROM_POINTER(cid, device, &context);
383 O2_SIGNPOST_START(device, cid, "Init", "Entering Init callback.");
384 context.statelessProcess = spec.algorithm.onProcess;
385 context.statefulProcess = nullptr;
386 context.error = spec.algorithm.onError;
387 context.initError = spec.algorithm.onInitError;
388
389 auto configStore = DeviceConfigurationHelpers::getConfiguration(mServiceRegistry, spec.name.c_str(), spec.options);
390 if (configStore == nullptr) {
391 std::vector<std::unique_ptr<ParamRetriever>> retrievers;
392 retrievers.emplace_back(std::make_unique<FairOptionsRetriever>(GetConfig()));
393 configStore = std::make_unique<ConfigParamStore>(spec.options, std::move(retrievers));
394 configStore->preload();
395 configStore->activate();
396 }
397
398 using boost::property_tree::ptree;
399
401 for (auto& entry : configStore->store()) {
402 std::stringstream ss;
403 std::string str;
404 if (entry.second.empty() == false) {
405 boost::property_tree::json_parser::write_json(ss, entry.second, false);
406 str = ss.str();
407 } else {
408 str = entry.second.get_value<std::string>();
409 }
410 std::string configString = fmt::format("[CONFIG] {}={} 1 {}", entry.first, str, configStore->provenance(entry.first.c_str())).c_str();
411 mServiceRegistry.get<DriverClient>(ServiceRegistry::globalDeviceSalt()).tell(configString.c_str());
412 }
413
414 mConfigRegistry = std::make_unique<ConfigParamRegistry>(std::move(configStore));
415
416 // Setup the error handlers for init
417 if (context.initError) {
418 context.initErrorHandling = [&errorCallback = context.initError,
419 &serviceRegistry = mServiceRegistry](RuntimeErrorRef e) {
423 auto& context = ref.get<DataProcessorContext>();
424 auto& err = error_from_ref(e);
425 O2_SIGNPOST_ID_FROM_POINTER(cid, device, &context);
426 O2_SIGNPOST_EVENT_EMIT_ERROR(device, cid, "Init", "Exception caught while in Init: %{public}s. Invoking errorCallback.", err.what);
427 BacktraceHelpers::demangled_backtrace_symbols(err.backtrace, err.maxBacktrace, STDERR_FILENO);
428 auto& stats = ref.get<DataProcessingStats>();
430 InitErrorContext errorContext{ref, e};
431 errorCallback(errorContext);
432 };
433 } else {
434 context.initErrorHandling = [&serviceRegistry = mServiceRegistry](RuntimeErrorRef e) {
435 auto& err = error_from_ref(e);
439 auto& context = ref.get<DataProcessorContext>();
440 O2_SIGNPOST_ID_FROM_POINTER(cid, device, &context);
441 O2_SIGNPOST_EVENT_EMIT_ERROR(device, cid, "Init", "Exception caught while in Init: %{public}s. Exiting with 1.", err.what);
442 BacktraceHelpers::demangled_backtrace_symbols(err.backtrace, err.maxBacktrace, STDERR_FILENO);
443 auto& stats = ref.get<DataProcessingStats>();
445 exit(1);
446 };
447 }
448
449 context.expirationHandlers.clear();
450 context.init = spec.algorithm.onInit;
451 if (context.init) {
452 static bool noCatch = getenv("O2_NO_CATCHALL_EXCEPTIONS") && strcmp(getenv("O2_NO_CATCHALL_EXCEPTIONS"), "0");
453 InitContext initContext{*mConfigRegistry, mServiceRegistry};
454
455 if (noCatch) {
456 try {
457 context.statefulProcess = context.init(initContext);
459 if (context.initErrorHandling) {
460 (context.initErrorHandling)(e);
461 }
462 }
463 } else {
464 try {
465 context.statefulProcess = context.init(initContext);
466 } catch (std::exception& ex) {
470 auto e = runtime_error(ex.what());
471 (context.initErrorHandling)(e);
473 (context.initErrorHandling)(e);
474 }
475 }
476 }
477 auto& state = ref.get<DeviceState>();
478 state.inputChannelInfos.resize(spec.inputChannels.size());
482 int validChannelId = 0;
483 for (size_t ci = 0; ci < spec.inputChannels.size(); ++ci) {
484 auto& name = spec.inputChannels[ci].name;
485 if (name.find(spec.channelPrefix + "from_internal-dpl-clock") == 0) {
486 state.inputChannelInfos[ci].state = InputChannelState::Pull;
487 state.inputChannelInfos[ci].id = {ChannelIndex::INVALID};
488 validChannelId++;
489 } else {
490 state.inputChannelInfos[ci].id = {validChannelId++};
491 }
492 }
493
494 // Invoke the callback policy for this device.
495 if (spec.callbacksPolicy.policy != nullptr) {
496 InitContext initContext{*mConfigRegistry, mServiceRegistry};
497 spec.callbacksPolicy.policy(mServiceRegistry.get<CallbackService>(ServiceRegistry::globalDeviceSalt()), initContext);
498 }
499
500 // Services which are stream should be initialised now
501 auto* options = GetConfig();
502 for (size_t si = 0; si < mStreams.size(); ++si) {
504 mServiceRegistry.lateBindStreamServices(state, *options, streamSalt);
505 }
506 O2_SIGNPOST_END(device, cid, "Init", "Exiting Init callback.");
507}
508
509void on_signal_callback(uv_signal_t* handle, int signum)
510{
511 O2_SIGNPOST_ID_FROM_POINTER(sid, device, handle);
512 O2_SIGNPOST_START(device, sid, "signal_state", "Signal %d received.", signum);
513
514 auto* registry = (ServiceRegistry*)handle->data;
515 if (!registry) {
516 O2_SIGNPOST_END(device, sid, "signal_state", "No registry active. Ignoring signal.");
517 return;
518 }
519 ServiceRegistryRef ref{*registry};
520 auto& state = ref.get<DeviceState>();
521 auto& quotaEvaluator = ref.get<ComputingQuotaEvaluator>();
522 auto& stats = ref.get<DataProcessingStats>();
524 size_t ri = 0;
525 while (ri != quotaEvaluator.mOffers.size()) {
526 auto& offer = quotaEvaluator.mOffers[ri];
527 // We were already offered some sharedMemory, so we
528 // do not consider the offer.
529 // FIXME: in principle this should account for memory
530 // available and being offered, however we
531 // want to get out of the woods for now.
532 if (offer.valid && offer.sharedMemory != 0) {
533 O2_SIGNPOST_END(device, sid, "signal_state", "Memory already offered.");
534 return;
535 }
536 ri++;
537 }
538 // Find the first empty offer and have 1GB of shared memory there
539 for (auto& offer : quotaEvaluator.mOffers) {
540 if (offer.valid == false) {
541 offer.cpu = 0;
542 offer.memory = 0;
543 offer.sharedMemory = 1000000000;
544 offer.valid = true;
545 offer.user = -1;
546 break;
547 }
548 }
550 O2_SIGNPOST_END(device, sid, "signal_state", "Done processing signals.");
551}
552
553struct DecongestionContext {
556};
557
558auto decongestionCallbackLate = [](AsyncTask& task, size_t aid) -> void {
559 auto& oldestTimeslice = task.user<DecongestionContext>().oldestTimeslice;
560 auto& ref = task.user<DecongestionContext>().ref;
561
562 auto& decongestion = ref.get<DecongestionService>();
563 auto& proxy = ref.get<FairMQDeviceProxy>();
564 if (oldestTimeslice.timeslice.value <= decongestion.lastTimeslice) {
565 LOG(debug) << "Not sending already sent oldest possible timeslice " << oldestTimeslice.timeslice.value;
566 return;
567 }
568 for (int fi = 0; fi < proxy.getNumForwardChannels(); fi++) {
569 auto& info = proxy.getForwardChannelInfo(ChannelIndex{fi});
570 auto& state = proxy.getForwardChannelState(ChannelIndex{fi});
571 O2_SIGNPOST_ID_GENERATE(aid, async_queue);
572 // TODO: this we could cache in the proxy at the bind moment.
573 if (info.channelType != ChannelAccountingType::DPL) {
574 O2_SIGNPOST_EVENT_EMIT(async_queue, aid, "forwardInputsCallback", "Skipping channel %{public}s because it's not a DPL channel",
575 info.name.c_str());
576
577 continue;
578 }
579 if (DataProcessingHelpers::sendOldestPossibleTimeframe(ref, info, state, oldestTimeslice.timeslice.value)) {
580 O2_SIGNPOST_EVENT_EMIT(async_queue, aid, "forwardInputsCallback", "Forwarding to channel %{public}s oldest possible timeslice %zu, prio 20",
581 info.name.c_str(), oldestTimeslice.timeslice.value);
582 }
583 }
584};
585
586// This is how we do the forwarding, i.e. we push
587// the inputs which are shared between this device and others
588// to the next one in the daisy chain.
589// FIXME: do it in a smarter way than O(N^2)
590static auto forwardInputs = [](ServiceRegistryRef registry, TimesliceSlot slot, std::vector<std::span<fair::mq::MessagePtr>>& currentSetOfInputs,
591 TimesliceIndex::OldestOutputInfo oldestTimeslice, bool copy, bool consume = true) {
592 auto& proxy = registry.get<FairMQDeviceProxy>();
593
594 O2_SIGNPOST_ID_GENERATE(sid, forwarding);
595 O2_SIGNPOST_START(forwarding, sid, "forwardInputs", "Starting forwarding for slot %zu with oldestTimeslice %zu %{public}s%{public}s%{public}s",
596 slot.index, oldestTimeslice.timeslice.value, copy ? "with copy" : "", copy && consume ? " and " : "", consume ? "with consume" : "");
597 auto forwardedParts = DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copy, consume);
598
599 for (int fi = 0; fi < proxy.getNumForwardChannels(); fi++) {
600 if (forwardedParts[fi].Size() == 0) {
601 continue;
602 }
603 ForwardChannelInfo info = proxy.getForwardChannelInfo(ChannelIndex{fi});
604 auto& parts = forwardedParts[fi];
605 if (info.policy == nullptr) {
606 O2_SIGNPOST_EVENT_EMIT_ERROR(forwarding, sid, "forwardInputs", "Forwarding to %{public}s %d has no policy.", info.name.c_str(), fi);
607 continue;
608 }
609 O2_SIGNPOST_EVENT_EMIT(forwarding, sid, "forwardInputs", "Forwarding to %{public}s %d", info.name.c_str(), fi);
610 info.policy->forward(parts, ChannelIndex{fi}, registry);
611 }
612
613 auto& asyncQueue = registry.get<AsyncQueue>();
614 auto& decongestion = registry.get<DecongestionService>();
615 O2_SIGNPOST_ID_GENERATE(aid, async_queue);
616 O2_SIGNPOST_EVENT_EMIT(async_queue, aid, "forwardInputs", "Queuing forwarding oldestPossible %zu", oldestTimeslice.timeslice.value);
617 AsyncQueueHelpers::post(asyncQueue, AsyncTask{.timeslice = oldestTimeslice.timeslice, .id = decongestion.oldestPossibleTimesliceTask, .debounce = -1, .callback = decongestionCallbackLate}
618 .user<DecongestionContext>({.ref = registry, .oldestTimeslice = oldestTimeslice}));
619 O2_SIGNPOST_END(forwarding, sid, "forwardInputs", "Forwarding done");
620};
621
622static auto cleanEarlyForward = [](ServiceRegistryRef registry, TimesliceSlot slot, std::vector<std::span<fair::mq::MessagePtr>>& currentSetOfInputs,
623 TimesliceIndex::OldestOutputInfo oldestTimeslice, bool copy, bool consume = true) {
624 auto& proxy = registry.get<FairMQDeviceProxy>();
625
626 O2_SIGNPOST_ID_GENERATE(sid, forwarding);
627 O2_SIGNPOST_START(forwarding, sid, "forwardInputs", "Cleaning up slot %zu with oldestTimeslice %zu %{public}s%{public}s%{public}s",
628 slot.index, oldestTimeslice.timeslice.value, copy ? "with copy" : "", copy && consume ? " and " : "", consume ? "with consume" : "");
629 // Always copy them, because we do not want to actually send them.
630 // We merely need the side effect of the consume, if applicable.
631 for (size_t ii = 0, ie = currentSetOfInputs.size(); ii < ie; ++ii) {
632 DataProcessingHelpers::cleanForwardedMessages(currentSetOfInputs[ii], consume);
633 }
634
635 O2_SIGNPOST_END(forwarding, sid, "forwardInputs", "Cleaning done");
636};
637
638extern volatile int region_read_global_dummy_variable;
640
642void handleRegionCallbacks(ServiceRegistryRef registry, std::vector<fair::mq::RegionInfo>& infos)
643{
644 if (infos.empty() == false) {
645 std::vector<fair::mq::RegionInfo> toBeNotified;
646 toBeNotified.swap(infos); // avoid any MT issue.
647 static bool dummyRead = getenv("DPL_DEBUG_MAP_ALL_SHM_REGIONS") && atoi(getenv("DPL_DEBUG_MAP_ALL_SHM_REGIONS"));
648 for (auto const& info : toBeNotified) {
649 if (dummyRead) {
650 for (size_t i = 0; i < info.size / sizeof(region_read_global_dummy_variable); i += 4096 / sizeof(region_read_global_dummy_variable)) {
651 region_read_global_dummy_variable = ((int*)info.ptr)[i];
652 }
653 }
654 registry.get<CallbackService>().call<CallbackService::Id::RegionInfoCallback>(info);
655 }
656 }
657}
658
659namespace
660{
662{
663 auto* state = (DeviceState*)handle->data;
665}
666} // namespace
667
668void DataProcessingDevice::initPollers()
669{
670 auto ref = ServiceRegistryRef{mServiceRegistry};
671 auto& deviceContext = ref.get<DeviceContext>();
672 auto& context = ref.get<DataProcessorContext>();
673 auto& spec = ref.get<DeviceSpec const>();
674 auto& state = ref.get<DeviceState>();
675 // We add a timer only in case a channel poller is not there.
676 if ((context.statefulProcess != nullptr) || (context.statelessProcess != nullptr)) {
677 for (auto& [channelName, channel] : GetChannels()) {
678 InputChannelInfo* channelInfo;
679 for (size_t ci = 0; ci < spec.inputChannels.size(); ++ci) {
680 auto& channelSpec = spec.inputChannels[ci];
681 channelInfo = &state.inputChannelInfos[ci];
682 if (channelSpec.name != channelName) {
683 continue;
684 }
685 channelInfo->channel = &this->GetChannel(channelName, 0);
686 break;
687 }
688 if ((channelName.rfind("from_internal-dpl", 0) == 0) &&
689 (channelName.rfind("from_internal-dpl-aod", 0) != 0) &&
690 (channelName.rfind("from_internal-dpl-ccdb-backend", 0) != 0) &&
691 (channelName.rfind("from_internal-dpl-injected", 0)) != 0) {
692 LOGP(detail, "{} is an internal channel. Skipping as no input will come from there.", channelName);
693 continue;
694 }
695 // We only watch receiving sockets.
696 if (channelName.rfind("from_" + spec.name + "_", 0) == 0) {
697 LOGP(detail, "{} is to send data. Not polling.", channelName);
698 continue;
699 }
700
701 if (channelName.rfind("from_", 0) != 0) {
702 LOGP(detail, "{} is not a DPL socket. Not polling.", channelName);
703 continue;
704 }
705
706 // We assume there is always a ZeroMQ socket behind.
707 int zmq_fd = 0;
708 size_t zmq_fd_len = sizeof(zmq_fd);
709 // FIXME: I should probably save those somewhere... ;-)
710 auto* poller = (uv_poll_t*)malloc(sizeof(uv_poll_t));
711 channel[0].GetSocket().GetOption("fd", &zmq_fd, &zmq_fd_len);
712 if (zmq_fd == 0) {
713 LOG(error) << "Cannot get file descriptor for channel." << channelName;
714 continue;
715 }
716 LOGP(detail, "Polling socket for {}", channelName);
717 auto* pCtx = (PollerContext*)malloc(sizeof(PollerContext));
718 pCtx->name = strdup(channelName.c_str());
719 pCtx->loop = state.loop;
720 pCtx->device = this;
721 pCtx->state = &state;
722 pCtx->fd = zmq_fd;
723 assert(channelInfo != nullptr);
724 pCtx->channelInfo = channelInfo;
725 pCtx->socket = &channel[0].GetSocket();
726 pCtx->read = true;
727 poller->data = pCtx;
728 uv_poll_init(state.loop, poller, zmq_fd);
729 if (channelName.rfind("from_", 0) != 0) {
730 LOGP(detail, "{} is an out of band channel.", channelName);
731 state.activeOutOfBandPollers.push_back(poller);
732 } else {
733 channelInfo->pollerIndex = state.activeInputPollers.size();
734 state.activeInputPollers.push_back(poller);
735 }
736 }
737 // In case we do not have any input channel and we do not have
738 // any timers or signal watchers we still wake up whenever we can send data to downstream
739 // devices to allow for enumerations.
740 if (state.activeInputPollers.empty() &&
741 state.activeOutOfBandPollers.empty() &&
742 state.activeTimers.empty() &&
743 state.activeSignals.empty()) {
744 // FIXME: this is to make sure we do not reset the output timer
745 // for readout proxies or similar. In principle this should go once
746 // we move to OutOfBand InputSpec.
747 if (state.inputChannelInfos.empty()) {
748 LOGP(detail, "No input channels. Setting exit transition timeout to 0.");
749 deviceContext.exitTransitionTimeout = 0;
750 }
751 for (auto& [channelName, channel] : GetChannels()) {
752 if (channelName.rfind(spec.channelPrefix + "from_internal-dpl", 0) == 0) {
753 LOGP(detail, "{} is an internal channel. Not polling.", channelName);
754 continue;
755 }
756 if (channelName.rfind(spec.channelPrefix + "from_" + spec.name + "_", 0) == 0) {
757 LOGP(detail, "{} is an out of band channel. Not polling for output.", channelName);
758 continue;
759 }
760 // We assume there is always a ZeroMQ socket behind.
761 int zmq_fd = 0;
762 size_t zmq_fd_len = sizeof(zmq_fd);
763 // FIXME: I should probably save those somewhere... ;-)
764 auto* poller = (uv_poll_t*)malloc(sizeof(uv_poll_t));
765 channel[0].GetSocket().GetOption("fd", &zmq_fd, &zmq_fd_len);
766 if (zmq_fd == 0) {
767 LOGP(error, "Cannot get file descriptor for channel {}", channelName);
768 continue;
769 }
770 LOG(detail) << "Polling socket for " << channel[0].GetName();
771 // FIXME: leak
772 auto* pCtx = (PollerContext*)malloc(sizeof(PollerContext));
773 pCtx->name = strdup(channelName.c_str());
774 pCtx->loop = state.loop;
775 pCtx->device = this;
776 pCtx->state = &state;
777 pCtx->fd = zmq_fd;
778 pCtx->read = false;
779 poller->data = pCtx;
780 uv_poll_init(state.loop, poller, zmq_fd);
781 state.activeOutputPollers.push_back(poller);
782 }
783 }
784 } else {
785 LOGP(detail, "This is a fake device so we exit after the first iteration.");
786 deviceContext.exitTransitionTimeout = 0;
787 // This is a fake device, so we can request to exit immediately
788 ServiceRegistryRef ref{mServiceRegistry};
789 ref.get<ControlService>().readyToQuit(QuitRequest::Me);
790 // A two second timer to stop internal devices which do not want to
791 auto* timer = (uv_timer_t*)malloc(sizeof(uv_timer_t));
792 uv_timer_init(state.loop, timer);
793 timer->data = &state;
794 uv_update_time(state.loop);
795 uv_timer_start(timer, on_idle_timer, 2000, 2000);
796 state.activeTimers.push_back(timer);
797 }
798}
799
800void DataProcessingDevice::startPollers()
801{
802 auto ref = ServiceRegistryRef{mServiceRegistry};
803 auto& deviceContext = ref.get<DeviceContext>();
804 auto& state = ref.get<DeviceState>();
805
806 for (auto* poller : state.activeInputPollers) {
807 O2_SIGNPOST_ID_FROM_POINTER(sid, device, poller);
808 O2_SIGNPOST_START(device, sid, "socket_state", "Input socket waiting for connection.");
809 uv_poll_start(poller, UV_WRITABLE, &on_socket_polled);
810 ((PollerContext*)poller->data)->pollerState = PollerContext::PollerState::Disconnected;
811 }
812 for (auto& poller : state.activeOutOfBandPollers) {
813 uv_poll_start(poller, UV_WRITABLE, &on_out_of_band_polled);
814 ((PollerContext*)poller->data)->pollerState = PollerContext::PollerState::Disconnected;
815 }
816 for (auto* poller : state.activeOutputPollers) {
817 O2_SIGNPOST_ID_FROM_POINTER(sid, device, poller);
818 O2_SIGNPOST_START(device, sid, "socket_state", "Output socket waiting for connection.");
819 uv_poll_start(poller, UV_WRITABLE, &on_socket_polled);
820 ((PollerContext*)poller->data)->pollerState = PollerContext::PollerState::Disconnected;
821 }
822
823 deviceContext.gracePeriodTimer = (uv_timer_t*)malloc(sizeof(uv_timer_t));
824 deviceContext.gracePeriodTimer->data = new ServiceRegistryRef(mServiceRegistry);
825 uv_timer_init(state.loop, deviceContext.gracePeriodTimer);
826
827 deviceContext.dataProcessingGracePeriodTimer = (uv_timer_t*)malloc(sizeof(uv_timer_t));
828 deviceContext.dataProcessingGracePeriodTimer->data = new ServiceRegistryRef(mServiceRegistry);
829 uv_timer_init(state.loop, deviceContext.dataProcessingGracePeriodTimer);
830}
831
832void DataProcessingDevice::stopPollers()
833{
834 auto ref = ServiceRegistryRef{mServiceRegistry};
835 auto& deviceContext = ref.get<DeviceContext>();
836 auto& state = ref.get<DeviceState>();
837 LOGP(detail, "Stopping {} input pollers", state.activeInputPollers.size());
838 for (auto* poller : state.activeInputPollers) {
839 O2_SIGNPOST_ID_FROM_POINTER(sid, device, poller);
840 O2_SIGNPOST_END(device, sid, "socket_state", "Output socket closed.");
841 uv_poll_stop(poller);
842 ((PollerContext*)poller->data)->pollerState = PollerContext::PollerState::Stopped;
843 }
844 LOGP(detail, "Stopping {} out of band pollers", state.activeOutOfBandPollers.size());
845 for (auto* poller : state.activeOutOfBandPollers) {
846 uv_poll_stop(poller);
847 ((PollerContext*)poller->data)->pollerState = PollerContext::PollerState::Stopped;
848 }
849 LOGP(detail, "Stopping {} output pollers", state.activeOutOfBandPollers.size());
850 for (auto* poller : state.activeOutputPollers) {
851 O2_SIGNPOST_ID_FROM_POINTER(sid, device, poller);
852 O2_SIGNPOST_END(device, sid, "socket_state", "Output socket closed.");
853 uv_poll_stop(poller);
854 ((PollerContext*)poller->data)->pollerState = PollerContext::PollerState::Stopped;
855 }
856
857 uv_timer_stop(deviceContext.gracePeriodTimer);
858 delete (ServiceRegistryRef*)deviceContext.gracePeriodTimer->data;
859 free(deviceContext.gracePeriodTimer);
860 deviceContext.gracePeriodTimer = nullptr;
861
862 uv_timer_stop(deviceContext.dataProcessingGracePeriodTimer);
863 delete (ServiceRegistryRef*)deviceContext.dataProcessingGracePeriodTimer->data;
864 free(deviceContext.dataProcessingGracePeriodTimer);
865 deviceContext.dataProcessingGracePeriodTimer = nullptr;
866}
867
869{
870 auto ref = ServiceRegistryRef{mServiceRegistry};
871 auto& deviceContext = ref.get<DeviceContext>();
872 auto& context = ref.get<DataProcessorContext>();
873
874 O2_SIGNPOST_ID_FROM_POINTER(cid, device, &context);
875 O2_SIGNPOST_START(device, cid, "InitTask", "Entering InitTask callback.");
876 auto& spec = getRunningDevice(mRunningDevice, mServiceRegistry);
877 auto distinct = DataRelayerHelpers::createDistinctRouteIndex(spec.inputs);
878 auto& state = ref.get<DeviceState>();
879 int i = 0;
880 for (auto& di : distinct) {
881 auto& route = spec.inputs[di];
882 if (route.configurator.has_value() == false) {
883 i++;
884 continue;
885 }
886 ExpirationHandler handler{
887 .name = route.configurator->name,
888 .routeIndex = RouteIndex{i++},
889 .lifetime = route.matcher.lifetime,
890 .creator = route.configurator->creatorConfigurator(state, mServiceRegistry, *mConfigRegistry),
891 .checker = route.configurator->danglingConfigurator(state, *mConfigRegistry),
892 .handler = route.configurator->expirationConfigurator(state, *mConfigRegistry)};
893 context.expirationHandlers.emplace_back(std::move(handler));
894 }
895
896 if (state.awakeMainThread == nullptr) {
897 state.awakeMainThread = (uv_async_t*)malloc(sizeof(uv_async_t));
898 state.awakeMainThread->data = &state;
899 uv_async_init(state.loop, state.awakeMainThread, on_awake_main_thread);
900 }
901
902 deviceContext.expectedRegionCallbacks = std::stoi(fConfig->GetValue<std::string>("expected-region-callbacks"));
903 deviceContext.exitTransitionTimeout = std::stoi(fConfig->GetValue<std::string>("exit-transition-timeout"));
904 deviceContext.dataProcessingTimeout = std::stoi(fConfig->GetValue<std::string>("data-processing-timeout"));
905
906 for (auto& channel : GetChannels()) {
907 channel.second.at(0).Transport()->SubscribeToRegionEvents([&context = deviceContext,
908 &registry = mServiceRegistry,
909 &pendingRegionInfos = mPendingRegionInfos,
910 &regionInfoMutex = mRegionInfoMutex](fair::mq::RegionInfo info) {
911 std::lock_guard<std::mutex> lock(regionInfoMutex);
912 LOG(detail) << ">>> Region info event" << info.event;
913 LOG(detail) << "id: " << info.id;
914 LOG(detail) << "ptr: " << info.ptr;
915 LOG(detail) << "size: " << info.size;
916 LOG(detail) << "flags: " << info.flags;
917 // Now we check for pending events with the mutex,
918 // so the lines below are atomic.
919 pendingRegionInfos.push_back(info);
920 context.expectedRegionCallbacks -= 1;
921 // We always want to handle these on the main loop,
922 // so we awake it.
923 ServiceRegistryRef ref{registry};
924 uv_async_send(ref.get<DeviceState>().awakeMainThread);
925 });
926 }
927
928 // Add a signal manager for SIGUSR1 so that we can force
929 // an event from the outside, making sure that the event loop can
930 // be unblocked (e.g. by a quitting DPL driver) even when there
931 // is no data pending to be processed.
932 if (deviceContext.sigusr1Handle == nullptr) {
933 deviceContext.sigusr1Handle = (uv_signal_t*)malloc(sizeof(uv_signal_t));
934 deviceContext.sigusr1Handle->data = &mServiceRegistry;
935 uv_signal_init(state.loop, deviceContext.sigusr1Handle);
936 uv_signal_start(deviceContext.sigusr1Handle, on_signal_callback, SIGUSR1);
937 }
938 // If there is any signal, we want to make sure they are active
939 for (auto& handle : state.activeSignals) {
940 handle->data = &state;
941 }
942 // When we start, we must make sure that we do listen to the signal
943 deviceContext.sigusr1Handle->data = &mServiceRegistry;
944
946 DataProcessingDevice::initPollers();
947
948 // Whenever we InitTask, we consider as if the previous iteration
949 // was successful, so that even if there is no timer or receiving
950 // channel, we can still start an enumeration.
951 DataProcessorContext* initialContext = nullptr;
952 bool idle = state.lastActiveDataProcessor.compare_exchange_strong(initialContext, (DataProcessorContext*)-1);
953 if (!idle) {
954 LOG(error) << "DataProcessor " << state.lastActiveDataProcessor.load()->spec->name << " was unexpectedly active";
955 }
956
957 // We should be ready to run here. Therefore we copy all the
958 // required parts in the DataProcessorContext. Eventually we should
959 // do so on a per thread basis, with fine grained locks.
960 // FIXME: this should not use ServiceRegistry::threadSalt, but
961 // more a ServiceRegistry::globalDataProcessorSalt(N) where
962 // N is the number of the multiplexed data processor.
963 // We will get there.
964 this->fillContext(mServiceRegistry.get<DataProcessorContext>(ServiceRegistry::globalDeviceSalt()), deviceContext);
965
966 O2_SIGNPOST_END(device, cid, "InitTask", "Exiting InitTask callback waiting for the remaining region callbacks.");
967
968 auto hasPendingEvents = [&mutex = mRegionInfoMutex, &pendingRegionInfos = mPendingRegionInfos](DeviceContext& deviceContext) {
969 std::lock_guard<std::mutex> lock(mutex);
970 return (pendingRegionInfos.empty() == false) || deviceContext.expectedRegionCallbacks > 0;
971 };
972 O2_SIGNPOST_START(device, cid, "InitTask", "Waiting for registation events.");
977 while (hasPendingEvents(deviceContext)) {
978 // Wait for the callback to signal its done, so that we do not busy wait.
979 uv_run(state.loop, UV_RUN_ONCE);
980 // Handle callbacks if any
981 {
982 O2_SIGNPOST_EVENT_EMIT(device, cid, "InitTask", "Memory registration event received.");
983 std::lock_guard<std::mutex> lock(mRegionInfoMutex);
984 handleRegionCallbacks(mServiceRegistry, mPendingRegionInfos);
985 }
986 }
987 O2_SIGNPOST_END(device, cid, "InitTask", "Done waiting for registration events.");
988}
989
991{
992 context.isSink = false;
993 // If nothing is a sink, the rate limiting simply does not trigger.
994 bool enableRateLimiting = std::stoi(fConfig->GetValue<std::string>("timeframes-rate-limit"));
995
996 auto ref = ServiceRegistryRef{mServiceRegistry};
997 auto& spec = ref.get<DeviceSpec const>();
998
999 // The policy is now allowed to state the default.
1000 context.balancingInputs = spec.completionPolicy.balanceChannels;
1001 // This is needed because the internal injected dummy sink should not
1002 // try to balance inputs unless the rate limiting is requested.
1003 if (enableRateLimiting == false && spec.name.find("internal-dpl-injected-dummy-sink") != std::string::npos) {
1004 context.balancingInputs = false;
1005 }
1006 if (enableRateLimiting) {
1007 for (auto& spec : spec.outputs) {
1008 if (spec.matcher.binding.value == "dpl-summary") {
1009 context.isSink = true;
1010 break;
1011 }
1012 }
1013 }
1014
1015 context.registry = &mServiceRegistry;
1018 if (context.error != nullptr) {
1019 context.errorHandling = [&errorCallback = context.error,
1020 &serviceRegistry = mServiceRegistry](RuntimeErrorRef e, InputRecord& record) {
1024 auto& err = error_from_ref(e);
1025 auto& context = ref.get<DataProcessorContext>();
1026 O2_SIGNPOST_ID_FROM_POINTER(cid, device, &context);
1027 O2_SIGNPOST_EVENT_EMIT_ERROR(device, cid, "Run", "Exception while running: %{public}s. Invoking callback.", err.what);
1028 BacktraceHelpers::demangled_backtrace_symbols(err.backtrace, err.maxBacktrace, STDERR_FILENO);
1029 auto& stats = ref.get<DataProcessingStats>();
1031 ErrorContext errorContext{record, ref, e};
1032 errorCallback(errorContext);
1033 };
1034 } else {
1035 context.errorHandling = [&serviceRegistry = mServiceRegistry](RuntimeErrorRef e, InputRecord& record) {
1036 auto& err = error_from_ref(e);
1040 auto& context = ref.get<DataProcessorContext>();
1041 auto& deviceContext = ref.get<DeviceContext>();
1042 O2_SIGNPOST_ID_FROM_POINTER(cid, device, &context);
1043 BacktraceHelpers::demangled_backtrace_symbols(err.backtrace, err.maxBacktrace, STDERR_FILENO);
1044 auto& stats = ref.get<DataProcessingStats>();
1046 switch (deviceContext.processingPolicies.error) {
1048 O2_SIGNPOST_EVENT_EMIT_ERROR(device, cid, "Run", "Exception while running: %{public}s. Rethrowing.", err.what);
1049 throw e;
1050 default:
1051 O2_SIGNPOST_EVENT_EMIT_ERROR(device, cid, "Run", "Exception while running: %{public}s. Skipping to next timeframe.", err.what);
1052 break;
1053 }
1054 };
1055 }
1056
1057 auto decideEarlyForward = [&context, &deviceContext, &spec, this]() -> ForwardPolicy {
1058 ForwardPolicy defaultEarlyForwardPolicy = getenv("DPL_OLD_EARLY_FORWARD") ? ForwardPolicy::AtCompletionPolicySatisified : ForwardPolicy::AtInjection;
1059 // FIXME: try again with the new policy by default.
1060 //
1061 // Make the new policy optional until we handle some of the corner cases
1062 // with custom policies which expect the early forward to happen only when
1063 // all the data is available, like in the TPC case.
1064 // ForwardPolicy defaultEarlyForwardPolicy = getenv("DPL_NEW_EARLY_FORWARD") ? ForwardPolicy::AtInjection : ForwardPolicy::AtCompletionPolicySatisified;
1065 for (auto& forward : spec.forwards) {
1066 if (DataSpecUtils::match(forward.matcher, ConcreteDataTypeMatcher{"TPC", "DIGITSMCTR"}) ||
1067 DataSpecUtils::match(forward.matcher, ConcreteDataTypeMatcher{"TPC", "CLNATIVEMCLBL"}) ||
1068 DataSpecUtils::match(forward.matcher, ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, "DIGITS"}) ||
1069 DataSpecUtils::match(forward.matcher, ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, "CLUSTERNATIVE"})) {
1070 defaultEarlyForwardPolicy = ForwardPolicy::AtCompletionPolicySatisified;
1071 break;
1072 }
1073 }
1074 // Output proxies should wait for the completion policy before forwarding.
1075 // Because they actually do not do anything, that's equivalent to
1076 // forwarding after the processing.
1077 for (auto& label : spec.labels) {
1078 if (label.value == "output-proxy") {
1079 defaultEarlyForwardPolicy = ForwardPolicy::AfterProcessing;
1080 break;
1081 }
1082 }
1083
1086 ForwardPolicy forwardPolicy = defaultEarlyForwardPolicy;
1087 if (spec.forwards.empty() == false) {
1088 switch (deviceContext.processingPolicies.earlyForward) {
1090 forwardPolicy = ForwardPolicy::AfterProcessing;
1091 break;
1093 forwardPolicy = defaultEarlyForwardPolicy;
1094 break;
1096 forwardPolicy = defaultEarlyForwardPolicy;
1097 break;
1098 }
1099 }
1100 bool onlyConditions = true;
1101 bool overriddenEarlyForward = false;
1102 for (auto& forwarded : spec.forwards) {
1103 if (forwarded.matcher.lifetime != Lifetime::Condition) {
1104 onlyConditions = false;
1105 }
1107 forwardPolicy = ForwardPolicy::AfterProcessing;
1108 overriddenEarlyForward = true;
1109 LOG(detail) << "Cannot forward early because of RAWDATA input: " << DataSpecUtils::describe(forwarded.matcher);
1110 break;
1111 }
1112 if (forwarded.matcher.lifetime == Lifetime::Optional) {
1113 forwardPolicy = ForwardPolicy::AfterProcessing;
1114 overriddenEarlyForward = true;
1115 LOG(detail) << "Cannot forward early because of Optional input: " << DataSpecUtils::describe(forwarded.matcher);
1116 break;
1117 }
1118 }
1119 if (!overriddenEarlyForward && onlyConditions) {
1120 forwardPolicy = defaultEarlyForwardPolicy;
1121 LOG(detail) << "Enabling early forwarding because only conditions to be forwarded";
1122 }
1123 return forwardPolicy;
1124 };
1125 context.forwardPolicy = decideEarlyForward();
1126}
1127
1129{
1130 auto ref = ServiceRegistryRef{mServiceRegistry};
1131 auto& state = ref.get<DeviceState>();
1132
1133 O2_SIGNPOST_ID_FROM_POINTER(cid, device, state.loop);
1134 O2_SIGNPOST_START(device, cid, "PreRun", "Entering PreRun callback.");
1135 state.quitRequested = false;
1137 state.allowedProcessing = DeviceState::Any;
1138 for (auto& info : state.inputChannelInfos) {
1139 if (info.state != InputChannelState::Pull) {
1140 info.state = InputChannelState::Running;
1141 }
1142 }
1143
1144 // Catch callbacks which fail before we start.
1145 // Notice that when running multiple dataprocessors
1146 // we should probably allow expendable ones to fail.
1147 try {
1148 auto& dpContext = ref.get<DataProcessorContext>();
1149 dpContext.preStartCallbacks(ref);
1150 for (size_t i = 0; i < mStreams.size(); ++i) {
1151 auto streamRef = ServiceRegistryRef{mServiceRegistry, ServiceRegistry::globalStreamSalt(i + 1)};
1152 auto& context = streamRef.get<StreamContext>();
1153 context.preStartStreamCallbacks(streamRef);
1154 }
1155 } catch (std::exception& e) {
1156 O2_SIGNPOST_EVENT_EMIT_ERROR(device, cid, "PreRun", "Exception of type std::exception caught in PreRun: %{public}s. Rethrowing.", e.what());
1157 O2_SIGNPOST_END(device, cid, "PreRun", "Exiting PreRun due to exception thrown.");
1158 throw;
1159 } catch (o2::framework::RuntimeErrorRef& e) {
1160 auto& err = error_from_ref(e);
1161 O2_SIGNPOST_EVENT_EMIT_ERROR(device, cid, "PreRun", "Exception of type o2::framework::RuntimeErrorRef caught in PreRun: %{public}s. Rethrowing.", err.what);
1162 O2_SIGNPOST_END(device, cid, "PreRun", "Exiting PreRun due to exception thrown.");
1163 throw;
1164 } catch (...) {
1165 O2_SIGNPOST_END(device, cid, "PreRun", "Unknown exception being thrown. Rethrowing.");
1166 throw;
1167 }
1168
1169 ref.get<CallbackService>().call<CallbackService::Id::Start>();
1170 startPollers();
1171
1172 // Raise to 1 when we are ready to start processing
1173 using o2::monitoring::Metric;
1174 using o2::monitoring::Monitoring;
1175 using o2::monitoring::tags::Key;
1176 using o2::monitoring::tags::Value;
1177
1178 auto& monitoring = ref.get<Monitoring>();
1179 monitoring.send(Metric{(uint64_t)1, "device_state"}.addTag(Key::Subsystem, Value::DPL));
1180 O2_SIGNPOST_END(device, cid, "PreRun", "Exiting PreRun callback.");
1181}
1182
1184{
1185 ServiceRegistryRef ref{mServiceRegistry};
1186 // Raise to 1 when we are ready to start processing
1187 using o2::monitoring::Metric;
1188 using o2::monitoring::Monitoring;
1189 using o2::monitoring::tags::Key;
1190 using o2::monitoring::tags::Value;
1191
1192 auto& monitoring = ref.get<Monitoring>();
1193 monitoring.send(Metric{(uint64_t)0, "device_state"}.addTag(Key::Subsystem, Value::DPL));
1194
1195 stopPollers();
1196 ref.get<CallbackService>().call<CallbackService::Id::Stop>();
1197 auto& dpContext = ref.get<DataProcessorContext>();
1198 dpContext.postStopCallbacks(ref);
1199}
1200
1202{
1203 ServiceRegistryRef ref{mServiceRegistry};
1204 ref.get<CallbackService>().call<CallbackService::Id::Reset>();
1205}
1206
1208{
1209 ServiceRegistryRef ref{mServiceRegistry};
1210 auto& state = ref.get<DeviceState>();
1212 bool firstLoop = true;
1213 O2_SIGNPOST_ID_FROM_POINTER(lid, device, state.loop);
1214 O2_SIGNPOST_START(device, lid, "device_state", "First iteration of the device loop");
1215
1216 auto* poolSizeEnv = getenv("DPL_THREADPOOL_SIZE");
1217 bool dplEnableMultithreding = poolSizeEnv && std::atoi(poolSizeEnv) > 0;
1218
1219 while (state.transitionHandling != TransitionHandlingState::Expired) {
1220 if (state.nextFairMQState.empty() == false) {
1221 (void)this->ChangeState(state.nextFairMQState.back());
1222 state.nextFairMQState.pop_back();
1223 }
1224 // Notify on the main thread the new region callbacks, making sure
1225 // no callback is issued if there is something still processing.
1226 {
1227 std::lock_guard<std::mutex> lock(mRegionInfoMutex);
1228 handleRegionCallbacks(mServiceRegistry, mPendingRegionInfos);
1229 }
1230 // This will block for the correct delay (or until we get data
1231 // on a socket). We also do not block on the first iteration
1232 // so that devices which do not have a timer can still start an
1233 // enumeration.
1234 {
1235 ServiceRegistryRef ref{mServiceRegistry};
1236 ref.get<DriverClient>().flushPending(mServiceRegistry);
1237 DataProcessorContext* lastActive = state.lastActiveDataProcessor.load();
1238 // Reset to zero unless some other DataPorcessorContext completed in the meanwhile.
1239 // In such case we will take care of it at next iteration.
1240 state.lastActiveDataProcessor.compare_exchange_strong(lastActive, nullptr);
1241
1242 auto shouldNotWait = (lastActive != nullptr &&
1243 (state.streaming != StreamingState::Idle) && (state.activeSignals.empty())) ||
1245 if (firstLoop) {
1246 shouldNotWait = true;
1247 firstLoop = false;
1248 }
1249 if (lastActive != nullptr) {
1251 }
1252 if (NewStatePending()) {
1253 O2_SIGNPOST_EVENT_EMIT(device, lid, "run_loop", "New state pending. Waiting for it to be handled.");
1254 shouldNotWait = true;
1256 }
1258 // If we are Idle, we can then consider the transition to be expired.
1259 if (state.transitionHandling == TransitionHandlingState::Requested && state.streaming == StreamingState::Idle) {
1260 O2_SIGNPOST_EVENT_EMIT(device, lid, "run_loop", "State transition requested and we are now in Idle. We can consider it to be completed.");
1261 state.transitionHandling = TransitionHandlingState::Expired;
1262 }
1263 if (state.severityStack.empty() == false) {
1264 fair::Logger::SetConsoleSeverity((fair::Severity)state.severityStack.back());
1265 state.severityStack.pop_back();
1266 }
1267 // for (auto &info : mDeviceContext.state->inputChannelInfos) {
1268 // shouldNotWait |= info.readPolled;
1269 // }
1270 state.loopReason = DeviceState::NO_REASON;
1271 state.firedTimers.clear();
1272 if ((state.tracingFlags & DeviceState::LoopReason::TRACE_CALLBACKS) != 0) {
1273 state.severityStack.push_back((int)fair::Logger::GetConsoleSeverity());
1274 fair::Logger::SetConsoleSeverity(fair::Severity::trace);
1275 }
1276 // Run the asynchronous queue just before sleeping again, so that:
1277 // - we can trigger further events from the queue
1278 // - we can guarantee this is the last thing we do in the loop (
1279 // assuming no one else is adding to the queue before this point).
1280 auto onDrop = [&registry = mServiceRegistry, lid](TimesliceSlot slot, std::vector<std::span<fair::mq::MessagePtr>>& dropped, TimesliceIndex::OldestOutputInfo oldestOutputInfo) {
1281 O2_SIGNPOST_START(device, lid, "run_loop", "Dropping message from slot %" PRIu64 ". Forwarding as needed.", (uint64_t)slot.index);
1282 ServiceRegistryRef ref{registry};
1283 ref.get<AsyncQueue>();
1284 ref.get<DecongestionService>();
1285 ref.get<DataRelayer>();
1286 // Get the current timeslice for the slot.
1287 auto& variables = ref.get<TimesliceIndex>().getVariablesForSlot(slot);
1289 forwardInputs(registry, slot, dropped, oldestOutputInfo, false, true);
1290 };
1291 auto& relayer = ref.get<DataRelayer>();
1292 relayer.prunePending(onDrop);
1293 auto& queue = ref.get<AsyncQueue>();
1294 auto oldestPossibleTimeslice = relayer.getOldestPossibleOutput();
1295 AsyncQueueHelpers::run(queue, {oldestPossibleTimeslice.timeslice.value});
1296 if (shouldNotWait == false) {
1297 auto& dpContext = ref.get<DataProcessorContext>();
1298 dpContext.preLoopCallbacks(ref);
1299 }
1300 O2_SIGNPOST_END(device, lid, "run_loop", "Run loop completed. %{}s", shouldNotWait ? "Will immediately schedule a new one" : "Waiting for next event.");
1301 uv_run(state.loop, shouldNotWait ? UV_RUN_NOWAIT : UV_RUN_ONCE);
1302 O2_SIGNPOST_START(device, lid, "run_loop", "Run loop started. Loop reason %d.", state.loopReason);
1303 if ((state.loopReason & state.tracingFlags) != 0) {
1304 state.severityStack.push_back((int)fair::Logger::GetConsoleSeverity());
1305 fair::Logger::SetConsoleSeverity(fair::Severity::trace);
1306 } else if (state.severityStack.empty() == false) {
1307 fair::Logger::SetConsoleSeverity((fair::Severity)state.severityStack.back());
1308 state.severityStack.pop_back();
1309 }
1310 O2_SIGNPOST_EVENT_EMIT(device, lid, "run_loop", "Loop reason mask %x & %x = %x", state.loopReason, state.tracingFlags, state.loopReason & state.tracingFlags);
1311
1312 if ((state.loopReason & DeviceState::LoopReason::OOB_ACTIVITY) != 0) {
1313 O2_SIGNPOST_EVENT_EMIT(device, lid, "run_loop", "Out of band activity detected. Rescanning everything.");
1314 relayer.rescan();
1315 }
1316
1317 if (!state.pendingOffers.empty()) {
1318 O2_SIGNPOST_EVENT_EMIT(device, lid, "run_loop", "Pending %" PRIu64 " offers. updating the ComputingQuotaEvaluator.", (uint64_t)state.pendingOffers.size());
1319 ref.get<ComputingQuotaEvaluator>().updateOffers(state.pendingOffers, uv_now(state.loop));
1320 }
1321 }
1322
1323 // Notify on the main thread the new region callbacks, making sure
1324 // no callback is issued if there is something still processing.
1325 // Notice that we still need to perform callbacks also after
1326 // the socket epolled, because otherwise we would end up serving
1327 // the callback after the first data arrives is the system is too
1328 // fast to transition from Init to Run.
1329 {
1330 std::lock_guard<std::mutex> lock(mRegionInfoMutex);
1331 handleRegionCallbacks(mServiceRegistry, mPendingRegionInfos);
1332 }
1333
1334 // Receive and relay incoming data on the main thread so that I/O
1335 // overlaps with computation running concurrently on work threads.
1337
1338 assert(mStreams.size() == mHandles.size());
1340 TaskStreamRef streamRef{-1};
1341 for (size_t ti = 0; ti < mStreams.size(); ti++) {
1342 auto& taskInfo = mStreams[ti];
1343 if (taskInfo.running) {
1344 continue;
1345 }
1346 // Stream 0 is for when we run in
1347 streamRef.index = ti;
1348 }
1349 using o2::monitoring::Metric;
1350 using o2::monitoring::Monitoring;
1351 using o2::monitoring::tags::Key;
1352 using o2::monitoring::tags::Value;
1353 // We have an empty stream, let's check if we have enough
1354 // resources for it to run something
1355 if (streamRef.index != -1) {
1356 // Synchronous execution of the callbacks. This will be moved in the
1357 // moved in the on_socket_polled once we have threading in place.
1358 uv_work_t& handle = mHandles[streamRef.index];
1359 TaskStreamInfo& stream = mStreams[streamRef.index];
1360 handle.data = &mStreams[streamRef.index];
1361
1362 static std::function<void(ComputingQuotaOffer const&, ComputingQuotaStats const& stats)> reportExpiredOffer = [&registry = mServiceRegistry](ComputingQuotaOffer const& offer, ComputingQuotaStats const& stats) {
1363 ServiceRegistryRef ref{registry};
1364 auto& dpStats = ref.get<DataProcessingStats>();
1365 dpStats.updateStats({static_cast<short>(ProcessingStatsId::RESOURCE_OFFER_EXPIRED), DataProcessingStats::Op::Set, stats.totalExpiredOffers});
1366 dpStats.updateStats({static_cast<short>(ProcessingStatsId::ARROW_BYTES_EXPIRED), DataProcessingStats::Op::Set, stats.totalExpiredBytes});
1367 dpStats.updateStats({static_cast<short>(ProcessingStatsId::TIMESLICE_NUMBER_EXPIRED), DataProcessingStats::Op::Set, stats.totalExpiredTimeslices});
1368 dpStats.processCommandQueue();
1369 };
1370 auto ref = ServiceRegistryRef{mServiceRegistry};
1371
1372 // Deciding wether to run or not can be done by passing a request to
1373 // the evaluator. In this case, the request is always satisfied and
1374 // we run on whatever resource is available.
1375 auto& spec = ref.get<DeviceSpec const>();
1376 ComputingQuotaOffer accumulated;
1377 bool enough = ref.get<ComputingQuotaEvaluator>().selectOffer(streamRef.index, spec.resourcePolicy.request, uv_now(state.loop), &accumulated);
1378
1379 struct SchedulingStats {
1380 std::atomic<size_t> lastScheduled = 0;
1381 std::atomic<size_t> numberOfUnscheduledSinceLastScheduled = 0;
1382 std::atomic<size_t> numberOfUnscheduled = 0;
1383 std::atomic<size_t> numberOfScheduled = 0;
1384 std::atomic<size_t> nextWarnAt = 1;
1385 };
1386 static SchedulingStats schedulingStats;
1387 O2_SIGNPOST_ID_GENERATE(sid, scheduling);
1388 if (enough) {
1389 stream.id = streamRef;
1390 stream.running = true;
1391 stream.registry = &mServiceRegistry;
1392 schedulingStats.lastScheduled = uv_now(state.loop);
1393 schedulingStats.numberOfScheduled++;
1394 schedulingStats.numberOfUnscheduledSinceLastScheduled = 0;
1395 schedulingStats.nextWarnAt = 1;
1396 O2_SIGNPOST_EVENT_EMIT(scheduling, sid, "Run", "Enough resources to schedule computation on stream %d", streamRef.index);
1397 if (dplEnableMultithreding) [[unlikely]] {
1398 stream.task = &handle;
1399 uv_queue_work(state.loop, stream.task, run_callback, run_completion);
1400 } else {
1401 run_callback(&handle);
1402 run_completion(&handle, 0);
1403 }
1404 } else {
1405 auto const lastSched = schedulingStats.lastScheduled.load();
1406 auto const schedInfo = lastSched ? fmt::format(", last scheduled {} ms ago", uv_now(state.loop) - lastSched) : std::string(", never successfully scheduled");
1407 auto const buildMissingInfo = [&]() {
1408 auto const& required = spec.resourcePolicy.minRequired;
1409 std::string missingInfo;
1410 if (required.sharedMemory > 0 && accumulated.sharedMemory < required.sharedMemory) {
1411 missingInfo += fmt::format(" shared memory (have {} MB, need {} MB)", accumulated.sharedMemory / 1000000, required.sharedMemory / 1000000);
1412 }
1413 if (required.timeslices > 0 && accumulated.timeslices < required.timeslices) {
1414 missingInfo += fmt::format(" timeslices (have {}, need {})", accumulated.timeslices, required.timeslices);
1415 }
1416 if (required.cpu > 0 && accumulated.cpu < required.cpu) {
1417 missingInfo += fmt::format(" CPU cores (have {}, need {})", accumulated.cpu, required.cpu);
1418 }
1419 if (required.memory > 0 && accumulated.memory < required.memory) {
1420 missingInfo += fmt::format(" memory (have {} MB, need {} MB)", accumulated.memory / 1000000, required.memory / 1000000);
1421 }
1422 return missingInfo.empty() ? std::string(" (policy: ") + spec.resourcePolicy.name + ")" : " -" + missingInfo;
1423 };
1424 auto const timeSinceLastScheduled = lastSched ? uv_now(state.loop) - lastSched : 0;
1425 if (schedulingStats.numberOfUnscheduledSinceLastScheduled >= schedulingStats.nextWarnAt) {
1426 auto const missingStr = buildMissingInfo();
1427 if (timeSinceLastScheduled >= 50) {
1428 O2_SIGNPOST_EVENT_EMIT_WARN(scheduling, sid, "Run",
1429 "Not enough resources to schedule computation on stream %d. %zu consecutive skips%s. Missing:%s. Data is not lost and it will be scheduled again.",
1430 streamRef.index,
1431 schedulingStats.numberOfUnscheduledSinceLastScheduled.load(),
1432 schedInfo.c_str(),
1433 missingStr.c_str());
1434 } else {
1435 O2_SIGNPOST_EVENT_EMIT(scheduling, sid, "Run",
1436 "Not enough resources to schedule computation on stream %d. %zu consecutive skips%s. Missing:%s. Data is not lost and it will be scheduled again.",
1437 streamRef.index,
1438 schedulingStats.numberOfUnscheduledSinceLastScheduled.load(),
1439 schedInfo.c_str(),
1440 missingStr.c_str());
1441 }
1442 schedulingStats.nextWarnAt = schedulingStats.nextWarnAt * 2;
1443 } else {
1444 auto const missingStr = buildMissingInfo();
1445 O2_SIGNPOST_EVENT_EMIT(scheduling, sid, "Run",
1446 "Not enough resources to schedule computation on stream %d. %zu consecutive skips%s. Missing:%s. Data is not lost and it will be scheduled again.",
1447 streamRef.index,
1448 schedulingStats.numberOfUnscheduledSinceLastScheduled.load(),
1449 schedInfo.c_str(),
1450 missingStr.c_str());
1451 }
1452 schedulingStats.numberOfUnscheduled++;
1453 schedulingStats.numberOfUnscheduledSinceLastScheduled++;
1454 auto ref = ServiceRegistryRef{mServiceRegistry};
1455 ref.get<ComputingQuotaEvaluator>().handleExpired(reportExpiredOffer);
1456 }
1457 }
1458 }
1459
1460 O2_SIGNPOST_END(device, lid, "run_loop", "Run loop completed. Transition handling state %d.", (int)state.transitionHandling);
1461 auto& spec = ref.get<DeviceSpec const>();
1463 for (size_t ci = 0; ci < spec.inputChannels.size(); ++ci) {
1464 auto& info = state.inputChannelInfos[ci];
1465 info.parts.fParts.clear();
1466 }
1467 state.transitionHandling = TransitionHandlingState::NoTransition;
1468}
1469
1473{
1474 auto& context = ref.get<DataProcessorContext>();
1475 O2_SIGNPOST_ID_FROM_POINTER(dpid, device, &context);
1476 O2_SIGNPOST_START(device, dpid, "do_prepare", "Starting DataProcessorContext::doPrepare.");
1477
1478 {
1479 ref.get<CallbackService>().call<CallbackService::Id::ClockTick>();
1480 }
1481 // Whether or not we had something to do.
1482
1483 // Initialise the value for context.allDone. It will possibly be updated
1484 // below if any of the channels is not done.
1485 //
1486 // Notice that fake input channels (InputChannelState::Pull) cannot possibly
1487 // expect to receive an EndOfStream signal. Thus we do not wait for these
1488 // to be completed. In the case of data source devices, as they do not have
1489 // real data input channels, they have to signal EndOfStream themselves.
1490 auto& state = ref.get<DeviceState>();
1491 auto& spec = ref.get<DeviceSpec const>();
1492 O2_SIGNPOST_ID_FROM_POINTER(cid, device, state.inputChannelInfos.data());
1493 O2_SIGNPOST_START(device, cid, "do_prepare", "Reported channel states.");
1494 context.allDone = std::any_of(state.inputChannelInfos.begin(), state.inputChannelInfos.end(), [cid](const auto& info) {
1495 if (info.channel) {
1496 O2_SIGNPOST_EVENT_EMIT(device, cid, "do_prepare", "Input channel %{public}s%{public}s has %zu parts left and is in state %d.",
1497 info.channel->GetName().c_str(), (info.id.value == ChannelIndex::INVALID ? " (non DPL)" : ""), info.parts.fParts.size(), (int)info.state);
1498 } else {
1499 O2_SIGNPOST_EVENT_EMIT(device, cid, "do_prepare", "External channel %d is in state %d.", info.id.value, (int)info.state);
1500 }
1501 return (info.parts.fParts.empty() == true && info.state != InputChannelState::Pull);
1502 });
1503 O2_SIGNPOST_END(device, cid, "do_prepare", "End report.");
1504 O2_SIGNPOST_EVENT_EMIT(device, dpid, "do_prepare", "Processing %zu input channels.", spec.inputChannels.size());
1507 static std::vector<int> pollOrder;
1508 pollOrder.resize(state.inputChannelInfos.size());
1509 std::iota(pollOrder.begin(), pollOrder.end(), 0);
1510 std::sort(pollOrder.begin(), pollOrder.end(), [&infos = state.inputChannelInfos](int a, int b) {
1511 return infos[a].oldestForChannel.value < infos[b].oldestForChannel.value;
1512 });
1513
1514 // Nothing to poll...
1515 if (pollOrder.empty()) {
1516 O2_SIGNPOST_END(device, dpid, "do_prepare", "Nothing to poll. Waiting for next iteration.");
1517 return;
1518 }
1519 auto currentOldest = state.inputChannelInfos[pollOrder.front()].oldestForChannel;
1520 auto currentNewest = state.inputChannelInfos[pollOrder.back()].oldestForChannel;
1521 auto delta = currentNewest.value - currentOldest.value;
1522 O2_SIGNPOST_EVENT_EMIT(device, dpid, "do_prepare", "Oldest possible timeframe range %" PRIu64 " => %" PRIu64 " delta %" PRIu64,
1523 (int64_t)currentOldest.value, (int64_t)currentNewest.value, (int64_t)delta);
1524 auto& infos = state.inputChannelInfos;
1525
1526 if (context.balancingInputs) {
1527 static int pipelineLength = DefaultsHelpers::pipelineLength(*ref.get<RawDeviceService>().device()->fConfig);
1528 static uint64_t ahead = getenv("DPL_MAX_CHANNEL_AHEAD") ? std::atoll(getenv("DPL_MAX_CHANNEL_AHEAD")) : std::max(8, std::min(pipelineLength - 48, pipelineLength / 2));
1529 auto newEnd = std::remove_if(pollOrder.begin(), pollOrder.end(), [&infos, limitNew = currentOldest.value + ahead](int a) -> bool {
1530 return infos[a].oldestForChannel.value > limitNew;
1531 });
1532 for (auto it = pollOrder.begin(); it < pollOrder.end(); it++) {
1533 const auto& channelInfo = state.inputChannelInfos[*it];
1534 if (channelInfo.pollerIndex != -1) {
1535 auto& poller = state.activeInputPollers[channelInfo.pollerIndex];
1536 auto& pollerContext = *(PollerContext*)(poller->data);
1537 if (pollerContext.pollerState == PollerContext::PollerState::Connected || pollerContext.pollerState == PollerContext::PollerState::Suspended) {
1538 bool running = pollerContext.pollerState == PollerContext::PollerState::Connected;
1539 bool shouldBeRunning = it < newEnd;
1540 if (running != shouldBeRunning) {
1541 uv_poll_start(poller, shouldBeRunning ? UV_READABLE | UV_DISCONNECT | UV_PRIORITIZED : 0, &on_socket_polled);
1542 pollerContext.pollerState = shouldBeRunning ? PollerContext::PollerState::Connected : PollerContext::PollerState::Suspended;
1543 }
1544 }
1545 }
1546 }
1547 pollOrder.erase(newEnd, pollOrder.end());
1548 }
1549 O2_SIGNPOST_END(device, dpid, "do_prepare", "%zu channels pass the channel inbalance balance check.", pollOrder.size());
1550
1551 for (auto sci : pollOrder) {
1552 auto& info = state.inputChannelInfos[sci];
1553 O2_SIGNPOST_ID_FROM_POINTER(cid, device, &info);
1554 O2_SIGNPOST_START(device, cid, "channels", "Processing channel %s", info.channel->GetName().c_str());
1555
1557 context.allDone = false;
1558 }
1559 if (info.state != InputChannelState::Running) {
1560 // Remember to flush data if we are not running
1561 // and there is some message pending.
1562 if (info.parts.Size()) {
1564 }
1565 O2_SIGNPOST_END(device, cid, "channels", "Flushing channel %s which is in state %d and has %zu parts still pending.",
1566 info.channel->GetName().c_str(), (int)info.state, info.parts.Size());
1567 continue;
1568 }
1569 if (info.channel == nullptr) {
1570 O2_SIGNPOST_END(device, cid, "channels", "Channel %s which is in state %d is nullptr and has %zu parts still pending.",
1571 info.channel->GetName().c_str(), (int)info.state, info.parts.Size());
1572 continue;
1573 }
1574 // Only poll DPL channels for now.
1575 if (info.channelType != ChannelAccountingType::DPL) {
1576 O2_SIGNPOST_END(device, cid, "channels", "Channel %s which is in state %d is not a DPL channel and has %zu parts still pending.",
1577 info.channel->GetName().c_str(), (int)info.state, info.parts.Size());
1578 continue;
1579 }
1580 auto& socket = info.channel->GetSocket();
1581 // If we have pending events from a previous iteration,
1582 // we do receive in any case.
1583 // Otherwise we check if there is any pending event and skip
1584 // this channel in case there is none.
1585 if (info.hasPendingEvents == 0) {
1586 socket.Events(&info.hasPendingEvents);
1587 // If we do not read, we can continue.
1588 if ((info.hasPendingEvents & 1) == 0 && (info.parts.Size() == 0)) {
1589 O2_SIGNPOST_END(device, cid, "channels", "No pending events and no remaining parts to process for channel %{public}s", info.channel->GetName().c_str());
1590 continue;
1591 }
1592 }
1593 // We can reset this, because it means we have seen at least 1
1594 // message after the UV_READABLE was raised.
1595 info.readPolled = false;
1596 // Notice that there seems to be a difference between the documentation
1597 // of zeromq and the observed behavior. The fact that ZMQ_POLLIN
1598 // is raised does not mean that a message is immediately available to
1599 // read, just that it will be available soon, so the receive can
1600 // still return -2. To avoid this we keep receiving on the socket until
1601 // we get a message. In order not to overflow the DPL queue we process
1602 // one message at the time and we keep track of wether there were more
1603 // to process.
1604 bool newMessages = false;
1605 while (true) {
1606 O2_SIGNPOST_EVENT_EMIT(device, cid, "channels", "Receiving loop called for channel %{public}s (%d) with oldest possible timeslice %zu",
1607 info.channel->GetName().c_str(), info.id.value, info.oldestForChannel.value);
1608 if (info.parts.Size() < 64) {
1609 fair::mq::Parts parts;
1610 info.channel->Receive(parts, 0);
1611 if (parts.Size()) {
1612 O2_SIGNPOST_EVENT_EMIT(device, cid, "channels", "Received %zu parts from channel %{public}s (%d).", parts.Size(), info.channel->GetName().c_str(), info.id.value);
1613 }
1614 for (auto&& part : parts) {
1615 info.parts.fParts.emplace_back(std::move(part));
1616 }
1617 newMessages |= true;
1618 }
1619
1620 if (info.parts.Size() >= 0) {
1622 // Receiving data counts as activity now, so that
1623 // We can make sure we process all the pending
1624 // messages without hanging on the uv_run.
1625 break;
1626 }
1627 }
1628 // We check once again for pending events, keeping track if this was the
1629 // case so that we can immediately repeat this loop and avoid remaining
1630 // stuck in uv_run. This is because we will not get notified on the socket
1631 // if more events are pending due to zeromq level triggered approach.
1632 socket.Events(&info.hasPendingEvents);
1633 if (info.hasPendingEvents) {
1634 info.readPolled = false;
1635 // In case there were messages, we consider it as activity
1636 if (newMessages) {
1637 state.lastActiveDataProcessor.store(&context);
1638 }
1639 }
1640 O2_SIGNPOST_END(device, cid, "channels", "Done processing channel %{public}s (%d).",
1641 info.channel->GetName().c_str(), info.id.value);
1642 }
1643}
1644
1646{
1647 auto& context = ref.get<DataProcessorContext>();
1648 auto& streamContext = ref.get<StreamContext>();
1649 O2_SIGNPOST_ID_FROM_POINTER(dpid, device, &context);
1650 auto& state = ref.get<DeviceState>();
1651 auto& spec = ref.get<DeviceSpec const>();
1652
1653 if (state.streaming == StreamingState::Idle) {
1654 return;
1655 }
1656
1657 streamContext.completed.clear();
1658 streamContext.completed.reserve(16);
1659 if (DataProcessingDevice::tryDispatchComputation(ref, streamContext.completed)) {
1660 state.lastActiveDataProcessor.store(&context);
1661 }
1662 DanglingContext danglingContext{*context.registry};
1663
1664 context.preDanglingCallbacks(danglingContext);
1665 if (state.lastActiveDataProcessor.load() == nullptr) {
1666 ref.get<CallbackService>().call<CallbackService::Id::Idle>();
1667 }
1668 auto activity = ref.get<DataRelayer>().processDanglingInputs(context.expirationHandlers, *context.registry, true);
1669 if (activity.expiredSlots > 0) {
1670 state.lastActiveDataProcessor = &context;
1671 }
1672
1673 streamContext.completed.clear();
1674 if (DataProcessingDevice::tryDispatchComputation(ref, streamContext.completed)) {
1675 state.lastActiveDataProcessor = &context;
1676 }
1677
1678 context.postDanglingCallbacks(danglingContext);
1679
1680 // If we got notified that all the sources are done, we call the EndOfStream
1681 // callback and return false. Notice that what happens next is actually
1682 // dependent on the callback, not something which is controlled by the
1683 // framework itself.
1684 if (context.allDone == true && state.streaming == StreamingState::Streaming) {
1686 state.lastActiveDataProcessor = &context;
1687 }
1688
1689 if (state.streaming == StreamingState::EndOfStreaming) {
1690 O2_SIGNPOST_EVENT_EMIT(device, dpid, "state", "We are in EndOfStreaming. Flushing queues.");
1691 // We keep processing data until we are Idle.
1692 // FIXME: not sure this is the correct way to drain the queues, but
1693 // I guess we will see.
1696 auto& relayer = ref.get<DataRelayer>();
1697
1698 bool shouldProcess = DataProcessingHelpers::hasOnlyGenerated(spec) == false;
1699
1700 while (DataProcessingDevice::tryDispatchComputation(ref, streamContext.completed) && shouldProcess) {
1701 relayer.processDanglingInputs(context.expirationHandlers, *context.registry, false);
1702 }
1703
1704 auto& timingInfo = ref.get<TimingInfo>();
1705 // We should keep the data generated at end of stream only for those
1706 // which are not sources.
1707 timingInfo.keepAtEndOfStream = shouldProcess;
1708 // Fill timinginfo with some reasonable values for data sent with endOfStream
1709 timingInfo.timeslice = relayer.getOldestPossibleOutput().timeslice.value;
1710 timingInfo.tfCounter = -1;
1711 timingInfo.firstTForbit = -1;
1712 // timingInfo.runNumber = ; // Not sure where to get this if not already set
1713 timingInfo.creation = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now()).time_since_epoch().count();
1714 O2_SIGNPOST_EVENT_EMIT(calibration, dpid, "calibration", "TimingInfo.keepAtEndOfStream %d", timingInfo.keepAtEndOfStream);
1715
1716 EndOfStreamContext eosContext{*context.registry, ref.get<DataAllocator>()};
1717
1718 context.preEOSCallbacks(eosContext);
1719 auto& streamContext = ref.get<StreamContext>();
1720 streamContext.preEOSCallbacks(eosContext);
1721 ref.get<CallbackService>().call<CallbackService::Id::EndOfStream>(eosContext);
1722 streamContext.postEOSCallbacks(eosContext);
1723 context.postEOSCallbacks(eosContext);
1724
1725 for (auto& channel : spec.outputChannels) {
1726 O2_SIGNPOST_EVENT_EMIT(device, dpid, "state", "Sending end of stream to %{public}s.", channel.name.c_str());
1728 }
1729 // This is needed because the transport is deleted before the device.
1730 relayer.clear();
1732 // In case we should process, note the data processor responsible for it
1733 if (shouldProcess) {
1734 state.lastActiveDataProcessor = &context;
1735 }
1736 // On end of stream we shut down all output pollers.
1737 O2_SIGNPOST_EVENT_EMIT(device, dpid, "state", "Shutting down output pollers.");
1738 for (auto& poller : state.activeOutputPollers) {
1739 uv_poll_stop(poller);
1740 }
1741 return;
1742 }
1743
1744 if (state.streaming == StreamingState::Idle) {
1745 // On end of stream we shut down all output pollers.
1746 O2_SIGNPOST_EVENT_EMIT(device, dpid, "state", "Shutting down output pollers.");
1747 for (auto& poller : state.activeOutputPollers) {
1748 uv_poll_stop(poller);
1749 }
1750 }
1751
1752 return;
1753}
1754
1756{
1757 ServiceRegistryRef ref{mServiceRegistry};
1758 ref.get<DataRelayer>().clear();
1759 auto& deviceContext = ref.get<DeviceContext>();
1760 // If the signal handler is there, we should
1761 // hide the registry from it, so that we do not
1762 // end up calling the signal handler on something
1763 // which might not be there anymore.
1764 if (deviceContext.sigusr1Handle) {
1765 deviceContext.sigusr1Handle->data = nullptr;
1766 }
1767 // Makes sure we do not have a working context on
1768 // shutdown.
1769 for (auto& handle : ref.get<DeviceState>().activeSignals) {
1770 handle->data = nullptr;
1771 }
1772}
1773
1776 {
1777 }
1778};
1779
1780auto forwardOnInsertion(ServiceRegistryRef& ref, std::span<fair::mq::MessagePtr>& messages) -> void
1781{
1782 O2_SIGNPOST_ID_GENERATE(sid, forwarding);
1783
1784 auto& spec = ref.get<DeviceSpec const>();
1785 auto& context = ref.get<DataProcessorContext>();
1786 if (context.forwardPolicy == ForwardPolicy::AfterProcessing || spec.forwards.empty()) {
1787 O2_SIGNPOST_EVENT_EMIT(device, sid, "device", "Early forwardinding not enabled / needed.");
1788 return;
1789 }
1790
1791 O2_SIGNPOST_EVENT_EMIT(device, sid, "device", "Early forwardinding before injecting data into relayer.");
1792 auto& timesliceIndex = ref.get<TimesliceIndex>();
1793 auto oldestTimeslice = timesliceIndex.getOldestPossibleOutput();
1794
1795 auto& proxy = ref.get<FairMQDeviceProxy>();
1796
1797 O2_SIGNPOST_START(forwarding, sid, "forwardInputs",
1798 "Starting forwarding for incoming messages with oldestTimeslice %zu with copy",
1799 oldestTimeslice.timeslice.value);
1800 std::vector<fair::mq::Parts> forwardedParts(proxy.getNumForwardChannels());
1801 DataProcessingHelpers::routeForwardedMessages(proxy, messages, forwardedParts, true, false);
1802
1803 for (int fi = 0; fi < proxy.getNumForwardChannels(); fi++) {
1804 if (forwardedParts[fi].Size() == 0) {
1805 continue;
1806 }
1807 ForwardChannelInfo info = proxy.getForwardChannelInfo(ChannelIndex{fi});
1808 auto& parts = forwardedParts[fi];
1809 if (info.policy == nullptr) {
1810 O2_SIGNPOST_EVENT_EMIT_ERROR(forwarding, sid, "forwardInputs", "Forwarding to %{public}s %d has no policy.", info.name.c_str(), fi);
1811 continue;
1812 }
1813 O2_SIGNPOST_EVENT_EMIT(forwarding, sid, "forwardInputs", "Forwarding to %{public}s %d", info.name.c_str(), fi);
1814 info.policy->forward(parts, ChannelIndex{fi}, ref);
1815 }
1816 auto& asyncQueue = ref.get<AsyncQueue>();
1817 auto& decongestion = ref.get<DecongestionService>();
1818 O2_SIGNPOST_ID_GENERATE(aid, async_queue);
1819 O2_SIGNPOST_EVENT_EMIT(async_queue, aid, "forwardInputs", "Queuing forwarding oldestPossible %zu", oldestTimeslice.timeslice.value);
1820 AsyncQueueHelpers::post(asyncQueue, AsyncTask{.timeslice = oldestTimeslice.timeslice, .id = decongestion.oldestPossibleTimesliceTask, .debounce = -1, .callback = decongestionCallbackLate}
1821 .user<DecongestionContext>({.ref = ref, .oldestTimeslice = oldestTimeslice}));
1822 O2_SIGNPOST_END(forwarding, sid, "forwardInputs", "Forwarding done");
1823};
1824
1830{
1833
1834 auto& context = ref.get<DataProcessorContext>();
1835 // This is the same id as the upper level function, so we get the events
1836 // associated with the same interval. We will simply use "handle_data" as
1837 // the category.
1838 O2_SIGNPOST_ID_FROM_POINTER(cid, device, &info);
1839
1840 // This is how we validate inputs. I.e. we try to enforce the O2 Data model
1841 // and we do a few stats. We bind parts as a lambda captured variable, rather
1842 // than an input, because we do not want the outer loop actually be exposed
1843 // to the implementation details of the messaging layer.
1844 auto getInputTypes = [&info, &context]() -> std::optional<std::vector<InputInfo>> {
1845 O2_SIGNPOST_ID_FROM_POINTER(cid, device, &info);
1846 auto ref = ServiceRegistryRef{*context.registry};
1847 auto& stats = ref.get<DataProcessingStats>();
1848 auto& state = ref.get<DeviceState>();
1849 auto& parts = info.parts;
1850 stats.updateStats({(int)ProcessingStatsId::TOTAL_INPUTS, DataProcessingStats::Op::Set, (int64_t)parts.Size()});
1851
1852 std::vector<InputInfo> results;
1853 // we can reserve the upper limit
1854 results.reserve(parts.Size() / 2);
1855 size_t nTotalPayloads = 0;
1856
1857 auto insertInputInfo = [&results, &nTotalPayloads](size_t position, size_t length, InputType type, ChannelIndex index) {
1858 results.emplace_back(position, length, type, index);
1859 if (type != InputType::Invalid && length > 1) {
1860 nTotalPayloads += length - 1;
1861 }
1862 };
1863
1864 for (size_t pi = 0; pi < parts.Size(); pi += 2) {
1865 auto* headerData = parts.At(pi)->GetData();
1866 auto sih = o2::header::get<SourceInfoHeader*>(headerData);
1867 auto dh = o2::header::get<DataHeader*>(headerData);
1868 if (sih) {
1869 O2_SIGNPOST_EVENT_EMIT(device, cid, "handle_data", "Got SourceInfoHeader with state %d", (int)sih->state);
1870 info.state = sih->state;
1871 insertInputInfo(pi, 2, InputType::SourceInfo, info.id);
1872 state.lastActiveDataProcessor = &context;
1873 if (dh) {
1874 LOGP(error, "Found data attached to a SourceInfoHeader");
1875 }
1876 continue;
1877 }
1878 auto dih = o2::header::get<DomainInfoHeader*>(headerData);
1879 if (dih) {
1880 O2_SIGNPOST_EVENT_EMIT(device, cid, "handle_data", "Got DomainInfoHeader with oldestPossibleTimeslice %d", (int)dih->oldestPossibleTimeslice);
1881 insertInputInfo(pi, 2, InputType::DomainInfo, info.id);
1882 state.lastActiveDataProcessor = &context;
1883 if (dh) {
1884 LOGP(error, "Found data attached to a DomainInfoHeader");
1885 }
1886 continue;
1887 }
1888 if (!dh) {
1889 insertInputInfo(pi, 0, InputType::Invalid, info.id);
1890 O2_SIGNPOST_EVENT_EMIT_ERROR(device, cid, "handle_data", "Header is not a DataHeader?");
1891 continue;
1892 }
1893 if (dh->payloadSize > parts.At(pi + 1)->GetSize()) {
1894 insertInputInfo(pi, 0, InputType::Invalid, info.id);
1895 O2_SIGNPOST_EVENT_EMIT_ERROR(device, cid, "handle_data", "DataHeader payloadSize mismatch");
1896 continue;
1897 }
1898 auto dph = o2::header::get<DataProcessingHeader*>(headerData);
1899 // We only deal with the tracking of parts if the log is enabled.
1900 // This is because in principle we should track the size of each of
1901 // the parts and sum it up. Not for now.
1902 O2_SIGNPOST_ID_FROM_POINTER(pid, parts, headerData);
1903 O2_SIGNPOST_START(parts, pid, "parts", "Processing DataHeader %{public}-4s/%{public}-16s/%d with splitPayloadParts %d and splitPayloadIndex %d",
1904 dh->dataOrigin.str, dh->dataDescription.str, dh->subSpecification, dh->splitPayloadParts, dh->splitPayloadIndex);
1905 if (!dph) {
1906 insertInputInfo(pi, 2, InputType::Invalid, info.id);
1907 O2_SIGNPOST_EVENT_EMIT_ERROR(device, cid, "handle_data", "Header stack does not contain DataProcessingHeader");
1908 continue;
1909 }
1910 if (dh->splitPayloadParts > 0 && dh->splitPayloadParts == dh->splitPayloadIndex) {
1911 // this is indicating a sequence of payloads following the header
1912 // FIXME: we will probably also set the DataHeader version
1913 insertInputInfo(pi, dh->splitPayloadParts + 1, InputType::Data, info.id);
1914 pi += dh->splitPayloadParts - 1;
1915 } else {
1916 // We can set the type for the next splitPayloadParts
1917 // because we are guaranteed they are all the same.
1918 // If splitPayloadParts = 0, we assume that means there is only one (header, payload)
1919 // pair.
1920 size_t finalSplitPayloadIndex = pi + (dh->splitPayloadParts > 0 ? dh->splitPayloadParts : 1) * 2;
1921 if (finalSplitPayloadIndex > parts.Size()) {
1922 O2_SIGNPOST_EVENT_EMIT_ERROR(device, cid, "handle_data", "DataHeader::splitPayloadParts invalid");
1923 insertInputInfo(pi, 0, InputType::Invalid, info.id);
1924 continue;
1925 }
1926 insertInputInfo(pi, 2, InputType::Data, info.id);
1927 for (; pi + 2 < finalSplitPayloadIndex; pi += 2) {
1928 insertInputInfo(pi + 2, 2, InputType::Data, info.id);
1929 }
1930 }
1931 }
1932 if (results.size() + nTotalPayloads != parts.Size()) {
1933 O2_SIGNPOST_EVENT_EMIT_ERROR(device, cid, "handle_data", "inconsistent number of inputs extracted. %zu vs parts (%zu)", results.size() + nTotalPayloads, parts.Size());
1934 return std::nullopt;
1935 }
1936 return results;
1937 };
1938
1939 auto reportError = [ref](const char* message) {
1940 auto& stats = ref.get<DataProcessingStats>();
1942 };
1943
1944 auto handleValidMessages = [&info, ref, &reportError, &context](std::vector<InputInfo> const& inputInfos) {
1945 auto& relayer = ref.get<DataRelayer>();
1946 auto& state = ref.get<DeviceState>();
1947 static WaitBackpressurePolicy policy;
1948 auto& parts = info.parts;
1949 // We relay execution to make sure we have a complete set of parts
1950 // available.
1951 bool hasBackpressure = false;
1952 size_t minBackpressureTimeslice = -1;
1953 bool hasData = false;
1954 size_t oldestPossibleTimeslice = -1;
1955 static std::vector<int> ordering;
1956 // Same as inputInfos but with iota.
1957 ordering.resize(inputInfos.size());
1958 std::iota(ordering.begin(), ordering.end(), 0);
1959 // stable sort orderings by type and position
1960 std::stable_sort(ordering.begin(), ordering.end(), [&inputInfos](int const& a, int const& b) {
1961 auto const& ai = inputInfos[a];
1962 auto const& bi = inputInfos[b];
1963 if (ai.type != bi.type) {
1964 return ai.type < bi.type;
1965 }
1966 return ai.position < bi.position;
1967 });
1968 for (size_t ii = 0; ii < inputInfos.size(); ++ii) {
1969 auto const& input = inputInfos[ordering[ii]];
1970 switch (input.type) {
1971 case InputType::Data: {
1972 hasData = true;
1973 auto headerIndex = input.position;
1974 auto nMessages = 0;
1975 auto nPayloadsPerHeader = 0;
1976 if (input.size > 2) {
1977 // header and multiple payload sequence
1978 nMessages = input.size;
1979 nPayloadsPerHeader = nMessages - 1;
1980 } else {
1981 // multiple header-payload pairs
1982 auto dh = o2::header::get<DataHeader*>(parts.At(headerIndex)->GetData());
1983 nMessages = dh->splitPayloadParts > 0 ? dh->splitPayloadParts * 2 : 2;
1984 nPayloadsPerHeader = 1;
1985 ii += (nMessages / 2) - 1;
1986 }
1987 auto onDrop = [ref](TimesliceSlot slot, std::vector<std::span<fair::mq::MessagePtr>>& dropped, TimesliceIndex::OldestOutputInfo oldestOutputInfo) {
1988 O2_SIGNPOST_ID_GENERATE(cid, async_queue);
1989 O2_SIGNPOST_EVENT_EMIT(async_queue, cid, "onDrop", "Dropping message from slot %zu. Forwarding as needed. Timeslice %zu",
1990 slot.index, oldestOutputInfo.timeslice.value);
1991 ref.get<AsyncQueue>();
1992 ref.get<DecongestionService>();
1993 ref.get<DataRelayer>();
1994 // Get the current timeslice for the slot.
1995 auto& variables = ref.get<TimesliceIndex>().getVariablesForSlot(slot);
1997 forwardInputs(ref, slot, dropped, oldestOutputInfo, false, true);
1998 };
1999
2000 auto relayed = relayer.relay(parts.At(headerIndex)->GetData(),
2001 &parts.At(headerIndex),
2002 input,
2003 nMessages,
2004 nPayloadsPerHeader,
2005 context.forwardPolicy == ForwardPolicy::AtInjection ? forwardOnInsertion : nullptr,
2006 onDrop);
2007 switch (relayed.type) {
2009 if (info.normalOpsNotified == true && info.backpressureNotified == false) {
2010 LOGP(alarm, "Backpressure on channel {}. Waiting.", info.channel->GetName());
2011 auto& monitoring = ref.get<o2::monitoring::Monitoring>();
2012 monitoring.send(o2::monitoring::Metric{1, fmt::format("backpressure_{}", info.channel->GetName())});
2013 info.backpressureNotified = true;
2014 info.normalOpsNotified = false;
2015 }
2016 policy.backpressure(info);
2017 hasBackpressure = true;
2018 minBackpressureTimeslice = std::min<size_t>(minBackpressureTimeslice, relayed.timeslice.value);
2019 break;
2023 if (info.normalOpsNotified == false && info.backpressureNotified == true) {
2024 LOGP(info, "Back to normal on channel {}.", info.channel->GetName());
2025 auto& monitoring = ref.get<o2::monitoring::Monitoring>();
2026 monitoring.send(o2::monitoring::Metric{0, fmt::format("backpressure_{}", info.channel->GetName())});
2027 info.normalOpsNotified = true;
2028 info.backpressureNotified = false;
2029 }
2030 break;
2031 }
2032 } break;
2033 case InputType::SourceInfo: {
2034 LOGP(detail, "Received SourceInfo");
2035 auto& context = ref.get<DataProcessorContext>();
2036 state.lastActiveDataProcessor = &context;
2037 auto headerIndex = input.position;
2038 auto payloadIndex = input.position + 1;
2039 assert(payloadIndex < parts.Size());
2040 // FIXME: the message with the end of stream cannot contain
2041 // split parts.
2042 parts.At(headerIndex).reset(nullptr);
2043 parts.At(payloadIndex).reset(nullptr);
2044 // for (size_t i = 0; i < dh->splitPayloadParts > 0 ? dh->splitPayloadParts * 2 - 1 : 1; ++i) {
2045 // parts.At(headerIndex + 1 + i).reset(nullptr);
2046 // }
2047 // pi += dh->splitPayloadParts > 0 ? dh->splitPayloadParts - 1 : 0;
2048
2049 } break;
2050 case InputType::DomainInfo: {
2053 auto& context = ref.get<DataProcessorContext>();
2054 state.lastActiveDataProcessor = &context;
2055 auto headerIndex = input.position;
2056 auto payloadIndex = input.position + 1;
2057 assert(payloadIndex < parts.Size());
2058 // FIXME: the message with the end of stream cannot contain
2059 // split parts.
2060
2061 auto dih = o2::header::get<DomainInfoHeader*>(parts.At(headerIndex)->GetData());
2062 if (hasBackpressure && dih->oldestPossibleTimeslice >= minBackpressureTimeslice) {
2063 break;
2064 }
2065 oldestPossibleTimeslice = std::min(oldestPossibleTimeslice, dih->oldestPossibleTimeslice);
2066 LOGP(debug, "Got DomainInfoHeader, new oldestPossibleTimeslice {} on channel {}", oldestPossibleTimeslice, info.id.value);
2067 parts.At(headerIndex).reset(nullptr);
2068 parts.At(payloadIndex).reset(nullptr);
2069 } break;
2070 case InputType::Invalid: {
2071 reportError("Invalid part found.");
2072 } break;
2073 }
2074 }
2077 if (oldestPossibleTimeslice != (size_t)-1) {
2078 info.oldestForChannel = {oldestPossibleTimeslice};
2079 auto& context = ref.get<DataProcessorContext>();
2080 context.domainInfoUpdatedCallback(*context.registry, oldestPossibleTimeslice, info.id);
2081 ref.get<CallbackService>().call<CallbackService::Id::DomainInfoUpdated>((ServiceRegistryRef)*context.registry, (size_t)oldestPossibleTimeslice, (ChannelIndex)info.id);
2082 state.lastActiveDataProcessor = &context;
2083 }
2084 auto it = std::remove_if(parts.fParts.begin(), parts.fParts.end(), [](auto& msg) -> bool { return msg.get() == nullptr; });
2085 parts.fParts.erase(it, parts.end());
2086 if (parts.fParts.size()) {
2087 LOG(debug) << parts.fParts.size() << " messages backpressured";
2088 }
2089 };
2090
2091 // Second part. This is the actual outer loop we want to obtain, with
2092 // implementation details which can be read. Notice how most of the state
2093 // is actually hidden. For example we do not expose what "input" is. This
2094 // will allow us to keep the same toplevel logic even if the actual meaning
2095 // of input is changed (for example we might move away from multipart
2096 // messages). Notice also that we need to act diffently depending on the
2097 // actual CompletionOp we want to perform. In particular forwarding inputs
2098 // also gets rid of them from the cache.
2099 auto inputTypes = getInputTypes();
2100 if (bool(inputTypes) == false) {
2101 reportError("Parts should come in couples. Dropping it.");
2102 return;
2103 }
2104 handleValidMessages(*inputTypes);
2105 return;
2106}
2107
2108namespace
2109{
2110struct InputLatency {
2111 uint64_t minLatency = std::numeric_limits<uint64_t>::max();
2112 uint64_t maxLatency = std::numeric_limits<uint64_t>::min();
2113};
2114
2115auto calculateInputRecordLatency(InputRecord const& record, uint64_t currentTime) -> InputLatency
2116{
2117 InputLatency result;
2118
2119 for (auto& item : record) {
2120 auto* header = o2::header::get<DataProcessingHeader*>(item.header);
2121 if (header == nullptr) {
2122 continue;
2123 }
2124 int64_t partLatency = (0x7fffffffffffffff & currentTime) - (0x7fffffffffffffff & header->creation);
2125 if (partLatency < 0) {
2126 partLatency = 0;
2127 }
2128 result.minLatency = std::min(result.minLatency, (uint64_t)partLatency);
2129 result.maxLatency = std::max(result.maxLatency, (uint64_t)partLatency);
2130 }
2131 return result;
2132};
2133
2134auto calculateTotalInputRecordSize(InputRecord const& record) -> int
2135{
2136 size_t totalInputSize = 0;
2137 for (auto& item : record) {
2138 auto* header = o2::header::get<DataHeader*>(item.header);
2139 if (header == nullptr) {
2140 continue;
2141 }
2142 totalInputSize += header->payloadSize;
2143 }
2144 return totalInputSize;
2145};
2146
2147template <typename T>
2148void update_maximum(std::atomic<T>& maximum_value, T const& value) noexcept
2149{
2150 T prev_value = maximum_value;
2151 while (prev_value < value &&
2152 !maximum_value.compare_exchange_weak(prev_value, value)) {
2153 }
2154}
2155} // namespace
2156
2157bool DataProcessingDevice::tryDispatchComputation(ServiceRegistryRef ref, std::vector<DataRelayer::RecordAction>& completed)
2158{
2159 auto& context = ref.get<DataProcessorContext>();
2160 LOGP(debug, "DataProcessingDevice::tryDispatchComputation");
2161 // This is the actual hidden state for the outer loop. In case we decide we
2162 // want to support multithreaded dispatching of operations, I can simply
2163 // move these to some thread local store and the rest of the lambdas
2164 // should work just fine.
2165 std::vector<std::span<fair::mq::MessagePtr>> currentSetOfInputs;
2166 std::vector<std::vector<fair::mq::MessagePtr>> ownedInputs;
2167
2168 //
2169 auto getInputSpan = [ref, &currentSetOfInputs, &ownedInputs](TimesliceSlot slot, bool consume = true) {
2170 auto& relayer = ref.get<DataRelayer>();
2171 if (consume) {
2172 ownedInputs = relayer.consumeAllInputsForTimeslice(slot);
2173 } else {
2174 ownedInputs = relayer.consumeExistingInputsForTimeslice(slot);
2175 }
2176 currentSetOfInputs.resize(ownedInputs.size());
2177 for (size_t i = 0; i < ownedInputs.size(); ++i) {
2178 currentSetOfInputs[i] = std::span(ownedInputs[i]);
2179 }
2180 // Convert raw message indices directly to a DataRef in O(1).
2181 // Used both by the sequential PartIterator and as the fallback for positional access.
2182 auto indicesGetter = [&currentSetOfInputs](size_t i, DataRefIndices indices) -> DataRef {
2183 auto const& msgs = currentSetOfInputs[i];
2184 if (msgs.size() <= indices.headerIdx) {
2185 return DataRef{};
2186 }
2187 auto const& headerMsg = msgs[indices.headerIdx];
2188 char const* payloadData = nullptr;
2189 size_t payloadSize = 0;
2190 if (msgs.size() > indices.payloadIdx && msgs[indices.payloadIdx]) {
2191 payloadData = static_cast<char const*>(msgs[indices.payloadIdx]->GetData());
2192 payloadSize = msgs[indices.payloadIdx]->GetSize();
2193 }
2194 return DataRef{nullptr,
2195 headerMsg ? static_cast<char const*>(headerMsg->GetData()) : nullptr,
2196 payloadData,
2197 payloadSize};
2198 };
2199 auto nofPartsGetter = [&currentSetOfInputs](size_t i) -> size_t {
2200 return (currentSetOfInputs[i] | count_payloads{});
2201 };
2202 auto refCountGetter = [&currentSetOfInputs](size_t idx) -> int {
2203 auto& header = static_cast<const fair::mq::shmem::Message&>(*(currentSetOfInputs[idx] | get_header{0}));
2204 return header.GetRefCount();
2205 };
2206 auto nextIndicesGetter = [&currentSetOfInputs](size_t i, DataRefIndices current) -> DataRefIndices {
2207 auto next = currentSetOfInputs[i] | get_next_pair{current};
2208 return next.headerIdx < currentSetOfInputs[i].size() ? next : DataRefIndices{size_t(-1), size_t(-1)};
2209 };
2210 auto payloadGetter = [&currentSetOfInputs](size_t i, DataRefIndices current) -> fair::mq::Message* {
2211 auto const& msgs = currentSetOfInputs[i];
2212 if (msgs.size() <= current.payloadIdx || !msgs[current.payloadIdx]) {
2213 return nullptr;
2214 }
2215 return msgs[current.payloadIdx].get();
2216 };
2217 return InputSpan{nofPartsGetter, refCountGetter, indicesGetter, nextIndicesGetter, payloadGetter, currentSetOfInputs.size()};
2218 };
2219
2220 auto markInputsAsDone = [ref](TimesliceSlot slot) -> void {
2221 auto& relayer = ref.get<DataRelayer>();
2223 };
2224
2225 // I need a preparation step which gets the current timeslice id and
2226 // propagates it to the various contextes (i.e. the actual entities which
2227 // create messages) because the messages need to have the timeslice id into
2228 // it.
2229 auto prepareAllocatorForCurrentTimeSlice = [ref](TimesliceSlot i) -> void {
2230 auto& relayer = ref.get<DataRelayer>();
2231 auto& timingInfo = ref.get<TimingInfo>();
2232 auto timeslice = relayer.getTimesliceForSlot(i);
2233
2234 timingInfo.timeslice = timeslice.value;
2235 timingInfo.tfCounter = relayer.getFirstTFCounterForSlot(i);
2236 timingInfo.firstTForbit = relayer.getFirstTFOrbitForSlot(i);
2237 timingInfo.runNumber = relayer.getRunNumberForSlot(i);
2238 timingInfo.creation = relayer.getCreationTimeForSlot(i);
2239 };
2240 auto updateRunInformation = [ref](TimesliceSlot i) -> void {
2241 auto& dataProcessorContext = ref.get<DataProcessorContext>();
2242 auto& relayer = ref.get<DataRelayer>();
2243 auto& timingInfo = ref.get<TimingInfo>();
2244 auto timeslice = relayer.getTimesliceForSlot(i);
2245 // We report wether or not this timing info refers to a new Run.
2246 timingInfo.globalRunNumberChanged = !TimingInfo::timesliceIsTimer(timeslice.value) && dataProcessorContext.lastRunNumberProcessed != timingInfo.runNumber;
2247 // A switch to runNumber=0 should not appear and thus does not set globalRunNumberChanged, unless it is seen in the first processed timeslice
2248 timingInfo.globalRunNumberChanged &= (dataProcessorContext.lastRunNumberProcessed == -1 || timingInfo.runNumber != 0);
2249 // FIXME: for now there is only one stream, however we
2250 // should calculate this correctly once we finally get the
2251 // the StreamContext in.
2252 timingInfo.streamRunNumberChanged = timingInfo.globalRunNumberChanged;
2253 };
2254
2255 // When processing them, timers will have to be cleaned up
2256 // to avoid double counting them.
2257 // This was actually the easiest solution we could find for
2258 // O2-646.
2259 auto cleanTimers = [&currentSetOfInputs, &ownedInputs](TimesliceSlot slot, InputRecord& record) {
2260 assert(record.size() == currentSetOfInputs.size());
2261 for (size_t ii = 0, ie = record.size(); ii < ie; ++ii) {
2262 // assuming that for timer inputs we do have exactly one PartRef object
2263 // in the MessageSet, multiple PartRef Objects are only possible for either
2264 // split payload messages of wildcard matchers, both for data inputs
2265 DataRef input = record.getByPos(ii);
2266 if (input.spec->lifetime != Lifetime::Timer) {
2267 continue;
2268 }
2269 if (input.header == nullptr) {
2270 continue;
2271 }
2272 // For the consume=false (Process) path, ownedInputs holds the actual
2273 // message vectors and the span points into them.
2274 ownedInputs[ii].clear();
2275 currentSetOfInputs[ii] = {};
2276 }
2277 };
2278
2279 // Function to cleanup record. For the moment we
2280 // simply use it to keep track of input messages
2281 // which are not needed, to display them in the GUI.
2282 auto cleanupRecord = [](InputRecord& record) {
2283 if (O2_LOG_ENABLED(parts) == false) {
2284 return;
2285 }
2286 for (size_t pi = 0, pe = record.size(); pi < pe; ++pi) {
2287 DataRef input = record.getByPos(pi);
2288 if (input.header == nullptr) {
2289 continue;
2290 }
2291 auto sih = o2::header::get<SourceInfoHeader*>(input.header);
2292 if (sih) {
2293 continue;
2294 }
2295
2296 auto dh = o2::header::get<DataHeader*>(input.header);
2297 if (!dh) {
2298 continue;
2299 }
2300 // We use the address of the first header of a split payload
2301 // to identify the interval.
2302 O2_SIGNPOST_ID_FROM_POINTER(pid, parts, dh);
2303 O2_SIGNPOST_END(parts, pid, "parts", "Cleaning up parts associated to %p", dh);
2304
2305 // No split parts, we simply skip the payload
2306 if (dh->splitPayloadParts > 0 && dh->splitPayloadParts == dh->splitPayloadIndex) {
2307 // this is indicating a sequence of payloads following the header
2308 // FIXME: we will probably also set the DataHeader version
2309 pi += dh->splitPayloadParts - 1;
2310 } else {
2311 size_t pi = pi + (dh->splitPayloadParts > 0 ? dh->splitPayloadParts : 1) * 2;
2312 }
2313 }
2314 };
2315
2316 ref.get<DataRelayer>().getReadyToProcess(completed);
2317 if (completed.empty() == true) {
2318 LOGP(debug, "No computations available for dispatching.");
2319 return false;
2320 }
2321
2322 int pipelineLength = DefaultsHelpers::pipelineLength(*ref.get<RawDeviceService>().device()->fConfig);
2323
2324 auto postUpdateStats = [ref, pipelineLength](DataRelayer::RecordAction const& action, InputRecord const& record, uint64_t tStart, uint64_t tStartMilli) {
2325 auto& stats = ref.get<DataProcessingStats>();
2326 auto& states = ref.get<DataProcessingStates>();
2327 std::atomic_thread_fence(std::memory_order_release);
2328 char relayerSlotState[1024];
2329 int written = snprintf(relayerSlotState, 1024, "%d ", pipelineLength);
2330 char* buffer = relayerSlotState + written;
2331 for (size_t ai = 0; ai != record.size(); ai++) {
2332 buffer[ai] = record.isValid(ai) ? '3' : '0';
2333 }
2334 buffer[record.size()] = 0;
2335 states.updateState({.id = short((int)ProcessingStateId::DATA_RELAYER_BASE + action.slot.index),
2336 .size = (int)(record.size() + buffer - relayerSlotState),
2337 .data = relayerSlotState});
2338 uint64_t tEnd = uv_hrtime();
2339 // tEnd and tStart are in nanoseconds according to https://docs.libuv.org/en/v1.x/misc.html#c.uv_hrtime
2340 int64_t wallTimeMs = (tEnd - tStart) / 1000000;
2342 // Sum up the total wall time, in milliseconds.
2344 // The time interval is in seconds while tEnd - tStart is in nanoseconds, so we divide by 1000000 to get the fraction in ms/s.
2346 stats.updateStats({(int)ProcessingStatsId::LAST_PROCESSED_SIZE, DataProcessingStats::Op::Set, calculateTotalInputRecordSize(record)});
2347 stats.updateStats({(int)ProcessingStatsId::TOTAL_PROCESSED_SIZE, DataProcessingStats::Op::Add, calculateTotalInputRecordSize(record)});
2348 auto latency = calculateInputRecordLatency(record, tStartMilli);
2349 stats.updateStats({(int)ProcessingStatsId::LAST_MIN_LATENCY, DataProcessingStats::Op::Set, (int)latency.minLatency});
2350 stats.updateStats({(int)ProcessingStatsId::LAST_MAX_LATENCY, DataProcessingStats::Op::Set, (int)latency.maxLatency});
2351 static int count = 0;
2353 count++;
2354 };
2355
2356 auto preUpdateStats = [ref, pipelineLength](DataRelayer::RecordAction const& action, InputRecord const& record, uint64_t) {
2357 auto& states = ref.get<DataProcessingStates>();
2358 std::atomic_thread_fence(std::memory_order_release);
2359 char relayerSlotState[1024];
2360 snprintf(relayerSlotState, 1024, "%d ", pipelineLength);
2361 char* buffer = strchr(relayerSlotState, ' ') + 1;
2362 for (size_t ai = 0; ai != record.size(); ai++) {
2363 buffer[ai] = record.isValid(ai) ? '2' : '0';
2364 }
2365 buffer[record.size()] = 0;
2366 states.updateState({.id = short((int)ProcessingStateId::DATA_RELAYER_BASE + action.slot.index), .size = (int)(record.size() + buffer - relayerSlotState), .data = relayerSlotState});
2367 };
2368
2369 // This is the main dispatching loop
2370 auto& state = ref.get<DeviceState>();
2371 auto& spec = ref.get<DeviceSpec const>();
2372
2373 auto& dpContext = ref.get<DataProcessorContext>();
2374 auto& streamContext = ref.get<StreamContext>();
2375 O2_SIGNPOST_ID_GENERATE(sid, device);
2376 O2_SIGNPOST_START(device, sid, "device", "Start processing ready actions");
2377
2378 auto& stats = ref.get<DataProcessingStats>();
2379 auto& relayer = ref.get<DataRelayer>();
2380 using namespace o2::framework;
2381 stats.updateStats({(int)ProcessingStatsId::PENDING_INPUTS, DataProcessingStats::Op::Set, static_cast<int64_t>(relayer.getParallelTimeslices() - completed.size())});
2382 stats.updateStats({(int)ProcessingStatsId::INCOMPLETE_INPUTS, DataProcessingStats::Op::Set, completed.empty() ? 1 : 0});
2383 switch (spec.completionPolicy.order) {
2385 std::sort(completed.begin(), completed.end(), [](auto const& a, auto const& b) { return a.timeslice.value < b.timeslice.value; });
2386 break;
2388 std::sort(completed.begin(), completed.end(), [](auto const& a, auto const& b) { return a.slot.index < b.slot.index; });
2389 break;
2391 default:
2392 break;
2393 }
2394
2395 for (auto action : completed) {
2396 O2_SIGNPOST_ID_GENERATE(aid, device);
2397 O2_SIGNPOST_START(device, aid, "device", "Processing action on slot %lu for action %{public}s", action.slot.index, fmt::format("{}", action.op).c_str());
2398 if (action.op == CompletionPolicy::CompletionOp::Wait) {
2399 O2_SIGNPOST_END(device, aid, "device", "Waiting for more data.");
2400 continue;
2401 }
2402
2403 bool shouldConsume = action.op == CompletionPolicy::CompletionOp::Consume ||
2405 prepareAllocatorForCurrentTimeSlice(TimesliceSlot{action.slot});
2406 if (action.op != CompletionPolicy::CompletionOp::Discard &&
2409 updateRunInformation(TimesliceSlot{action.slot});
2410 }
2411 InputSpan span = getInputSpan(action.slot, shouldConsume);
2412 auto& spec = ref.get<DeviceSpec const>();
2413 InputRecord record{spec.inputs,
2414 span,
2415 *context.registry};
2416 ProcessingContext processContext{record, ref, ref.get<DataAllocator>()};
2417 {
2418 // Notice this should be thread safe and reentrant
2419 // as it is called from many threads.
2420 streamContext.preProcessingCallbacks(processContext);
2421 dpContext.preProcessingCallbacks(processContext);
2422 }
2423 if (action.op == CompletionPolicy::CompletionOp::Discard) {
2424 context.postDispatchingCallbacks(processContext);
2425 if (spec.forwards.empty() == false) {
2426 auto& timesliceIndex = ref.get<TimesliceIndex>();
2427 forwardInputs(ref, action.slot, currentSetOfInputs, timesliceIndex.getOldestPossibleOutput(), false);
2428 O2_SIGNPOST_END(device, aid, "device", "Forwarding inputs consume: %d.", false);
2429 continue;
2430 }
2431 }
2432 // If there is no optional inputs we canForwardEarly
2433 // the messages to that parallel processing can happen.
2434 // In this case we pass true to indicate that we want to
2435 // copy the messages to the subsequent data processor.
2436 bool hasForwards = spec.forwards.empty() == false;
2437 bool consumeSomething = action.op == CompletionPolicy::CompletionOp::Consume || action.op == CompletionPolicy::CompletionOp::ConsumeExisting;
2438
2439 if (context.forwardPolicy == ForwardPolicy::AtCompletionPolicySatisified && hasForwards && consumeSomething) {
2440 O2_SIGNPOST_EVENT_EMIT(device, aid, "device", "Early forwarding: %{public}s.", fmt::format("{}", action.op).c_str());
2441 auto& timesliceIndex = ref.get<TimesliceIndex>();
2442 forwardInputs(ref, action.slot, currentSetOfInputs, timesliceIndex.getOldestPossibleOutput(), true, action.op == CompletionPolicy::CompletionOp::Consume);
2443 } else if (context.forwardPolicy == ForwardPolicy::AtInjection && hasForwards && consumeSomething) {
2444 // We used to do fowarding here, however we now do it much earlier.
2445 // We still need to clean the inputs which were already consumed
2446 // via ConsumeExisting and which still have an header to hold the slot.
2447 // FIXME: do we? This should really happen when we do the forwarding on
2448 // insertion, because otherwise we lose the relevant information on how to
2449 // navigate the set of headers. We could actually rely on the messageset index,
2450 // is that the right thing to do though?
2451 O2_SIGNPOST_EVENT_EMIT(device, aid, "device", "cleaning early forwarding: %{public}s.", fmt::format("{}", action.op).c_str());
2452 auto& timesliceIndex = ref.get<TimesliceIndex>();
2453 cleanEarlyForward(ref, action.slot, currentSetOfInputs, timesliceIndex.getOldestPossibleOutput(), true, action.op == CompletionPolicy::CompletionOp::Consume);
2454 }
2455
2456 markInputsAsDone(action.slot);
2457
2458 uint64_t tStart = uv_hrtime();
2459 uint64_t tStartMilli = TimingHelpers::getRealtimeSinceEpochStandalone();
2460 preUpdateStats(action, record, tStart);
2461
2462 static bool noCatch = getenv("O2_NO_CATCHALL_EXCEPTIONS") && strcmp(getenv("O2_NO_CATCHALL_EXCEPTIONS"), "0");
2463
2464 auto runNoCatch = [&context, ref, &processContext](DataRelayer::RecordAction& action) mutable {
2465 auto& state = ref.get<DeviceState>();
2466 auto& spec = ref.get<DeviceSpec const>();
2467 auto& streamContext = ref.get<StreamContext>();
2468 auto& dpContext = ref.get<DataProcessorContext>();
2469 auto shouldProcess = [](DataRelayer::RecordAction& action) -> bool {
2470 switch (action.op) {
2475 return true;
2476 break;
2477 default:
2478 return false;
2479 }
2480 };
2481 if (state.quitRequested == false) {
2482 {
2483 // Callbacks from services
2484 dpContext.preProcessingCallbacks(processContext);
2485 streamContext.preProcessingCallbacks(processContext);
2486 dpContext.preProcessingCallbacks(processContext);
2487 // Callbacks from users
2488 ref.get<CallbackService>().call<CallbackService::Id::PreProcessing>(o2::framework::ServiceRegistryRef{ref}, (int)action.op);
2489 }
2490 O2_SIGNPOST_ID_FROM_POINTER(pcid, device, &processContext);
2491 if (context.statefulProcess && shouldProcess(action)) {
2492 // This way, usercode can use the the same processing context to identify
2493 // its signposts and we can map user code to device iterations.
2494 O2_SIGNPOST_START(device, pcid, "device", "Stateful process");
2495 (context.statefulProcess)(processContext);
2496 O2_SIGNPOST_END(device, pcid, "device", "Stateful process");
2497 } else if (context.statelessProcess && shouldProcess(action)) {
2498 O2_SIGNPOST_START(device, pcid, "device", "Stateful process");
2499 (context.statelessProcess)(processContext);
2500 O2_SIGNPOST_END(device, pcid, "device", "Stateful process");
2501 } else if (context.statelessProcess || context.statefulProcess) {
2502 O2_SIGNPOST_EVENT_EMIT(device, pcid, "device", "Skipping processing because we are discarding.");
2503 } else {
2504 O2_SIGNPOST_EVENT_EMIT(device, pcid, "device", "No processing callback provided. Switching to %{public}s.", "Idle");
2506 }
2507 if (shouldProcess(action)) {
2508 auto& timingInfo = ref.get<TimingInfo>();
2509 if (timingInfo.globalRunNumberChanged) {
2510 context.lastRunNumberProcessed = timingInfo.runNumber;
2511 }
2512 }
2513
2514 // Notify the sink we just consumed some timeframe data
2515 if (context.isSink && action.op == CompletionPolicy::CompletionOp::Consume) {
2516 O2_SIGNPOST_EVENT_EMIT(device, pcid, "device", "Sending dpl-summary");
2517 auto& allocator = ref.get<DataAllocator>();
2518 allocator.make<int>(OutputRef{"dpl-summary", runtime_hash(spec.name.c_str())}, 1);
2519 }
2520
2521 // Extra callback which allows a service to add extra outputs.
2522 // This is needed e.g. to ensure that injected CCDB outputs are added
2523 // before an end of stream.
2524 {
2525 ref.get<CallbackService>().call<CallbackService::Id::FinaliseOutputs>(o2::framework::ServiceRegistryRef{ref}, (int)action.op);
2526 dpContext.finaliseOutputsCallbacks(processContext);
2527 streamContext.finaliseOutputsCallbacks(processContext);
2528 }
2529
2530 {
2531 ref.get<CallbackService>().call<CallbackService::Id::PostProcessing>(o2::framework::ServiceRegistryRef{ref}, (int)action.op);
2532 dpContext.postProcessingCallbacks(processContext);
2533 streamContext.postProcessingCallbacks(processContext);
2534 }
2535 }
2536 };
2537
2538 if ((state.tracingFlags & DeviceState::LoopReason::TRACE_USERCODE) != 0) {
2539 state.severityStack.push_back((int)fair::Logger::GetConsoleSeverity());
2540 fair::Logger::SetConsoleSeverity(fair::Severity::trace);
2541 }
2542 if (noCatch) {
2543 try {
2544 runNoCatch(action);
2545 } catch (o2::framework::RuntimeErrorRef e) {
2546 (context.errorHandling)(e, record);
2547 }
2548 } else {
2549 try {
2550 runNoCatch(action);
2551 } catch (std::exception& ex) {
2555 auto e = runtime_error(ex.what());
2556 (context.errorHandling)(e, record);
2557 } catch (o2::framework::RuntimeErrorRef e) {
2558 (context.errorHandling)(e, record);
2559 }
2560 }
2561 if (state.severityStack.empty() == false) {
2562 fair::Logger::SetConsoleSeverity((fair::Severity)state.severityStack.back());
2563 state.severityStack.pop_back();
2564 }
2565
2566 postUpdateStats(action, record, tStart, tStartMilli);
2567 // We forward inputs only when we consume them. If we simply Process them,
2568 // we keep them for next message arriving.
2569 if (action.op == CompletionPolicy::CompletionOp::Consume) {
2570 cleanupRecord(record);
2571 context.postDispatchingCallbacks(processContext);
2572 ref.get<CallbackService>().call<CallbackService::Id::DataConsumed>(o2::framework::ServiceRegistryRef{ref});
2573 }
2574 if ((context.forwardPolicy == ForwardPolicy::AfterProcessing) && hasForwards && consumeSomething) {
2575 O2_SIGNPOST_EVENT_EMIT(device, aid, "device", "Late forwarding");
2576 auto& timesliceIndex = ref.get<TimesliceIndex>();
2577 forwardInputs(ref, action.slot, currentSetOfInputs, timesliceIndex.getOldestPossibleOutput(), false, action.op == CompletionPolicy::CompletionOp::Consume);
2578 }
2579 context.postForwardingCallbacks(processContext);
2580 if (action.op == CompletionPolicy::CompletionOp::Process) {
2581 cleanTimers(action.slot, record);
2582 }
2583 O2_SIGNPOST_END(device, aid, "device", "Done processing action on slot %lu for action %{public}s", action.slot.index, fmt::format("{}", action.op).c_str());
2584 }
2585 O2_SIGNPOST_END(device, sid, "device", "Start processing ready actions");
2586
2587 // We now broadcast the end of stream if it was requested
2588 if (state.streaming == StreamingState::EndOfStreaming) {
2589 LOGP(detail, "Broadcasting end of stream");
2590 for (auto& channel : spec.outputChannels) {
2592 }
2594 }
2595
2596 return true;
2597}
2598
2600{
2601 LOG(error) << msg;
2602 ServiceRegistryRef ref{mServiceRegistry};
2603 auto& stats = ref.get<DataProcessingStats>();
2605}
2606
2607std::unique_ptr<ConfigParamStore> DeviceConfigurationHelpers::getConfiguration(ServiceRegistryRef registry, const char* name, std::vector<ConfigParamSpec> const& options)
2608{
2609
2610 if (registry.active<ConfigurationInterface>()) {
2611 auto& cfg = registry.get<ConfigurationInterface>();
2612 try {
2613 cfg.getRecursive(name);
2614 std::vector<std::unique_ptr<ParamRetriever>> retrievers;
2615 retrievers.emplace_back(std::make_unique<ConfigurationOptionsRetriever>(&cfg, name));
2616 auto configStore = std::make_unique<ConfigParamStore>(options, std::move(retrievers));
2617 configStore->preload();
2618 configStore->activate();
2619 return configStore;
2620 } catch (...) {
2621 // No overrides...
2622 }
2623 }
2624 return {nullptr};
2625}
2626
2627} // namespace o2::framework
benchmark::State & state
struct uv_timer_s uv_timer_t
struct uv_signal_s uv_signal_t
struct uv_async_s uv_async_t
struct uv_poll_s uv_poll_t
struct uv_loop_s uv_loop_t
o2::monitoring::Metric Metric
uint64_t maxLatency
o2::configuration::ConfigurationInterface ConfigurationInterface
constexpr int DEFAULT_MAX_CHANNEL_AHEAD
uint64_t minLatency
std::ostringstream debug
int32_t i
std::enable_if_t< std::is_signed< T >::value, bool > hasData(const CalArray< T > &cal)
Definition Painter.cxx:600
uint16_t pid
Definition RawData.h:2
uint32_t res
Definition RawData.h:0
#define O2_SIGNPOST_EVENT_EMIT_ERROR(log, id, name, format,...)
Definition Signpost.h:554
#define O2_DECLARE_DYNAMIC_LOG(name)
Definition Signpost.h:490
#define O2_SIGNPOST_ID_FROM_POINTER(name, log, pointer)
Definition Signpost.h:506
#define O2_SIGNPOST_END(log, id, name, format,...)
Definition Signpost.h:609
#define O2_LOG_ENABLED(log)
Definition Signpost.h:111
#define O2_SIGNPOST_ID_GENERATE(name, log)
Definition Signpost.h:507
#define O2_SIGNPOST_EVENT_EMIT_WARN(log, id, name, format,...)
Definition Signpost.h:564
#define O2_SIGNPOST_EVENT_EMIT(log, id, name, format,...)
Definition Signpost.h:523
#define O2_SIGNPOST_START(log, id, name, format,...)
Definition Signpost.h:603
constexpr uint32_t runtime_hash(char const *str)
o2::monitoring::Monitoring Monitoring
StringRef key
@ DeviceStateChanged
Invoked the device undergoes a state change.
decltype(auto) make(const Output &spec, Args... args)
static void doRun(ServiceRegistryRef)
void fillContext(DataProcessorContext &context, DeviceContext &deviceContext)
DataProcessingDevice(RunningDeviceRef ref, ServiceRegistry &)
static void doPrepare(ServiceRegistryRef)
static bool tryDispatchComputation(ServiceRegistryRef ref, std::vector< DataRelayer::RecordAction > &completed)
static void handleData(ServiceRegistryRef, InputChannelInfo &)
uint32_t getFirstTFOrbitForSlot(TimesliceSlot slot)
Get the firstTForbit associate to a given slot.
void updateCacheStatus(TimesliceSlot slot, CacheEntryStatus oldStatus, CacheEntryStatus newStatus)
uint32_t getRunNumberForSlot(TimesliceSlot slot)
Get the runNumber associated to a given slot.
void prunePending(OnDropCallback)
Prune all the pending entries in the cache.
std::vector< std::vector< fair::mq::MessagePtr > > consumeAllInputsForTimeslice(TimesliceSlot id)
uint64_t getCreationTimeForSlot(TimesliceSlot slot)
Get the creation time associated to a given slot.
ActivityStats processDanglingInputs(std::vector< ExpirationHandler > const &, ServiceRegistryRef context, bool createNew)
uint32_t getFirstTFCounterForSlot(TimesliceSlot slot)
Get the firstTFCounter associate to a given slot.
A service API to communicate with the driver.
The input API of the Data Processing Layer This class holds the inputs which are valid for processing...
size_t size() const
Number of elements in the InputSpan.
Definition InputSpan.h:102
virtual fair::mq::Device * device()=0
bool active() const
Check if service of type T is currently active.
OldestOutputInfo getOldestPossibleOutput() const
GLint GLsizei count
Definition glcorearb.h:399
GLuint64EXT * result
Definition glcorearb.h:5662
GLuint buffer
Definition glcorearb.h:655
GLuint entry
Definition glcorearb.h:5735
GLuint index
Definition glcorearb.h:781
GLuint const GLchar * name
Definition glcorearb.h:781
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLint GLint GLsizei GLint GLenum GLenum type
Definition glcorearb.h:275
GLboolean * data
Definition glcorearb.h:298
GLuint GLsizei GLsizei * length
Definition glcorearb.h:790
GLuint GLsizei const GLchar * label
Definition glcorearb.h:2519
GLsizei GLenum const void * indices
Definition glcorearb.h:400
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLuint GLsizei const GLchar * message
Definition glcorearb.h:2517
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLuint GLuint stream
Definition glcorearb.h:1806
GLint ref
Definition glcorearb.h:291
GLuint * states
Definition glcorearb.h:4932
Defining ITS Vertex explicitly as messageable.
Definition Cartesian.h:288
RuntimeErrorRef runtime_error(const char *)
ServiceKind
The kind of service we are asking for.
void on_idle_timer(uv_timer_t *handle)
@ DPL
The channel is a normal input channel.
void run_completion(uv_work_t *handle, int status)
void on_socket_polled(uv_poll_t *poller, int status, int events)
void run_callback(uv_work_t *handle)
auto forwardOnInsertion(ServiceRegistryRef &ref, std::span< fair::mq::MessagePtr > &messages) -> void
volatile int region_read_global_dummy_variable
void handleRegionCallbacks(ServiceRegistryRef registry, std::vector< fair::mq::RegionInfo > &infos)
Invoke the callbacks for the mPendingRegionInfos.
void on_out_of_band_polled(uv_poll_t *poller, int status, int events)
DeviceSpec const & getRunningDevice(RunningDeviceRef const &running, ServiceRegistryRef const &services)
@ EndOfStreaming
End of streaming requested, but not notified.
@ Streaming
Data is being processed.
@ Idle
End of streaming notified.
void on_communication_requested(uv_async_t *s)
@ Expired
A transition needs to be fullfilled ASAP.
@ NoTransition
No pending transitions.
@ Requested
A transition was notified to be requested.
RuntimeError & error_from_ref(RuntimeErrorRef)
void on_awake_main_thread(uv_async_t *handle)
@ Completed
The channel was signaled it will not receive any data.
@ Running
The channel is actively receiving data.
void on_signal_callback(uv_signal_t *handle, int signum)
@ Me
Only quit this data processor.
constexpr const char * channelName(int channel)
Definition Constants.h:318
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...
static void run(AsyncQueue &queue, TimesliceId oldestPossibleTimeslice)
static void post(AsyncQueue &queue, AsyncTask const &task)
An actuatual task to be executed.
Definition AsyncQueue.h:32
static void demangled_backtrace_symbols(void **backtrace, unsigned int total, int fd)
static constexpr int INVALID
CompletionOp
Action to take with the InputRecord:
@ Retry
Like Wait but mark the cacheline as dirty.
int64_t timeslices
How many timeslices it can process without giving back control.
int64_t sharedMemory
How much shared memory it can allocate.
Statistics on the offers consumed, expired.
static bool hasOnlyGenerated(DeviceSpec const &spec)
check if spec is a source devide
static std::vector< fair::mq::Parts > routeForwardedMessageSet(FairMQDeviceProxy &proxy, std::vector< std::span< fair::mq::MessagePtr > > &currentSetOfInputs, bool copy, bool consume)
Helper to route messages for forwarding.
static TransitionHandlingState updateStateTransition(ServiceRegistryRef const &ref, ProcessingPolicies const &policies)
starts the EoS timers and returns the new TransitionHandlingState in case as new state is requested
static void switchState(ServiceRegistryRef const &ref, StreamingState newState)
change the device StreamingState to newState
static void sendEndOfStream(ServiceRegistryRef const &ref, OutputChannelSpec const &channel)
static bool sendOldestPossibleTimeframe(ServiceRegistryRef const &ref, ForwardChannelInfo const &info, ForwardChannelState &state, size_t timeslice)
static void cleanForwardedMessages(std::span< fair::mq::MessagePtr > &currentSetOfInputs, bool consume)
static void routeForwardedMessages(FairMQDeviceProxy &proxy, std::span< fair::mq::MessagePtr > &currentSetOfInputs, std::vector< fair::mq::Parts > &forwardedParts, bool copy, bool consume)
Helper to route messages for forwarding.
Helper struct to hold statistics about the data processing happening.
@ CumulativeRate
Set the value to the specified value if it is positive.
@ Add
Update the rate of the metric given the amount since the last time.
std::function< void(o2::framework::RuntimeErrorRef e, InputRecord &record)> errorHandling
ForwardPolicy forwardPolicy
Wether or not the associated DataProcessor can forward things early.
AlgorithmSpec::InitErrorCallback initError
void preLoopCallbacks(ServiceRegistryRef)
Invoke callbacks before we enter the event loop.
void postStopCallbacks(ServiceRegistryRef)
Invoke callbacks on stop.
void preEOSCallbacks(EndOfStreamContext &)
Invoke callbacks to be executed before every EOS user callback invokation.
void preProcessingCallbacks(ProcessingContext &)
Invoke callbacks to be executed before every process method invokation.
void postEOSCallbacks(EndOfStreamContext &)
Invoke callbacks to be executed after every EOS user callback invokation.
void preStartCallbacks(ServiceRegistryRef)
Invoke callbacks to be executed in PreRun(), before the User Start callbacks.
AlgorithmSpec::ProcessCallback statefulProcess
const char * header
Definition DataRef.h:28
const InputSpec * spec
Definition DataRef.h:27
static std::vector< size_t > createDistinctRouteIndex(std::vector< InputRoute > const &)
@ Invalid
Ownership of the data has been taken.
@ Backpressured
The incoming data was not valid and has been dropped.
@ Dropped
The incoming data was not relayed, because we are backpressured.
static bool partialMatch(InputSpec const &spec, o2::header::DataOrigin const &origin)
static std::string describe(InputSpec const &spec)
static bool match(InputSpec const &spec, ConcreteDataMatcher const &target)
TimesliceIndex::OldestOutputInfo oldestTimeslice
static unsigned int pipelineLength(unsigned int minLength)
get max number of timeslices in the queue
static std::unique_ptr< ConfigParamStore > getConfiguration(ServiceRegistryRef registry, const char *name, std::vector< ConfigParamSpec > const &options)
ProcessingPolicies & processingPolicies
Running state information of a given device.
Definition DeviceState.h:34
std::atomic< int64_t > cleanupCount
Definition DeviceState.h:82
Forward channel information.
Definition ChannelInfo.h:88
fair::mq::Channel * channel
Definition ChannelInfo.h:51
enum Lifetime lifetime
Definition InputSpec.h:73
enum EarlyForwardPolicy earlyForward
Information about the running workflow.
static constexpr ServiceKind kind
static Salt streamSalt(short streamId, short dataProcessorId)
void lateBindStreamServices(DeviceState &state, fair::mq::ProgOptions &options, ServiceRegistry::Salt salt)
static Salt globalStreamSalt(short streamId)
void * get(ServiceTypeHash typeHash, Salt salt, ServiceKind kind, char const *name=nullptr) const
void finaliseOutputsCallbacks(ProcessingContext &)
Invoke callbacks to be executed after every process method invokation.
void preProcessingCallbacks(ProcessingContext &pcx)
Invoke callbacks to be executed before every process method invokation.
void postProcessingCallbacks(ProcessingContext &pcx)
Invoke callbacks to be executed after every process method invokation.
static int64_t getRealtimeSinceEpochStandalone()
bool keepAtEndOfStream
Wether this kind of data should be flushed during end of stream.
Definition TimingInfo.h:44
static bool timesliceIsTimer(size_t timeslice)
Definition TimingInfo.h:46
static TimesliceId getTimeslice(data_matcher::VariableContext const &variables)
void backpressure(InputChannelInfo const &)
locked_execution(ServiceRegistryRef &ref_)
the main header struct
Definition DataHeader.h:620
constexpr size_t min
constexpr size_t max
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
vec clear()
const std::string str
uint64_t const void const *restrict const msg
Definition x9.h:153