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
510{
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) {
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) {
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) {
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) {
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
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;
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;
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 // minWarnDelay: stalls shorter than this are never worth warning about.
1381 // healthToForget: uninterrupted health needed to halve the threshold, as a
1382 // multiple of the threshold itself.
1383 // minHealthToForget: no decay at all below this much health, so a storm of
1384 // short hiccups separated by short recoveries never re-arms the warning.
1385 enum : size_t { minWarnDelay = 50,
1386 healthToForget = 3,
1387 minHealthToForget = 60000 };
1388 std::atomic<size_t> lastScheduled = 0;
1389 std::atomic<size_t> numberOfUnscheduledSinceLastScheduled = 0;
1390 std::atomic<size_t> numberOfUnscheduled = 0;
1391 std::atomic<size_t> numberOfScheduled = 0;
1392 // Longest stall already warned about, doubled, so a stall no worse than an
1393 // earlier one stays silent.
1394 std::atomic<size_t> warnAboveDelay = minWarnDelay;
1395 // Start of the current uninterrupted healthy stretch, 0 until we first
1396 // recover from a stall.
1397 std::atomic<size_t> healthySince = 0;
1398
1399 // Halve the threshold for every healthToForget x threshold of health, down
1400 // to minWarnDelay, so a later and smaller stall is reported again instead
1401 // of being hidden by an older and worse one. The healthy stretch is spent
1402 // in one go at the start of a stall: letting it apply again mid-stall would
1403 // undo each backoff step and warn on every poll.
1404 void decayWarnThreshold(size_t now)
1405 {
1406 size_t const since = healthySince.exchange(0);
1407 if (since == 0) {
1408 return;
1409 }
1410 size_t healthy = now - since;
1411 if (healthy < minHealthToForget) {
1412 return;
1413 }
1414 size_t threshold = warnAboveDelay;
1415 while (threshold > minWarnDelay && healthy >= healthToForget * threshold) {
1416 healthy -= healthToForget * threshold;
1417 threshold = std::max<size_t>(threshold / 2, minWarnDelay);
1418 }
1419 warnAboveDelay = threshold;
1420 }
1421 };
1422 static SchedulingStats schedulingStats;
1423 O2_SIGNPOST_ID_GENERATE(sid, scheduling);
1424 if (enough) {
1425 stream.id = streamRef;
1426 stream.running = true;
1427 stream.registry = &mServiceRegistry;
1428 schedulingStats.lastScheduled = uv_now(state.loop);
1429 schedulingStats.numberOfScheduled++;
1430 if (schedulingStats.numberOfUnscheduledSinceLastScheduled != 0) {
1431 schedulingStats.healthySince = schedulingStats.lastScheduled.load();
1432 }
1433 schedulingStats.numberOfUnscheduledSinceLastScheduled = 0;
1434 O2_SIGNPOST_EVENT_EMIT(scheduling, sid, "Run", "Enough resources to schedule computation on stream %d", streamRef.index);
1435 if (dplEnableMultithreding) [[unlikely]] {
1436 stream.task = &handle;
1437 uv_queue_work(state.loop, stream.task, run_callback, run_completion);
1438 } else {
1441 }
1442 } else {
1443 auto const lastSched = schedulingStats.lastScheduled.load();
1444 auto const schedInfo = lastSched ? fmt::format(", last scheduled {} ms ago", uv_now(state.loop) - lastSched) : std::string(", never successfully scheduled");
1445 auto const buildMissingInfo = [&]() {
1446 auto const& required = spec.resourcePolicy.minRequired;
1447 std::string missingInfo;
1448 if (required.sharedMemory > 0 && accumulated.sharedMemory < required.sharedMemory) {
1449 missingInfo += fmt::format(" shared memory (have {} MB, need {} MB)", accumulated.sharedMemory / 1000000, required.sharedMemory / 1000000);
1450 }
1451 if (required.timeslices > 0 && accumulated.timeslices < required.timeslices) {
1452 missingInfo += fmt::format(" timeslices (have {}, need {})", accumulated.timeslices, required.timeslices);
1453 }
1454 if (required.cpu > 0 && accumulated.cpu < required.cpu) {
1455 missingInfo += fmt::format(" CPU cores (have {}, need {})", accumulated.cpu, required.cpu);
1456 }
1457 if (required.memory > 0 && accumulated.memory < required.memory) {
1458 missingInfo += fmt::format(" memory (have {} MB, need {} MB)", accumulated.memory / 1000000, required.memory / 1000000);
1459 }
1460 return missingInfo.empty() ? std::string(" (policy: ") + spec.resourcePolicy.name + ")" : " -" + missingInfo;
1461 };
1462 size_t const now = uv_now(state.loop);
1463 schedulingStats.decayWarnThreshold(now);
1464 size_t const timeSinceLastScheduled = lastSched ? now - lastSched : 0;
1465 // A single skip is the normal cadence of a rate limited reader, so a stall
1466 // has to be seen at least twice before it is worth a warning.
1467 bool const warn = schedulingStats.numberOfUnscheduledSinceLastScheduled != 0 &&
1468 timeSinceLastScheduled >= schedulingStats.warnAboveDelay;
1469 auto const missingStr = buildMissingInfo();
1470 if (warn) {
1471 schedulingStats.warnAboveDelay = timeSinceLastScheduled * 2;
1472 O2_SIGNPOST_EVENT_EMIT_WARN(scheduling, sid, "Run",
1473 "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.",
1474 streamRef.index,
1475 schedulingStats.numberOfUnscheduledSinceLastScheduled.load(),
1476 schedInfo.c_str(),
1477 missingStr.c_str());
1478 } else {
1479 O2_SIGNPOST_EVENT_EMIT(scheduling, sid, "Run",
1480 "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.",
1481 streamRef.index,
1482 schedulingStats.numberOfUnscheduledSinceLastScheduled.load(),
1483 schedInfo.c_str(),
1484 missingStr.c_str());
1485 }
1486 schedulingStats.numberOfUnscheduled++;
1487 schedulingStats.numberOfUnscheduledSinceLastScheduled++;
1488 auto ref = ServiceRegistryRef{mServiceRegistry};
1489 ref.get<ComputingQuotaEvaluator>().handleExpired(reportExpiredOffer);
1490 }
1491 }
1492 }
1493
1494 O2_SIGNPOST_END(device, lid, "run_loop", "Run loop completed. Transition handling state %d.", (int)state.transitionHandling);
1495 auto& spec = ref.get<DeviceSpec const>();
1497 for (size_t ci = 0; ci < spec.inputChannels.size(); ++ci) {
1498 auto& info = state.inputChannelInfos[ci];
1499 info.parts.fParts.clear();
1500 }
1501 state.transitionHandling = TransitionHandlingState::NoTransition;
1502}
1503
1507{
1508 auto& context = ref.get<DataProcessorContext>();
1509 O2_SIGNPOST_ID_FROM_POINTER(dpid, device, &context);
1510 O2_SIGNPOST_START(device, dpid, "do_prepare", "Starting DataProcessorContext::doPrepare.");
1511
1512 {
1513 ref.get<CallbackService>().call<CallbackService::Id::ClockTick>();
1514 }
1515 // Whether or not we had something to do.
1516
1517 // Initialise the value for context.allDone. It will possibly be updated
1518 // below if any of the channels is not done.
1519 //
1520 // Notice that fake input channels (InputChannelState::Pull) cannot possibly
1521 // expect to receive an EndOfStream signal. Thus we do not wait for these
1522 // to be completed. In the case of data source devices, as they do not have
1523 // real data input channels, they have to signal EndOfStream themselves.
1524 auto& state = ref.get<DeviceState>();
1525 auto& spec = ref.get<DeviceSpec const>();
1526 O2_SIGNPOST_ID_FROM_POINTER(cid, device, state.inputChannelInfos.data());
1527 O2_SIGNPOST_START(device, cid, "do_prepare", "Reported channel states.");
1528 context.allDone = std::any_of(state.inputChannelInfos.begin(), state.inputChannelInfos.end(), [cid](const auto& info) {
1529 if (info.channel) {
1530 O2_SIGNPOST_EVENT_EMIT(device, cid, "do_prepare", "Input channel %{public}s%{public}s has %zu parts left and is in state %d.",
1531 info.channel->GetName().c_str(), (info.id.value == ChannelIndex::INVALID ? " (non DPL)" : ""), info.parts.fParts.size(), (int)info.state);
1532 } else {
1533 O2_SIGNPOST_EVENT_EMIT(device, cid, "do_prepare", "External channel %d is in state %d.", info.id.value, (int)info.state);
1534 }
1535 return (info.parts.fParts.empty() == true && info.state != InputChannelState::Pull);
1536 });
1537 O2_SIGNPOST_END(device, cid, "do_prepare", "End report.");
1538 O2_SIGNPOST_EVENT_EMIT(device, dpid, "do_prepare", "Processing %zu input channels.", spec.inputChannels.size());
1541 static std::vector<int> pollOrder;
1542 pollOrder.resize(state.inputChannelInfos.size());
1543 std::iota(pollOrder.begin(), pollOrder.end(), 0);
1544 std::sort(pollOrder.begin(), pollOrder.end(), [&infos = state.inputChannelInfos](int a, int b) {
1545 return infos[a].oldestForChannel.value < infos[b].oldestForChannel.value;
1546 });
1547
1548 // Nothing to poll...
1549 if (pollOrder.empty()) {
1550 O2_SIGNPOST_END(device, dpid, "do_prepare", "Nothing to poll. Waiting for next iteration.");
1551 return;
1552 }
1553 auto currentOldest = state.inputChannelInfos[pollOrder.front()].oldestForChannel;
1554 auto currentNewest = state.inputChannelInfos[pollOrder.back()].oldestForChannel;
1555 auto delta = currentNewest.value - currentOldest.value;
1556 O2_SIGNPOST_EVENT_EMIT(device, dpid, "do_prepare", "Oldest possible timeframe range %" PRIu64 " => %" PRIu64 " delta %" PRIu64,
1557 (int64_t)currentOldest.value, (int64_t)currentNewest.value, (int64_t)delta);
1558 auto& infos = state.inputChannelInfos;
1559
1560 if (context.balancingInputs) {
1561 static int pipelineLength = DefaultsHelpers::pipelineLength(*ref.get<RawDeviceService>().device()->fConfig);
1562 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));
1563 auto newEnd = std::remove_if(pollOrder.begin(), pollOrder.end(), [&infos, limitNew = currentOldest.value + ahead](int a) -> bool {
1564 return infos[a].oldestForChannel.value > limitNew;
1565 });
1566 for (auto it = pollOrder.begin(); it < pollOrder.end(); it++) {
1567 const auto& channelInfo = state.inputChannelInfos[*it];
1568 if (channelInfo.pollerIndex != -1) {
1569 auto& poller = state.activeInputPollers[channelInfo.pollerIndex];
1570 auto& pollerContext = *(PollerContext*)(poller->data);
1571 if (pollerContext.pollerState == PollerContext::PollerState::Connected || pollerContext.pollerState == PollerContext::PollerState::Suspended) {
1572 bool running = pollerContext.pollerState == PollerContext::PollerState::Connected;
1573 bool shouldBeRunning = it < newEnd;
1574 if (running != shouldBeRunning) {
1575 uv_poll_start(poller, shouldBeRunning ? UV_READABLE | UV_DISCONNECT | UV_PRIORITIZED : 0, &on_socket_polled);
1576 pollerContext.pollerState = shouldBeRunning ? PollerContext::PollerState::Connected : PollerContext::PollerState::Suspended;
1577 }
1578 }
1579 }
1580 }
1581 pollOrder.erase(newEnd, pollOrder.end());
1582 }
1583 O2_SIGNPOST_END(device, dpid, "do_prepare", "%zu channels pass the channel inbalance balance check.", pollOrder.size());
1584
1585 for (auto sci : pollOrder) {
1586 auto& info = state.inputChannelInfos[sci];
1588 O2_SIGNPOST_START(device, cid, "channels", "Processing channel %s", info.channel->GetName().c_str());
1589
1591 context.allDone = false;
1592 }
1593 if (info.state != InputChannelState::Running) {
1594 // Remember to flush data if we are not running
1595 // and there is some message pending.
1596 if (info.parts.Size()) {
1598 }
1599 O2_SIGNPOST_END(device, cid, "channels", "Flushing channel %s which is in state %d and has %zu parts still pending.",
1600 info.channel->GetName().c_str(), (int)info.state, info.parts.Size());
1601 continue;
1602 }
1603 if (info.channel == nullptr) {
1604 O2_SIGNPOST_END(device, cid, "channels", "Channel %s which is in state %d is nullptr and has %zu parts still pending.",
1605 info.channel->GetName().c_str(), (int)info.state, info.parts.Size());
1606 continue;
1607 }
1608 // Only poll DPL channels for now.
1609 if (info.channelType != ChannelAccountingType::DPL) {
1610 O2_SIGNPOST_END(device, cid, "channels", "Channel %s which is in state %d is not a DPL channel and has %zu parts still pending.",
1611 info.channel->GetName().c_str(), (int)info.state, info.parts.Size());
1612 continue;
1613 }
1614 auto& socket = info.channel->GetSocket();
1615 // If we have pending events from a previous iteration,
1616 // we do receive in any case.
1617 // Otherwise we check if there is any pending event and skip
1618 // this channel in case there is none.
1619 if (info.hasPendingEvents == 0) {
1620 socket.Events(&info.hasPendingEvents);
1621 // If we do not read, we can continue.
1622 if ((info.hasPendingEvents & 1) == 0 && (info.parts.Size() == 0)) {
1623 O2_SIGNPOST_END(device, cid, "channels", "No pending events and no remaining parts to process for channel %{public}s", info.channel->GetName().c_str());
1624 continue;
1625 }
1626 }
1627 // We can reset this, because it means we have seen at least 1
1628 // message after the UV_READABLE was raised.
1629 info.readPolled = false;
1630 // Notice that there seems to be a difference between the documentation
1631 // of zeromq and the observed behavior. The fact that ZMQ_POLLIN
1632 // is raised does not mean that a message is immediately available to
1633 // read, just that it will be available soon, so the receive can
1634 // still return -2. To avoid this we keep receiving on the socket until
1635 // we get a message. In order not to overflow the DPL queue we process
1636 // one message at the time and we keep track of wether there were more
1637 // to process.
1638 bool newMessages = false;
1639 while (true) {
1640 O2_SIGNPOST_EVENT_EMIT(device, cid, "channels", "Receiving loop called for channel %{public}s (%d) with oldest possible timeslice %zu",
1641 info.channel->GetName().c_str(), info.id.value, info.oldestForChannel.value);
1642 if (info.parts.Size() < 64) {
1643 fair::mq::Parts parts;
1644 info.channel->Receive(parts, 0);
1645 if (parts.Size()) {
1646 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);
1647 }
1648 for (auto&& part : parts) {
1649 info.parts.fParts.emplace_back(std::move(part));
1650 }
1651 newMessages |= true;
1652 }
1653
1654 if (info.parts.Size() >= 0) {
1656 // Receiving data counts as activity now, so that
1657 // We can make sure we process all the pending
1658 // messages without hanging on the uv_run.
1659 break;
1660 }
1661 }
1662 // We check once again for pending events, keeping track if this was the
1663 // case so that we can immediately repeat this loop and avoid remaining
1664 // stuck in uv_run. This is because we will not get notified on the socket
1665 // if more events are pending due to zeromq level triggered approach.
1666 socket.Events(&info.hasPendingEvents);
1667 if (info.hasPendingEvents) {
1668 info.readPolled = false;
1669 // In case there were messages, we consider it as activity
1670 if (newMessages) {
1671 state.lastActiveDataProcessor.store(&context);
1672 }
1673 }
1674 O2_SIGNPOST_END(device, cid, "channels", "Done processing channel %{public}s (%d).",
1675 info.channel->GetName().c_str(), info.id.value);
1676 }
1677}
1678
1680{
1681 auto& context = ref.get<DataProcessorContext>();
1682 auto& streamContext = ref.get<StreamContext>();
1683 O2_SIGNPOST_ID_FROM_POINTER(dpid, device, &context);
1684 auto& state = ref.get<DeviceState>();
1685 auto& spec = ref.get<DeviceSpec const>();
1686
1687 if (state.streaming == StreamingState::Idle) {
1688 return;
1689 }
1690
1691 streamContext.completed.clear();
1692 streamContext.completed.reserve(16);
1693 if (DataProcessingDevice::tryDispatchComputation(ref, streamContext.completed)) {
1694 state.lastActiveDataProcessor.store(&context);
1695 }
1696 DanglingContext danglingContext{*context.registry};
1697
1698 context.preDanglingCallbacks(danglingContext);
1699 if (state.lastActiveDataProcessor.load() == nullptr) {
1700 ref.get<CallbackService>().call<CallbackService::Id::Idle>();
1701 }
1702 auto activity = ref.get<DataRelayer>().processDanglingInputs(context.expirationHandlers, *context.registry, true);
1703 if (activity.expiredSlots > 0) {
1704 state.lastActiveDataProcessor = &context;
1705 }
1706
1707 streamContext.completed.clear();
1708 if (DataProcessingDevice::tryDispatchComputation(ref, streamContext.completed)) {
1709 state.lastActiveDataProcessor = &context;
1710 }
1711
1712 context.postDanglingCallbacks(danglingContext);
1713
1714 // If we got notified that all the sources are done, we call the EndOfStream
1715 // callback and return false. Notice that what happens next is actually
1716 // dependent on the callback, not something which is controlled by the
1717 // framework itself.
1718 if (context.allDone == true && state.streaming == StreamingState::Streaming) {
1720 state.lastActiveDataProcessor = &context;
1721 }
1722
1723 if (state.streaming == StreamingState::EndOfStreaming) {
1724 O2_SIGNPOST_EVENT_EMIT(device, dpid, "state", "We are in EndOfStreaming. Flushing queues.");
1725 // We keep processing data until we are Idle.
1726 // FIXME: not sure this is the correct way to drain the queues, but
1727 // I guess we will see.
1730 auto& relayer = ref.get<DataRelayer>();
1731
1732 bool shouldProcess = DataProcessingHelpers::hasOnlyGenerated(spec) == false;
1733
1734 while (DataProcessingDevice::tryDispatchComputation(ref, streamContext.completed) && shouldProcess) {
1735 relayer.processDanglingInputs(context.expirationHandlers, *context.registry, false);
1736 }
1737
1738 auto& timingInfo = ref.get<TimingInfo>();
1739 // We should keep the data generated at end of stream only for those
1740 // which are not sources.
1741 timingInfo.keepAtEndOfStream = shouldProcess;
1742 // Fill timinginfo with some reasonable values for data sent with endOfStream
1743 timingInfo.timeslice = relayer.getOldestPossibleOutput().timeslice.value;
1744 timingInfo.tfCounter = -1;
1745 timingInfo.firstTForbit = -1;
1746 // timingInfo.runNumber = ; // Not sure where to get this if not already set
1747 timingInfo.creation = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now()).time_since_epoch().count();
1748 O2_SIGNPOST_EVENT_EMIT(calibration, dpid, "calibration", "TimingInfo.keepAtEndOfStream %d", timingInfo.keepAtEndOfStream);
1749
1750 EndOfStreamContext eosContext{*context.registry, ref.get<DataAllocator>()};
1751
1752 context.preEOSCallbacks(eosContext);
1753 auto& streamContext = ref.get<StreamContext>();
1754 streamContext.preEOSCallbacks(eosContext);
1755 ref.get<CallbackService>().call<CallbackService::Id::EndOfStream>(eosContext);
1756 streamContext.postEOSCallbacks(eosContext);
1757 context.postEOSCallbacks(eosContext);
1758
1759 for (auto& channel : spec.outputChannels) {
1760 O2_SIGNPOST_EVENT_EMIT(device, dpid, "state", "Sending end of stream to %{public}s.", channel.name.c_str());
1762 }
1763 // This is needed because the transport is deleted before the device.
1764 relayer.clear();
1766 // In case we should process, note the data processor responsible for it
1767 if (shouldProcess) {
1768 state.lastActiveDataProcessor = &context;
1769 }
1770 // On end of stream we shut down all output pollers.
1771 O2_SIGNPOST_EVENT_EMIT(device, dpid, "state", "Shutting down output pollers.");
1772 for (auto& poller : state.activeOutputPollers) {
1773 uv_poll_stop(poller);
1774 }
1775 return;
1776 }
1777
1778 if (state.streaming == StreamingState::Idle) {
1779 // On end of stream we shut down all output pollers.
1780 O2_SIGNPOST_EVENT_EMIT(device, dpid, "state", "Shutting down output pollers.");
1781 for (auto& poller : state.activeOutputPollers) {
1782 uv_poll_stop(poller);
1783 }
1784 }
1785
1786 return;
1787}
1788
1790{
1791 ServiceRegistryRef ref{mServiceRegistry};
1792 ref.get<DataRelayer>().clear();
1793 auto& deviceContext = ref.get<DeviceContext>();
1794 // If the signal handler is there, we should
1795 // hide the registry from it, so that we do not
1796 // end up calling the signal handler on something
1797 // which might not be there anymore.
1798 if (deviceContext.sigusr1Handle) {
1799 deviceContext.sigusr1Handle->data = nullptr;
1800 }
1801 // Makes sure we do not have a working context on
1802 // shutdown.
1803 for (auto& handle : ref.get<DeviceState>().activeSignals) {
1804 handle->data = nullptr;
1805 }
1806}
1807
1810 {
1811 }
1812};
1813
1814auto forwardOnInsertion(ServiceRegistryRef& ref, std::span<fair::mq::MessagePtr>& messages) -> void
1815{
1816 O2_SIGNPOST_ID_GENERATE(sid, forwarding);
1817
1818 auto& spec = ref.get<DeviceSpec const>();
1819 auto& context = ref.get<DataProcessorContext>();
1820 if (context.forwardPolicy == ForwardPolicy::AfterProcessing || spec.forwards.empty()) {
1821 O2_SIGNPOST_EVENT_EMIT(device, sid, "device", "Early forwardinding not enabled / needed.");
1822 return;
1823 }
1824
1825 O2_SIGNPOST_EVENT_EMIT(device, sid, "device", "Early forwardinding before injecting data into relayer.");
1826 auto& timesliceIndex = ref.get<TimesliceIndex>();
1827 auto oldestTimeslice = timesliceIndex.getOldestPossibleOutput();
1828
1829 auto& proxy = ref.get<FairMQDeviceProxy>();
1830
1831 O2_SIGNPOST_START(forwarding, sid, "forwardInputs",
1832 "Starting forwarding for incoming messages with oldestTimeslice %zu with copy",
1833 oldestTimeslice.timeslice.value);
1834 std::vector<fair::mq::Parts> forwardedParts(proxy.getNumForwardChannels());
1835 DataProcessingHelpers::routeForwardedMessages(proxy, messages, forwardedParts, true, false);
1836
1837 for (int fi = 0; fi < proxy.getNumForwardChannels(); fi++) {
1838 if (forwardedParts[fi].Size() == 0) {
1839 continue;
1840 }
1841 ForwardChannelInfo info = proxy.getForwardChannelInfo(ChannelIndex{fi});
1842 auto& parts = forwardedParts[fi];
1843 if (info.policy == nullptr) {
1844 O2_SIGNPOST_EVENT_EMIT_ERROR(forwarding, sid, "forwardInputs", "Forwarding to %{public}s %d has no policy.", info.name.c_str(), fi);
1845 continue;
1846 }
1847 O2_SIGNPOST_EVENT_EMIT(forwarding, sid, "forwardInputs", "Forwarding to %{public}s %d", info.name.c_str(), fi);
1848 info.policy->forward(parts, ChannelIndex{fi}, ref);
1849 }
1850 auto& asyncQueue = ref.get<AsyncQueue>();
1851 auto& decongestion = ref.get<DecongestionService>();
1852 O2_SIGNPOST_ID_GENERATE(aid, async_queue);
1853 O2_SIGNPOST_EVENT_EMIT(async_queue, aid, "forwardInputs", "Queuing forwarding oldestPossible %zu", oldestTimeslice.timeslice.value);
1854 AsyncQueueHelpers::post(asyncQueue, AsyncTask{.timeslice = oldestTimeslice.timeslice, .id = decongestion.oldestPossibleTimesliceTask, .debounce = -1, .callback = decongestionCallbackLate}
1855 .user<DecongestionContext>({.ref = ref, .oldestTimeslice = oldestTimeslice}));
1856 O2_SIGNPOST_END(forwarding, sid, "forwardInputs", "Forwarding done");
1857};
1858
1864{
1867
1868 auto& context = ref.get<DataProcessorContext>();
1869 // This is the same id as the upper level function, so we get the events
1870 // associated with the same interval. We will simply use "handle_data" as
1871 // the category.
1873
1874 // This is how we validate inputs. I.e. we try to enforce the O2 Data model
1875 // and we do a few stats. We bind parts as a lambda captured variable, rather
1876 // than an input, because we do not want the outer loop actually be exposed
1877 // to the implementation details of the messaging layer.
1878 auto getInputTypes = [&info, &context]() -> std::optional<std::vector<InputInfo>> {
1880 auto ref = ServiceRegistryRef{*context.registry};
1881 auto& stats = ref.get<DataProcessingStats>();
1882 auto& state = ref.get<DeviceState>();
1883 auto& parts = info.parts;
1884 stats.updateStats({(int)ProcessingStatsId::TOTAL_INPUTS, DataProcessingStats::Op::Set, (int64_t)parts.Size()});
1885
1886 std::vector<InputInfo> results;
1887 // we can reserve the upper limit
1888 results.reserve(parts.Size() / 2);
1889 size_t nTotalPayloads = 0;
1890
1891 auto insertInputInfo = [&results, &nTotalPayloads](size_t position, size_t length, InputType type, ChannelIndex index) {
1892 results.emplace_back(position, length, type, index);
1893 if (type != InputType::Invalid && length > 1) {
1894 nTotalPayloads += length - 1;
1895 }
1896 };
1897
1898 for (size_t pi = 0; pi < parts.Size(); pi += 2) {
1899 auto* headerData = parts.At(pi)->GetData();
1900 auto sih = o2::header::get<SourceInfoHeader*>(headerData);
1901 auto dh = o2::header::get<DataHeader*>(headerData);
1902 if (sih) {
1903 O2_SIGNPOST_EVENT_EMIT(device, cid, "handle_data", "Got SourceInfoHeader with state %d", (int)sih->state);
1904 info.state = sih->state;
1905 insertInputInfo(pi, 2, InputType::SourceInfo, info.id);
1906 state.lastActiveDataProcessor = &context;
1907 if (dh) {
1908 LOGP(error, "Found data attached to a SourceInfoHeader");
1909 }
1910 continue;
1911 }
1912 auto dih = o2::header::get<DomainInfoHeader*>(headerData);
1913 if (dih) {
1914 O2_SIGNPOST_EVENT_EMIT(device, cid, "handle_data", "Got DomainInfoHeader with oldestPossibleTimeslice %d", (int)dih->oldestPossibleTimeslice);
1915 insertInputInfo(pi, 2, InputType::DomainInfo, info.id);
1916 state.lastActiveDataProcessor = &context;
1917 if (dh) {
1918 LOGP(error, "Found data attached to a DomainInfoHeader");
1919 }
1920 continue;
1921 }
1922 if (!dh) {
1923 insertInputInfo(pi, 0, InputType::Invalid, info.id);
1924 O2_SIGNPOST_EVENT_EMIT_ERROR(device, cid, "handle_data", "Header is not a DataHeader?");
1925 continue;
1926 }
1927 if (dh->payloadSize > parts.At(pi + 1)->GetSize()) {
1928 insertInputInfo(pi, 0, InputType::Invalid, info.id);
1929 O2_SIGNPOST_EVENT_EMIT_ERROR(device, cid, "handle_data", "DataHeader payloadSize mismatch");
1930 continue;
1931 }
1932 auto dph = o2::header::get<DataProcessingHeader*>(headerData);
1933 // We only deal with the tracking of parts if the log is enabled.
1934 // This is because in principle we should track the size of each of
1935 // the parts and sum it up. Not for now.
1936 O2_SIGNPOST_ID_FROM_POINTER(pid, parts, headerData);
1937 O2_SIGNPOST_START(parts, pid, "parts", "Processing DataHeader %{public}-4s/%{public}-16s/%d with splitPayloadParts %d and splitPayloadIndex %d",
1938 dh->dataOrigin.str, dh->dataDescription.str, dh->subSpecification, dh->splitPayloadParts, dh->splitPayloadIndex);
1939 if (!dph) {
1940 insertInputInfo(pi, 2, InputType::Invalid, info.id);
1941 O2_SIGNPOST_EVENT_EMIT_ERROR(device, cid, "handle_data", "Header stack does not contain DataProcessingHeader");
1942 continue;
1943 }
1944 if (dh->splitPayloadParts > 0 && dh->splitPayloadParts == dh->splitPayloadIndex) {
1945 // this is indicating a sequence of payloads following the header
1946 // FIXME: we will probably also set the DataHeader version
1947 insertInputInfo(pi, dh->splitPayloadParts + 1, InputType::Data, info.id);
1948 pi += dh->splitPayloadParts - 1;
1949 } else {
1950 // We can set the type for the next splitPayloadParts
1951 // because we are guaranteed they are all the same.
1952 // If splitPayloadParts = 0, we assume that means there is only one (header, payload)
1953 // pair.
1954 size_t finalSplitPayloadIndex = pi + (dh->splitPayloadParts > 0 ? dh->splitPayloadParts : 1) * 2;
1955 if (finalSplitPayloadIndex > parts.Size()) {
1956 O2_SIGNPOST_EVENT_EMIT_ERROR(device, cid, "handle_data", "DataHeader::splitPayloadParts invalid");
1957 insertInputInfo(pi, 0, InputType::Invalid, info.id);
1958 continue;
1959 }
1960 insertInputInfo(pi, 2, InputType::Data, info.id);
1961 for (; pi + 2 < finalSplitPayloadIndex; pi += 2) {
1962 insertInputInfo(pi + 2, 2, InputType::Data, info.id);
1963 }
1964 }
1965 }
1966 if (results.size() + nTotalPayloads != parts.Size()) {
1967 O2_SIGNPOST_EVENT_EMIT_ERROR(device, cid, "handle_data", "inconsistent number of inputs extracted. %zu vs parts (%zu)", results.size() + nTotalPayloads, parts.Size());
1968 return std::nullopt;
1969 }
1970 return results;
1971 };
1972
1973 auto reportError = [ref](const char* message) {
1974 auto& stats = ref.get<DataProcessingStats>();
1976 };
1977
1978 auto handleValidMessages = [&info, ref, &reportError, &context](std::vector<InputInfo> const& inputInfos) {
1979 auto& relayer = ref.get<DataRelayer>();
1980 auto& state = ref.get<DeviceState>();
1981 static WaitBackpressurePolicy policy;
1982 auto& parts = info.parts;
1983 // We relay execution to make sure we have a complete set of parts
1984 // available.
1985 bool hasBackpressure = false;
1986 size_t minBackpressureTimeslice = -1;
1987 bool hasData = false;
1988 size_t oldestPossibleTimeslice = -1;
1989 static std::vector<int> ordering;
1990 // Same as inputInfos but with iota.
1991 ordering.resize(inputInfos.size());
1992 std::iota(ordering.begin(), ordering.end(), 0);
1993 // stable sort orderings by type and position
1994 std::stable_sort(ordering.begin(), ordering.end(), [&inputInfos](int const& a, int const& b) {
1995 auto const& ai = inputInfos[a];
1996 auto const& bi = inputInfos[b];
1997 if (ai.type != bi.type) {
1998 return ai.type < bi.type;
1999 }
2000 return ai.position < bi.position;
2001 });
2002 for (size_t ii = 0; ii < inputInfos.size(); ++ii) {
2003 auto const& input = inputInfos[ordering[ii]];
2004 switch (input.type) {
2005 case InputType::Data: {
2006 hasData = true;
2007 auto headerIndex = input.position;
2008 auto nMessages = 0;
2009 auto nPayloadsPerHeader = 0;
2010 if (input.size > 2) {
2011 // header and multiple payload sequence
2012 nMessages = input.size;
2013 nPayloadsPerHeader = nMessages - 1;
2014 } else {
2015 // multiple header-payload pairs
2016 auto dh = o2::header::get<DataHeader*>(parts.At(headerIndex)->GetData());
2017 nMessages = dh->splitPayloadParts > 0 ? dh->splitPayloadParts * 2 : 2;
2018 nPayloadsPerHeader = 1;
2019 ii += (nMessages / 2) - 1;
2020 }
2021 auto onDrop = [ref](TimesliceSlot slot, std::vector<std::span<fair::mq::MessagePtr>>& dropped, TimesliceIndex::OldestOutputInfo oldestOutputInfo) {
2022 O2_SIGNPOST_ID_GENERATE(cid, async_queue);
2023 O2_SIGNPOST_EVENT_EMIT(async_queue, cid, "onDrop", "Dropping message from slot %zu. Forwarding as needed. Timeslice %zu",
2024 slot.index, oldestOutputInfo.timeslice.value);
2025 ref.get<AsyncQueue>();
2026 ref.get<DecongestionService>();
2027 ref.get<DataRelayer>();
2028 // Get the current timeslice for the slot.
2029 auto& variables = ref.get<TimesliceIndex>().getVariablesForSlot(slot);
2031 forwardInputs(ref, slot, dropped, oldestOutputInfo, false, true);
2032 };
2033
2034 auto relayed = relayer.relay(parts.At(headerIndex)->GetData(),
2035 &parts.At(headerIndex),
2036 input,
2037 nMessages,
2038 nPayloadsPerHeader,
2039 context.forwardPolicy == ForwardPolicy::AtInjection ? forwardOnInsertion : nullptr,
2040 onDrop);
2041 switch (relayed.type) {
2043 if (info.normalOpsNotified == true && info.backpressureNotified == false) {
2044 LOGP(alarm, "Backpressure on channel {}. Waiting.", info.channel->GetName());
2045 auto& monitoring = ref.get<o2::monitoring::Monitoring>();
2046 monitoring.send(o2::monitoring::Metric{1, fmt::format("backpressure_{}", info.channel->GetName())});
2047 info.backpressureNotified = true;
2048 info.normalOpsNotified = false;
2049 }
2050 policy.backpressure(info);
2051 hasBackpressure = true;
2052 minBackpressureTimeslice = std::min<size_t>(minBackpressureTimeslice, relayed.timeslice.value);
2053 break;
2057 if (info.normalOpsNotified == false && info.backpressureNotified == true) {
2058 LOGP(info, "Back to normal on channel {}.", info.channel->GetName());
2059 auto& monitoring = ref.get<o2::monitoring::Monitoring>();
2060 monitoring.send(o2::monitoring::Metric{0, fmt::format("backpressure_{}", info.channel->GetName())});
2061 info.normalOpsNotified = true;
2062 info.backpressureNotified = false;
2063 }
2064 break;
2065 }
2066 } break;
2067 case InputType::SourceInfo: {
2068 LOGP(detail, "Received SourceInfo");
2069 auto& context = ref.get<DataProcessorContext>();
2070 state.lastActiveDataProcessor = &context;
2071 auto headerIndex = input.position;
2072 auto payloadIndex = input.position + 1;
2073 assert(payloadIndex < parts.Size());
2074 // FIXME: the message with the end of stream cannot contain
2075 // split parts.
2076 parts.At(headerIndex).reset(nullptr);
2077 parts.At(payloadIndex).reset(nullptr);
2078 // for (size_t i = 0; i < dh->splitPayloadParts > 0 ? dh->splitPayloadParts * 2 - 1 : 1; ++i) {
2079 // parts.At(headerIndex + 1 + i).reset(nullptr);
2080 // }
2081 // pi += dh->splitPayloadParts > 0 ? dh->splitPayloadParts - 1 : 0;
2082
2083 } break;
2084 case InputType::DomainInfo: {
2087 auto& context = ref.get<DataProcessorContext>();
2088 state.lastActiveDataProcessor = &context;
2089 auto headerIndex = input.position;
2090 auto payloadIndex = input.position + 1;
2091 assert(payloadIndex < parts.Size());
2092 // FIXME: the message with the end of stream cannot contain
2093 // split parts.
2094
2095 auto dih = o2::header::get<DomainInfoHeader*>(parts.At(headerIndex)->GetData());
2096 if (hasBackpressure && dih->oldestPossibleTimeslice >= minBackpressureTimeslice) {
2097 break;
2098 }
2099 oldestPossibleTimeslice = std::min(oldestPossibleTimeslice, dih->oldestPossibleTimeslice);
2100 LOGP(debug, "Got DomainInfoHeader, new oldestPossibleTimeslice {} on channel {}", oldestPossibleTimeslice, info.id.value);
2101 parts.At(headerIndex).reset(nullptr);
2102 parts.At(payloadIndex).reset(nullptr);
2103 } break;
2104 case InputType::Invalid: {
2105 reportError("Invalid part found.");
2106 } break;
2107 }
2108 }
2111 if (oldestPossibleTimeslice != (size_t)-1) {
2112 info.oldestForChannel = {oldestPossibleTimeslice};
2113 auto& context = ref.get<DataProcessorContext>();
2114 context.domainInfoUpdatedCallback(*context.registry, oldestPossibleTimeslice, info.id);
2115 ref.get<CallbackService>().call<CallbackService::Id::DomainInfoUpdated>((ServiceRegistryRef)*context.registry, (size_t)oldestPossibleTimeslice, (ChannelIndex)info.id);
2116 state.lastActiveDataProcessor = &context;
2117 }
2118 auto it = std::remove_if(parts.fParts.begin(), parts.fParts.end(), [](auto& msg) -> bool { return msg.get() == nullptr; });
2119 parts.fParts.erase(it, parts.end());
2120 if (parts.fParts.size()) {
2121 LOG(debug) << parts.fParts.size() << " messages backpressured";
2122 }
2123 };
2124
2125 // Second part. This is the actual outer loop we want to obtain, with
2126 // implementation details which can be read. Notice how most of the state
2127 // is actually hidden. For example we do not expose what "input" is. This
2128 // will allow us to keep the same toplevel logic even if the actual meaning
2129 // of input is changed (for example we might move away from multipart
2130 // messages). Notice also that we need to act diffently depending on the
2131 // actual CompletionOp we want to perform. In particular forwarding inputs
2132 // also gets rid of them from the cache.
2133 auto inputTypes = getInputTypes();
2134 if (bool(inputTypes) == false) {
2135 reportError("Parts should come in couples. Dropping it.");
2136 return;
2137 }
2138 handleValidMessages(*inputTypes);
2139 return;
2140}
2141
2142namespace
2143{
2144struct InputLatency {
2145 uint64_t minLatency = std::numeric_limits<uint64_t>::max();
2146 uint64_t maxLatency = std::numeric_limits<uint64_t>::min();
2147};
2148
2149auto calculateInputRecordLatency(InputRecord const& record, uint64_t currentTime) -> InputLatency
2150{
2151 InputLatency result;
2152
2153 for (auto& item : record) {
2154 auto* header = o2::header::get<DataProcessingHeader*>(item.header);
2155 if (header == nullptr) {
2156 continue;
2157 }
2158 int64_t partLatency = (0x7fffffffffffffff & currentTime) - (0x7fffffffffffffff & header->creation);
2159 if (partLatency < 0) {
2160 partLatency = 0;
2161 }
2162 result.minLatency = std::min(result.minLatency, (uint64_t)partLatency);
2163 result.maxLatency = std::max(result.maxLatency, (uint64_t)partLatency);
2164 }
2165 return result;
2166};
2167
2168auto calculateTotalInputRecordSize(InputRecord const& record) -> int
2169{
2170 size_t totalInputSize = 0;
2171 for (auto& item : record) {
2172 auto* header = o2::header::get<DataHeader*>(item.header);
2173 if (header == nullptr) {
2174 continue;
2175 }
2176 totalInputSize += header->payloadSize;
2177 }
2178 return totalInputSize;
2179};
2180
2181template <typename T>
2182void update_maximum(std::atomic<T>& maximum_value, T const& value) noexcept
2183{
2184 T prev_value = maximum_value;
2185 while (prev_value < value &&
2186 !maximum_value.compare_exchange_weak(prev_value, value)) {
2187 }
2188}
2189} // namespace
2190
2191bool DataProcessingDevice::tryDispatchComputation(ServiceRegistryRef ref, std::vector<DataRelayer::RecordAction>& completed)
2192{
2193 auto& context = ref.get<DataProcessorContext>();
2194 LOGP(debug, "DataProcessingDevice::tryDispatchComputation");
2195 // This is the actual hidden state for the outer loop. In case we decide we
2196 // want to support multithreaded dispatching of operations, I can simply
2197 // move these to some thread local store and the rest of the lambdas
2198 // should work just fine.
2199 std::vector<std::span<fair::mq::MessagePtr>> currentSetOfInputs;
2200 std::vector<std::vector<fair::mq::MessagePtr>> ownedInputs;
2201
2202 //
2203 auto getInputSpan = [ref, &currentSetOfInputs, &ownedInputs](TimesliceSlot slot, bool consume = true) {
2204 auto& relayer = ref.get<DataRelayer>();
2205 if (consume) {
2206 ownedInputs = relayer.consumeAllInputsForTimeslice(slot);
2207 } else {
2208 ownedInputs = relayer.consumeExistingInputsForTimeslice(slot);
2209 }
2210 currentSetOfInputs.resize(ownedInputs.size());
2211 for (size_t i = 0; i < ownedInputs.size(); ++i) {
2212 currentSetOfInputs[i] = std::span(ownedInputs[i]);
2213 }
2214 // Convert raw message indices directly to a DataRef in O(1).
2215 // Used both by the sequential PartIterator and as the fallback for positional access.
2216 auto indicesGetter = [&currentSetOfInputs](size_t i, DataRefIndices indices) -> DataRef {
2217 auto const& msgs = currentSetOfInputs[i];
2218 if (msgs.size() <= indices.headerIdx) {
2219 return DataRef{};
2220 }
2221 auto const& headerMsg = msgs[indices.headerIdx];
2222 char const* payloadData = nullptr;
2223 size_t payloadSize = 0;
2224 if (msgs.size() > indices.payloadIdx && msgs[indices.payloadIdx]) {
2225 payloadData = static_cast<char const*>(msgs[indices.payloadIdx]->GetData());
2226 payloadSize = msgs[indices.payloadIdx]->GetSize();
2227 }
2228 return DataRef{nullptr,
2229 headerMsg ? static_cast<char const*>(headerMsg->GetData()) : nullptr,
2230 payloadData,
2231 payloadSize};
2232 };
2233 auto nofPartsGetter = [&currentSetOfInputs](size_t i) -> size_t {
2234 return (currentSetOfInputs[i] | count_payloads{});
2235 };
2236 auto refCountGetter = [&currentSetOfInputs](size_t idx) -> int {
2237 auto& header = static_cast<const fair::mq::shmem::Message&>(*(currentSetOfInputs[idx] | get_header{0}));
2238 return header.GetRefCount();
2239 };
2240 auto nextIndicesGetter = [&currentSetOfInputs](size_t i, DataRefIndices current) -> DataRefIndices {
2241 auto next = currentSetOfInputs[i] | get_next_pair{current};
2242 return next.headerIdx < currentSetOfInputs[i].size() ? next : DataRefIndices{size_t(-1), size_t(-1)};
2243 };
2244 auto payloadGetter = [&currentSetOfInputs](size_t i, DataRefIndices current) -> fair::mq::Message* {
2245 auto const& msgs = currentSetOfInputs[i];
2246 if (msgs.size() <= current.payloadIdx || !msgs[current.payloadIdx]) {
2247 return nullptr;
2248 }
2249 return msgs[current.payloadIdx].get();
2250 };
2251 return InputSpan{nofPartsGetter, refCountGetter, indicesGetter, nextIndicesGetter, payloadGetter, currentSetOfInputs.size()};
2252 };
2253
2254 auto markInputsAsDone = [ref](TimesliceSlot slot) -> void {
2255 auto& relayer = ref.get<DataRelayer>();
2257 };
2258
2259 // I need a preparation step which gets the current timeslice id and
2260 // propagates it to the various contextes (i.e. the actual entities which
2261 // create messages) because the messages need to have the timeslice id into
2262 // it.
2263 auto prepareAllocatorForCurrentTimeSlice = [ref](TimesliceSlot i) -> void {
2264 auto& relayer = ref.get<DataRelayer>();
2265 auto& timingInfo = ref.get<TimingInfo>();
2266 auto timeslice = relayer.getTimesliceForSlot(i);
2267
2268 timingInfo.timeslice = timeslice.value;
2269 timingInfo.tfCounter = relayer.getFirstTFCounterForSlot(i);
2270 timingInfo.firstTForbit = relayer.getFirstTFOrbitForSlot(i);
2271 timingInfo.runNumber = relayer.getRunNumberForSlot(i);
2272 timingInfo.creation = relayer.getCreationTimeForSlot(i);
2273 };
2274 auto updateRunInformation = [ref](TimesliceSlot i) -> void {
2275 auto& dataProcessorContext = ref.get<DataProcessorContext>();
2276 auto& relayer = ref.get<DataRelayer>();
2277 auto& timingInfo = ref.get<TimingInfo>();
2278 auto timeslice = relayer.getTimesliceForSlot(i);
2279 // We report wether or not this timing info refers to a new Run.
2280 timingInfo.globalRunNumberChanged = !TimingInfo::timesliceIsTimer(timeslice.value) && dataProcessorContext.lastRunNumberProcessed != timingInfo.runNumber;
2281 // A switch to runNumber=0 should not appear and thus does not set globalRunNumberChanged, unless it is seen in the first processed timeslice
2282 timingInfo.globalRunNumberChanged &= (dataProcessorContext.lastRunNumberProcessed == -1 || timingInfo.runNumber != 0);
2283 // FIXME: for now there is only one stream, however we
2284 // should calculate this correctly once we finally get the
2285 // the StreamContext in.
2286 timingInfo.streamRunNumberChanged = timingInfo.globalRunNumberChanged;
2287 };
2288
2289 // When processing them, timers will have to be cleaned up
2290 // to avoid double counting them.
2291 // This was actually the easiest solution we could find for
2292 // O2-646.
2293 auto cleanTimers = [&currentSetOfInputs, &ownedInputs](TimesliceSlot slot, InputRecord& record) {
2294 assert(record.size() == currentSetOfInputs.size());
2295 for (size_t ii = 0, ie = record.size(); ii < ie; ++ii) {
2296 // assuming that for timer inputs we do have exactly one PartRef object
2297 // in the MessageSet, multiple PartRef Objects are only possible for either
2298 // split payload messages of wildcard matchers, both for data inputs
2299 DataRef input = record.getByPos(ii);
2300 if (input.spec->lifetime != Lifetime::Timer) {
2301 continue;
2302 }
2303 if (input.header == nullptr) {
2304 continue;
2305 }
2306 // For the consume=false (Process) path, ownedInputs holds the actual
2307 // message vectors and the span points into them.
2308 ownedInputs[ii].clear();
2309 currentSetOfInputs[ii] = {};
2310 }
2311 };
2312
2313 // Function to cleanup record. For the moment we
2314 // simply use it to keep track of input messages
2315 // which are not needed, to display them in the GUI.
2316 auto cleanupRecord = [](InputRecord& record) {
2317 if (O2_LOG_ENABLED(parts) == false) {
2318 return;
2319 }
2320 for (size_t pi = 0, pe = record.size(); pi < pe; ++pi) {
2321 DataRef input = record.getByPos(pi);
2322 if (input.header == nullptr) {
2323 continue;
2324 }
2325 auto sih = o2::header::get<SourceInfoHeader*>(input.header);
2326 if (sih) {
2327 continue;
2328 }
2329
2330 auto dh = o2::header::get<DataHeader*>(input.header);
2331 if (!dh) {
2332 continue;
2333 }
2334 // We use the address of the first header of a split payload
2335 // to identify the interval.
2336 O2_SIGNPOST_ID_FROM_POINTER(pid, parts, dh);
2337 O2_SIGNPOST_END(parts, pid, "parts", "Cleaning up parts associated to %p", dh);
2338
2339 // No split parts, we simply skip the payload
2340 if (dh->splitPayloadParts > 0 && dh->splitPayloadParts == dh->splitPayloadIndex) {
2341 // this is indicating a sequence of payloads following the header
2342 // FIXME: we will probably also set the DataHeader version
2343 pi += dh->splitPayloadParts - 1;
2344 } else {
2345 size_t pi = pi + (dh->splitPayloadParts > 0 ? dh->splitPayloadParts : 1) * 2;
2346 }
2347 }
2348 };
2349
2350 ref.get<DataRelayer>().getReadyToProcess(completed);
2351 if (completed.empty() == true) {
2352 LOGP(debug, "No computations available for dispatching.");
2353 return false;
2354 }
2355
2356 int pipelineLength = DefaultsHelpers::pipelineLength(*ref.get<RawDeviceService>().device()->fConfig);
2357
2358 auto postUpdateStats = [ref, pipelineLength](DataRelayer::RecordAction const& action, InputRecord const& record, uint64_t tStart, uint64_t tStartMilli) {
2359 auto& stats = ref.get<DataProcessingStats>();
2360 auto& states = ref.get<DataProcessingStates>();
2361 std::atomic_thread_fence(std::memory_order_release);
2362 char relayerSlotState[1024];
2363 int written = snprintf(relayerSlotState, 1024, "%d ", pipelineLength);
2364 char* buffer = relayerSlotState + written;
2365 for (size_t ai = 0; ai != record.size(); ai++) {
2366 buffer[ai] = record.isValid(ai) ? '3' : '0';
2367 }
2368 buffer[record.size()] = 0;
2369 states.updateState({.id = short((int)ProcessingStateId::DATA_RELAYER_BASE + action.slot.index),
2370 .size = (int)(record.size() + buffer - relayerSlotState),
2371 .data = relayerSlotState});
2372 uint64_t tEnd = uv_hrtime();
2373 // tEnd and tStart are in nanoseconds according to https://docs.libuv.org/en/v1.x/misc.html#c.uv_hrtime
2374 int64_t wallTimeMs = (tEnd - tStart) / 1000000;
2375 stats.updateStats({(int)ProcessingStatsId::LAST_ELAPSED_TIME_MS, DataProcessingStats::Op::Set, wallTimeMs});
2376 // Sum up the total wall time, in milliseconds.
2377 stats.updateStats({(int)ProcessingStatsId::TOTAL_WALL_TIME_MS, DataProcessingStats::Op::Add, wallTimeMs});
2378 // The time interval is in seconds while tEnd - tStart is in nanoseconds, so we divide by 1000000 to get the fraction in ms/s.
2380 stats.updateStats({(int)ProcessingStatsId::LAST_PROCESSED_SIZE, DataProcessingStats::Op::Set, calculateTotalInputRecordSize(record)});
2381 stats.updateStats({(int)ProcessingStatsId::TOTAL_PROCESSED_SIZE, DataProcessingStats::Op::Add, calculateTotalInputRecordSize(record)});
2382 auto latency = calculateInputRecordLatency(record, tStartMilli);
2383 stats.updateStats({(int)ProcessingStatsId::LAST_MIN_LATENCY, DataProcessingStats::Op::Set, (int)latency.minLatency});
2384 stats.updateStats({(int)ProcessingStatsId::LAST_MAX_LATENCY, DataProcessingStats::Op::Set, (int)latency.maxLatency});
2385 static int count = 0;
2387 count++;
2388 };
2389
2390 auto preUpdateStats = [ref, pipelineLength](DataRelayer::RecordAction const& action, InputRecord const& record, uint64_t) {
2391 auto& states = ref.get<DataProcessingStates>();
2392 std::atomic_thread_fence(std::memory_order_release);
2393 char relayerSlotState[1024];
2394 snprintf(relayerSlotState, 1024, "%d ", pipelineLength);
2395 char* buffer = strchr(relayerSlotState, ' ') + 1;
2396 for (size_t ai = 0; ai != record.size(); ai++) {
2397 buffer[ai] = record.isValid(ai) ? '2' : '0';
2398 }
2399 buffer[record.size()] = 0;
2400 states.updateState({.id = short((int)ProcessingStateId::DATA_RELAYER_BASE + action.slot.index), .size = (int)(record.size() + buffer - relayerSlotState), .data = relayerSlotState});
2401 };
2402
2403 // This is the main dispatching loop
2404 auto& state = ref.get<DeviceState>();
2405 auto& spec = ref.get<DeviceSpec const>();
2406
2407 auto& dpContext = ref.get<DataProcessorContext>();
2408 auto& streamContext = ref.get<StreamContext>();
2410 O2_SIGNPOST_START(device, sid, "device", "Start processing ready actions");
2411
2412 auto& stats = ref.get<DataProcessingStats>();
2413 auto& relayer = ref.get<DataRelayer>();
2414 using namespace o2::framework;
2415 stats.updateStats({(int)ProcessingStatsId::PENDING_INPUTS, DataProcessingStats::Op::Set, static_cast<int64_t>(relayer.getParallelTimeslices() - completed.size())});
2416 stats.updateStats({(int)ProcessingStatsId::INCOMPLETE_INPUTS, DataProcessingStats::Op::Set, completed.empty() ? 1 : 0});
2417 switch (spec.completionPolicy.order) {
2419 std::sort(completed.begin(), completed.end(), [](auto const& a, auto const& b) { return a.timeslice.value < b.timeslice.value; });
2420 break;
2422 std::sort(completed.begin(), completed.end(), [](auto const& a, auto const& b) { return a.slot.index < b.slot.index; });
2423 break;
2425 default:
2426 break;
2427 }
2428
2429 for (auto action : completed) {
2431 O2_SIGNPOST_START(device, aid, "device", "Processing action on slot %lu for action %{public}s", action.slot.index, fmt::format("{}", action.op).c_str());
2432 if (action.op == CompletionPolicy::CompletionOp::Wait) {
2433 O2_SIGNPOST_END(device, aid, "device", "Waiting for more data.");
2434 continue;
2435 }
2436
2437 bool shouldConsume = action.op == CompletionPolicy::CompletionOp::Consume ||
2439 prepareAllocatorForCurrentTimeSlice(TimesliceSlot{action.slot});
2440 if (action.op != CompletionPolicy::CompletionOp::Discard &&
2443 updateRunInformation(TimesliceSlot{action.slot});
2444 }
2445 InputSpan span = getInputSpan(action.slot, shouldConsume);
2446 auto& spec = ref.get<DeviceSpec const>();
2447 InputRecord record{spec.inputs,
2448 span,
2449 *context.registry};
2450 ProcessingContext processContext{record, ref, ref.get<DataAllocator>()};
2451 {
2452 // Notice this should be thread safe and reentrant
2453 // as it is called from many threads.
2454 streamContext.preProcessingCallbacks(processContext);
2455 dpContext.preProcessingCallbacks(processContext);
2456 }
2457 if (action.op == CompletionPolicy::CompletionOp::Discard) {
2458 context.postDispatchingCallbacks(processContext);
2459 if (spec.forwards.empty() == false) {
2460 auto& timesliceIndex = ref.get<TimesliceIndex>();
2461 forwardInputs(ref, action.slot, currentSetOfInputs, timesliceIndex.getOldestPossibleOutput(), false);
2462 O2_SIGNPOST_END(device, aid, "device", "Forwarding inputs consume: %d.", false);
2463 continue;
2464 }
2465 }
2466 // If there is no optional inputs we canForwardEarly
2467 // the messages to that parallel processing can happen.
2468 // In this case we pass true to indicate that we want to
2469 // copy the messages to the subsequent data processor.
2470 bool hasForwards = spec.forwards.empty() == false;
2471 bool consumeSomething = action.op == CompletionPolicy::CompletionOp::Consume || action.op == CompletionPolicy::CompletionOp::ConsumeExisting;
2472
2473 if (context.forwardPolicy == ForwardPolicy::AtCompletionPolicySatisified && hasForwards && consumeSomething) {
2474 O2_SIGNPOST_EVENT_EMIT(device, aid, "device", "Early forwarding: %{public}s.", fmt::format("{}", action.op).c_str());
2475 auto& timesliceIndex = ref.get<TimesliceIndex>();
2476 forwardInputs(ref, action.slot, currentSetOfInputs, timesliceIndex.getOldestPossibleOutput(), true, action.op == CompletionPolicy::CompletionOp::Consume);
2477 } else if (context.forwardPolicy == ForwardPolicy::AtInjection && hasForwards && consumeSomething) {
2478 // We used to do fowarding here, however we now do it much earlier.
2479 // We still need to clean the inputs which were already consumed
2480 // via ConsumeExisting and which still have an header to hold the slot.
2481 // FIXME: do we? This should really happen when we do the forwarding on
2482 // insertion, because otherwise we lose the relevant information on how to
2483 // navigate the set of headers. We could actually rely on the messageset index,
2484 // is that the right thing to do though?
2485 O2_SIGNPOST_EVENT_EMIT(device, aid, "device", "cleaning early forwarding: %{public}s.", fmt::format("{}", action.op).c_str());
2486 auto& timesliceIndex = ref.get<TimesliceIndex>();
2487 cleanEarlyForward(ref, action.slot, currentSetOfInputs, timesliceIndex.getOldestPossibleOutput(), true, action.op == CompletionPolicy::CompletionOp::Consume);
2488 }
2489
2490 markInputsAsDone(action.slot);
2491
2492 uint64_t tStart = uv_hrtime();
2494 preUpdateStats(action, record, tStart);
2495
2496 static bool noCatch = getenv("O2_NO_CATCHALL_EXCEPTIONS") && strcmp(getenv("O2_NO_CATCHALL_EXCEPTIONS"), "0");
2497
2498 auto runNoCatch = [&context, ref, &processContext](DataRelayer::RecordAction& action) mutable {
2499 auto& state = ref.get<DeviceState>();
2500 auto& spec = ref.get<DeviceSpec const>();
2501 auto& streamContext = ref.get<StreamContext>();
2502 auto& dpContext = ref.get<DataProcessorContext>();
2503 auto shouldProcess = [](DataRelayer::RecordAction& action) -> bool {
2504 switch (action.op) {
2509 return true;
2510 break;
2511 default:
2512 return false;
2513 }
2514 };
2515 if (state.quitRequested == false) {
2516 {
2517 // Callbacks from services
2518 dpContext.preProcessingCallbacks(processContext);
2519 streamContext.preProcessingCallbacks(processContext);
2520 dpContext.preProcessingCallbacks(processContext);
2521 // Callbacks from users
2522 ref.get<CallbackService>().call<CallbackService::Id::PreProcessing>(o2::framework::ServiceRegistryRef{ref}, (int)action.op);
2523 }
2524 O2_SIGNPOST_ID_FROM_POINTER(pcid, device, &processContext);
2525 if (context.statefulProcess && shouldProcess(action)) {
2526 // This way, usercode can use the the same processing context to identify
2527 // its signposts and we can map user code to device iterations.
2528 O2_SIGNPOST_START(device, pcid, "device", "Stateful process");
2529 (context.statefulProcess)(processContext);
2530 O2_SIGNPOST_END(device, pcid, "device", "Stateful process");
2531 } else if (context.statelessProcess && shouldProcess(action)) {
2532 O2_SIGNPOST_START(device, pcid, "device", "Stateful process");
2533 (context.statelessProcess)(processContext);
2534 O2_SIGNPOST_END(device, pcid, "device", "Stateful process");
2535 } else if (context.statelessProcess || context.statefulProcess) {
2536 O2_SIGNPOST_EVENT_EMIT(device, pcid, "device", "Skipping processing because we are discarding.");
2537 } else {
2538 O2_SIGNPOST_EVENT_EMIT(device, pcid, "device", "No processing callback provided. Switching to %{public}s.", "Idle");
2540 }
2541 if (shouldProcess(action)) {
2542 auto& timingInfo = ref.get<TimingInfo>();
2543 if (timingInfo.globalRunNumberChanged) {
2544 context.lastRunNumberProcessed = timingInfo.runNumber;
2545 }
2546 }
2547
2548 // Notify the sink we just consumed some timeframe data
2549 if (context.isSink && action.op == CompletionPolicy::CompletionOp::Consume) {
2550 O2_SIGNPOST_EVENT_EMIT(device, pcid, "device", "Sending dpl-summary");
2551 auto& allocator = ref.get<DataAllocator>();
2552 allocator.make<int>(OutputRef{"dpl-summary", runtime_hash(spec.name.c_str())}, 1);
2553 }
2554
2555 // Extra callback which allows a service to add extra outputs.
2556 // This is needed e.g. to ensure that injected CCDB outputs are added
2557 // before an end of stream.
2558 {
2559 ref.get<CallbackService>().call<CallbackService::Id::FinaliseOutputs>(o2::framework::ServiceRegistryRef{ref}, (int)action.op);
2560 dpContext.finaliseOutputsCallbacks(processContext);
2561 streamContext.finaliseOutputsCallbacks(processContext);
2562 }
2563
2564 {
2565 ref.get<CallbackService>().call<CallbackService::Id::PostProcessing>(o2::framework::ServiceRegistryRef{ref}, (int)action.op);
2566 dpContext.postProcessingCallbacks(processContext);
2567 streamContext.postProcessingCallbacks(processContext);
2568 }
2569 }
2570 };
2571
2572 if ((state.tracingFlags & DeviceState::LoopReason::TRACE_USERCODE) != 0) {
2573 state.severityStack.push_back((int)fair::Logger::GetConsoleSeverity());
2574 fair::Logger::SetConsoleSeverity(fair::Severity::trace);
2575 }
2576 if (noCatch) {
2577 try {
2578 runNoCatch(action);
2580 (context.errorHandling)(e, record);
2581 }
2582 } else {
2583 try {
2584 runNoCatch(action);
2585 } catch (std::exception& ex) {
2589 auto e = runtime_error(ex.what());
2590 (context.errorHandling)(e, record);
2592 (context.errorHandling)(e, record);
2593 }
2594 }
2595 if (state.severityStack.empty() == false) {
2596 fair::Logger::SetConsoleSeverity((fair::Severity)state.severityStack.back());
2597 state.severityStack.pop_back();
2598 }
2599
2600 postUpdateStats(action, record, tStart, tStartMilli);
2601 // We forward inputs only when we consume them. If we simply Process them,
2602 // we keep them for next message arriving.
2603 if (action.op == CompletionPolicy::CompletionOp::Consume) {
2604 cleanupRecord(record);
2605 context.postDispatchingCallbacks(processContext);
2606 ref.get<CallbackService>().call<CallbackService::Id::DataConsumed>(o2::framework::ServiceRegistryRef{ref});
2607 }
2608 if ((context.forwardPolicy == ForwardPolicy::AfterProcessing) && hasForwards && consumeSomething) {
2609 O2_SIGNPOST_EVENT_EMIT(device, aid, "device", "Late forwarding");
2610 auto& timesliceIndex = ref.get<TimesliceIndex>();
2611 forwardInputs(ref, action.slot, currentSetOfInputs, timesliceIndex.getOldestPossibleOutput(), false, action.op == CompletionPolicy::CompletionOp::Consume);
2612 }
2613 context.postForwardingCallbacks(processContext);
2614 if (action.op == CompletionPolicy::CompletionOp::Process) {
2615 cleanTimers(action.slot, record);
2616 }
2617 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());
2618 }
2619 O2_SIGNPOST_END(device, sid, "device", "Start processing ready actions");
2620
2621 // We now broadcast the end of stream if it was requested
2622 if (state.streaming == StreamingState::EndOfStreaming) {
2623 LOGP(detail, "Broadcasting end of stream");
2624 for (auto& channel : spec.outputChannels) {
2626 }
2628 }
2629
2630 return true;
2631}
2632
2634{
2635 LOG(error) << msg;
2636 ServiceRegistryRef ref{mServiceRegistry};
2637 auto& stats = ref.get<DataProcessingStats>();
2639}
2640
2641std::unique_ptr<ConfigParamStore> DeviceConfigurationHelpers::getConfiguration(ServiceRegistryRef registry, const char* name, std::vector<ConfigParamSpec> const& options)
2642{
2643
2644 if (registry.active<ConfigurationInterface>()) {
2645 auto& cfg = registry.get<ConfigurationInterface>();
2646 try {
2647 cfg.getRecursive(name);
2648 std::vector<std::unique_ptr<ParamRetriever>> retrievers;
2649 retrievers.emplace_back(std::make_unique<ConfigurationOptionsRetriever>(&cfg, name));
2650 auto configStore = std::make_unique<ConfigParamStore>(options, std::move(retrievers));
2651 configStore->preload();
2652 configStore->activate();
2653 return configStore;
2654 } catch (...) {
2655 // No overrides...
2656 }
2657 }
2658 return {nullptr};
2659}
2660
2661} // namespace o2::framework
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
std::vector< std::string > header
int32_t i
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char events
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error s
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char Directory with events to Events directory is and not inside events Show standalone event Run event generator maximum time bin of continuous for triggered for automatic continuous for automatic continuous triggered uint64_t
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char e
configStandalone Reconstruction rec_tpc multiplier for the combined cluster track error during track following area in cm for the search of for z only used if decrease removal by when above this local occupancy rowx Shift of TPC revert cluster flag edge bit distance to edge exceeds this ZOffset for secondary multiple of radius max P of electron K BB bands crossing max P of electron p BB bands crossing max P of electron d BB bands crossing max P of electron t BB bands crossing min accepted dEdxMaxTPC of the track Additive extra cluster error for Y2 if edge flag set Additive extra cluster error for Y2 if single set Additive extra cluster error for Y2 if or single set Additive extra cluster error for Z2 if or single set Extra errors to add to track when crossing depending on addErrorsCECrossing Inner portion of y range in sector that is not used in searching for extrapolated track candidates factor2YS for track merging factor2K for track merging Margin in cm of Y position when rejecting edge clusters based on uncorrected track Y Max fraction of shared clusters for tracklet Except pad from cluster finding if total number of charges in a fragment is above this Except pad from cluster finding if number of consecutive charges in a fragment is above this Threshold where a timebin is considered disabling the noisy pad check for that pad Treat signal above this minimum as saturated Smoothing factor for the exponential Highly Ionising Particle tail filter Number of timebins to use for the averaging Add additional custom track errors when crossing relative as fraction of Do N fit passes in final fit of< 0:reject at max -n hits") AddOptionRTC(rejectIFCLowRadiusCluster, uint8_t, 1, "", 0, "Reject clusters that get the IFC mask error during refit") AddOptionRTC(dEdxTruncLow, uint8_t, 2, "", 0, "Low truncation threshold, fraction of 128") AddOptionRTC(dEdxTruncHigh, uint8_t, 77, "", 0, "High truncation threshold, fraction of 128") AddOptionRTC(extrapolationTracking, int8_t, 1, "", 0, "Enable Extrapolation Tracking(prolong tracks to adjacent sectors to find short segments)") AddOptionRTC(disableRefitAttachment, uint8_t, 0, "", 0, "Bitmask to disable certain attachment steps during refit(1:attachment, 2:propagation, 4:loop following, 8:mirroring)") AddOptionRTC(rejectionStrategy, uint8_t, o2::gpu::GPUSettings::RejectionStrategyA, "", 0, "Enable rejection of TPC clusters for compression(0=no, 1=strategy A, 2=strategy B)") AddOptionRTC(mergeLoopersAfterburner, uint8_t, 1, "", 0, "Run afterburner for additional looper merging") AddOptionRTC(compressionTypeMask, uint8_t, o2::gpu::GPUSettings::CompressionFull, "", 0, "TPC Compression mode bits(1=truncate charge/width LSB, 2=differences, 4=track-model)") AddOptionRTC(compressionSortOrder, uint8_t, o2::gpu::GPUSettings::SortTime, "", 0, "Sort order of TPC compression(0=time, 1=pad, 2=Z-time-pad, 3=Z-pad-time, 4=no sorting(use incoming order))") AddOptionRTC(sigBitsCharge, uint8_t, 4, "", 0, "Number of significant bits for TPC cluster charge in compression mode 1") AddOptionRTC(sigBitsWidth, uint8_t, 3, "", 0, "Number of significant bits for TPC cluster width in compression mode 1") AddOptionRTC(dropLoopers, uint8_t, 0, "", 0, "Drop looping tracks starting from second loop") AddOptionRTC(mergerInterpolateErrors, uint8_t, 1, "", 0, "Use interpolation instead of extrapolation for chi2 based cluster rejection") AddOptionRTC(mergerInterpolateRejectAlsoOnCurrentPosition, uint8_t, 1, "", 0, "When using mergerInterpolateErrors, reject based on chi2 twice computed with interpolated and current track position starting from NDF > mergerNonInterpolateRejectMinNDF Merge tracks accross the central electrode Enable PID response Add sub threshold clusters in TPC dEdx computation OR mask of TPC flags that will reject the cluster in alternative dEdx Reject edge clusters based on uncorrected track Y during track fit Increase decrease removal by when on this number of pads from the edge h configStandalone Reconstruction rec_trd Max chi2 for TRD tracklets to be matched to a track Chi2 cut for strict matching mode Number of sigmas for ITS TPC track time error estimate Addition to search road around track prolongation along Y in cm Optimal tracklet rphi resolution in maximum chi2 per attached tracklet for TRD tracks take into account the full likelihood of the slope including exponential tails Tracks with less attached tracklets are discarded after the tracking Post trigger Pile up integration time in BCs fits all iterations but the last one in projections Use material lookup table for TPC refit Apply workarounds to avoid cyclic merge should not be needed tpc dyn configStandalone Processing proc_rtc Replace constant variables static by constexpr expressions Compile RTC in deterministic with NO_FAST_MATH flags and GPUCA_DETERMINISTIC_MODE define Use RTC to optimize GPU code h configStandalone Processing proc_rtctech Use a file lock to serialize access to the cache folder Print launch bounds used for RTC code as debugging for exit after Allow RTC with slave GPUReconstruction instances with optConstexpr and optSpecialcode Maximum number of RTC cache files per backend in the cache the oldest are Prepend RTC compilation commands by this string Load a parameter object containing the launch bounds from a file h configStandalone Processing proc_param TPC Cluster Z Error Parameterization h configStandalone Processing settings for neural network proc_nn std::string Specify inference device(cpu(default), rocm, cuda)") AddOption(nnInferenceDeviceId
char error[CURL_ERROR_SIZE]
CURL * handle
SurfaceTrackState state
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)
Acts::Vector3 position
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 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
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
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:623
constexpr size_t min
constexpr size_t max
void * malloc(size_t n)
void free(void *p)
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