Skip to content

Commit 2a45b16

Browse files
committed
DPL: AsyncQueue without std::function
1 parent bc67e6d commit 2a45b16

File tree

5 files changed

+236
-126
lines changed

5 files changed

+236
-126
lines changed

Framework/Core/include/Framework/AsyncQueue.h

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,49 +12,91 @@
1212
#define O2_FRAMEWORK_ASYNCQUUE_H_
1313

1414
#include "Framework/TimesliceSlot.h"
15-
#include <functional>
1615
#include <string>
1716
#include <vector>
1817

1918
namespace o2::framework
2019
{
2120

22-
struct AsyncTaskSpec {
23-
std::string name;
24-
// Its priority compared to the other tasks
25-
int score = 0;
26-
};
27-
2821
/// The position of the TaskSpec in the prototypes
22+
/// Up to 127 different kind of tasks are allowed per queue
2923
struct AsyncTaskId {
30-
int value = -1;
24+
int16_t value = -1;
3125
};
3226

3327
/// An actuatual task to be executed
3428
struct AsyncTask {
35-
// The task to be executed. Id can be used as unique
36-
// id for the signpost in the async_queue stream.
37-
std::function<void(size_t id)> task;
38-
// The associated task spec
39-
AsyncTaskId id = {-1};
29+
// The timeslice after which this callback should be executed.
4030
TimesliceId timeslice = {TimesliceId::INVALID};
4131
// Only the task with the highest debounce value will be executed
42-
int debounce = 0;
32+
// The associated task spec. Notice that the context
33+
// is stored separately so that we do not need to
34+
// manage lifetimes of std::functions
35+
AsyncTaskId id = {-1};
36+
// Debounce value.
37+
int8_t debounce = 0;
4338
bool runnable = false;
39+
// Some unuser integer
40+
int32_t unused = 0;
41+
// The callback to be executed. id can be used as unique
42+
// id for the signpost in the async_queue stream.
43+
// Context is provided by the userdata attached to the
44+
// task the moment we post it.
45+
// Notice that we do not store the task in the prototype
46+
// because we do support at the moment coalescing two
47+
// different callbaks with the same id.
48+
void (*callback)(AsyncTask& task, size_t id);
49+
// Some user data e.g. to decode what comes next
50+
// This can either be used via the .data pointer
51+
// or by asking a cast to the appropriate type via
52+
// the user() method.
53+
void* data[5] = {nullptr, nullptr, nullptr, nullptr, nullptr};
54+
55+
// Helper to return userdata
56+
template <typename T>
57+
T& user()
58+
{
59+
static_assert(sizeof(T) <= 5 * sizeof(void*), "User object does not fit user data");
60+
return *(T*)data;
61+
}
62+
63+
// Helper to set userdata
64+
// @return a reference to this task modified. This is meant to be used like:
65+
//
66+
// AsyncQueueHelpers::post(queue, AsyncTask{.id = myTask}.user(Context{.contextValue}));
67+
//
68+
// Coupled with the other one:
69+
//
70+
// task.user<Context>().contextValue;
71+
//
72+
// it can be used to mimick capturing lambdas
73+
template <typename T>
74+
AsyncTask& user(T&& value)
75+
{
76+
static_assert(sizeof(T) <= 5 * sizeof(void*), "User object does not fit user data");
77+
new (&data[0])(T){value};
78+
return *this;
79+
}
80+
};
81+
82+
struct AsyncTaskSpec {
83+
std::string name;
84+
// Its priority compared to the other tasks
85+
int score = 0;
4486
};
4587

4688
struct AsyncQueue {
4789
std::vector<AsyncTaskSpec> prototypes;
4890
std::vector<AsyncTask> tasks;
4991
size_t iteration = 0;
92+
AsyncQueue();
5093
};
5194

5295
struct AsyncQueueHelpers {
53-
using AsyncCallback = std::function<void(size_t)>;
5496
static AsyncTaskId create(AsyncQueue& queue, AsyncTaskSpec spec);
5597
// Schedule a task with @a taskId to be executed whenever the timeslice
5698
// is past timeslice. If debounce is provided, only execute the task
57-
static void post(AsyncQueue& queue, AsyncTaskId taskId, AsyncCallback task, TimesliceId timeslice, int64_t debounce = 0);
99+
static void post(AsyncQueue& queue, AsyncTask const& task);
58100
/// Run all the tasks which are older than the oldestPossible timeslice
59101
/// executing them by:
60102
/// 1. sorting the tasks by timeslice

Framework/Core/src/AsyncQueue.cxx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ O2_DECLARE_DYNAMIC_LOG(async_queue);
1717

1818
namespace o2::framework
1919
{
20+
AsyncQueue::AsyncQueue()
21+
{
22+
}
23+
2024
auto AsyncQueueHelpers::create(AsyncQueue& queue, AsyncTaskSpec spec) -> AsyncTaskId
2125
{
2226
AsyncTaskId id;
@@ -25,14 +29,9 @@ auto AsyncQueueHelpers::create(AsyncQueue& queue, AsyncTaskSpec spec) -> AsyncTa
2529
return id;
2630
}
2731

28-
auto AsyncQueueHelpers::post(AsyncQueue& queue, AsyncTaskId id, AsyncCallback task, TimesliceId timeslice, int64_t debounce) -> void
32+
auto AsyncQueueHelpers::post(AsyncQueue& queue, AsyncTask const& task) -> void
2933
{
30-
AsyncTask taskToPost;
31-
taskToPost.task = task;
32-
taskToPost.id = id;
33-
taskToPost.timeslice = timeslice;
34-
taskToPost.debounce = debounce;
35-
queue.tasks.push_back(taskToPost);
34+
queue.tasks.push_back(task);
3635
}
3736

3837
auto AsyncQueueHelpers::run(AsyncQueue& queue, TimesliceId oldestPossible) -> void
@@ -85,6 +84,8 @@ auto AsyncQueueHelpers::run(AsyncQueue& queue, TimesliceId oldestPossible) -> vo
8584
}
8685
}
8786
// Keep only the tasks with the highest debounce value for a given id
87+
// For this reason I need to keep the callback in the task itself, because
88+
// two different callbacks with the same id will be coalesced.
8889
auto newEnd = std::unique(order.begin(), order.end(), [&queue](int a, int b) {
8990
return queue.tasks[a].runnable == queue.tasks[b].runnable && queue.tasks[a].id.value == queue.tasks[b].id.value && queue.tasks[a].debounce >= 0 && queue.tasks[b].debounce >= 0;
9091
});
@@ -111,7 +112,7 @@ auto AsyncQueueHelpers::run(AsyncQueue& queue, TimesliceId oldestPossible) -> vo
111112
O2_SIGNPOST_EVENT_EMIT(async_queue, opid, "run", "Running task %{public}s (%d) for timeslice %zu",
112113
queue.prototypes[queue.tasks[i].id.value].name.c_str(), i,
113114
queue.tasks[i].timeslice.value);
114-
queue.tasks[i].task(opid.value);
115+
queue.tasks[i].callback(queue.tasks[i], opid.value);
115116
O2_SIGNPOST_EVENT_EMIT(async_queue, opid, "run", "Done running %d", i);
116117
}
117118
}

Framework/Core/src/CommonServices.cxx

Lines changed: 70 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -529,12 +529,72 @@ o2::framework::ServiceSpec CommonServices::ccdbSupportSpec()
529529
} },
530530
.kind = ServiceKind::Global};
531531
}
532+
struct DecongestionContext {
533+
ServiceRegistryRef ref;
534+
TimesliceIndex::OldestOutputInfo oldestPossibleOutput;
535+
};
536+
537+
auto decongestionCallback = [](AsyncTask& task, size_t id) -> void {
538+
auto& oldestPossibleOutput = task.user<DecongestionContext>().oldestPossibleOutput;
539+
auto& ref = task.user<DecongestionContext>().ref;
540+
541+
auto& decongestion = ref.get<DecongestionService>();
542+
auto& proxy = ref.get<FairMQDeviceProxy>();
543+
544+
O2_SIGNPOST_ID_GENERATE(cid, async_queue);
545+
cid.value = id;
546+
if (decongestion.lastTimeslice >= oldestPossibleOutput.timeslice.value) {
547+
O2_SIGNPOST_EVENT_EMIT(async_queue, cid, "oldest_possible_timeslice", "Not sending already sent value: %" PRIu64 "> %" PRIu64,
548+
decongestion.lastTimeslice, (uint64_t)oldestPossibleOutput.timeslice.value);
549+
return;
550+
}
551+
O2_SIGNPOST_EVENT_EMIT(async_queue, cid, "oldest_possible_timeslice", "Running oldest possible timeslice %" PRIu64 " propagation.",
552+
(uint64_t)oldestPossibleOutput.timeslice.value);
553+
DataProcessingHelpers::broadcastOldestPossibleTimeslice(ref, oldestPossibleOutput.timeslice.value);
554+
555+
for (int fi = 0; fi < proxy.getNumForwardChannels(); fi++) {
556+
auto& info = proxy.getForwardChannelInfo(ChannelIndex{fi});
557+
auto& state = proxy.getForwardChannelState(ChannelIndex{fi});
558+
// TODO: this we could cache in the proxy at the bind moment.
559+
if (info.channelType != ChannelAccountingType::DPL) {
560+
O2_SIGNPOST_EVENT_EMIT(async_queue, cid, "oldest_possible_timeslice", "Skipping channel %{public}s", info.name.c_str());
561+
continue;
562+
}
563+
if (DataProcessingHelpers::sendOldestPossibleTimeframe(ref, info, state, oldestPossibleOutput.timeslice.value)) {
564+
O2_SIGNPOST_EVENT_EMIT(async_queue, cid, "oldest_possible_timeslice",
565+
"Forwarding to channel %{public}s oldest possible timeslice %" PRIu64 ", priority %d",
566+
info.name.c_str(), (uint64_t)oldestPossibleOutput.timeslice.value, 20);
567+
}
568+
}
569+
decongestion.lastTimeslice = oldestPossibleOutput.timeslice.value;
570+
};
571+
572+
auto decongestionCallbackOrdered = [](AsyncTask& task, size_t id) -> void {
573+
auto& oldestPossibleOutput = task.user<DecongestionContext>().oldestPossibleOutput;
574+
auto& ref = task.user<DecongestionContext>().ref;
575+
576+
auto& decongestion = ref.get<DecongestionService>();
577+
auto& state = ref.get<DeviceState>();
578+
auto& timesliceIndex = ref.get<TimesliceIndex>();
579+
O2_SIGNPOST_ID_GENERATE(cid, async_queue);
580+
int64_t oldNextTimeslice = decongestion.nextTimeslice;
581+
decongestion.nextTimeslice = std::max(decongestion.nextTimeslice, (int64_t)oldestPossibleOutput.timeslice.value);
582+
if (oldNextTimeslice != decongestion.nextTimeslice) {
583+
if (state.transitionHandling != TransitionHandlingState::NoTransition && DefaultsHelpers::onlineDeploymentMode()) {
584+
O2_SIGNPOST_EVENT_EMIT_WARN(async_queue, cid, "oldest_possible_timeslice", "Stop transition requested. Some Lifetime::Timeframe data got dropped starting at %" PRIi64, oldNextTimeslice);
585+
} else {
586+
O2_SIGNPOST_EVENT_EMIT_ERROR(async_queue, cid, "oldest_possible_timeslice", "Some Lifetime::Timeframe data got dropped starting at %" PRIi64, oldNextTimeslice);
587+
}
588+
timesliceIndex.rescan();
589+
}
590+
};
532591

533592
// Decongestion service
534593
// If we do not have any Timeframe input, it means we must be creating timeslices
535594
// in order and that we should propagate the oldest possible timeslice at the end
536595
// of each processing step.
537-
o2::framework::ServiceSpec CommonServices::decongestionSpec()
596+
o2::framework::ServiceSpec
597+
CommonServices::decongestionSpec()
538598
{
539599
return ServiceSpec{
540600
.name = "decongestion",
@@ -548,7 +608,7 @@ o2::framework::ServiceSpec CommonServices::decongestionSpec()
548608
}
549609
}
550610
auto& queue = services.get<AsyncQueue>();
551-
decongestion->oldestPossibleTimesliceTask = AsyncQueueHelpers::create(queue, {"oldest-possible-timeslice", 100});
611+
decongestion->oldestPossibleTimesliceTask = AsyncQueueHelpers::create(queue, {.name = "oldest-possible-timeslice", .score = 100});
552612
return ServiceHandle{TypeIdHelpers::uniqueId<DecongestionService>(), decongestion, ServiceKind::Serial};
553613
},
554614
.postForwarding = [](ProcessingContext& ctx, void* service) {
@@ -632,7 +692,6 @@ o2::framework::ServiceSpec CommonServices::decongestionSpec()
632692
auto& decongestion = services.get<DecongestionService>();
633693
auto& relayer = services.get<DataRelayer>();
634694
auto& timesliceIndex = services.get<TimesliceIndex>();
635-
auto& proxy = services.get<FairMQDeviceProxy>();
636695
O2_SIGNPOST_ID_FROM_POINTER(cid, data_processor_context, &decongestion);
637696
O2_SIGNPOST_EVENT_EMIT(data_processor_context, cid, "oldest_possible_timeslice", "Received oldest possible timeframe %" PRIu64 " from channel %d",
638697
(uint64_t)oldestPossibleTimeslice, channel.value);
@@ -650,59 +709,22 @@ o2::framework::ServiceSpec CommonServices::decongestionSpec()
650709
return;
651710
}
652711
auto& queue = services.get<AsyncQueue>();
653-
const auto& spec = services.get<DeviceSpec const>();
654712
const auto& state = services.get<DeviceState>();
655-
auto *device = services.get<RawDeviceService>().device();
656713
/// We use the oldest possible timeslice to debounce, so that only the latest one
657714
/// at the end of one iteration is sent.
658715
O2_SIGNPOST_EVENT_EMIT(data_processor_context, cid, "oldest_possible_timeslice", "Queueing oldest possible timeslice %" PRIu64 " propagation for execution.",
659716
(uint64_t)oldestPossibleOutput.timeslice.value);
660717
AsyncQueueHelpers::post(
661-
queue, decongestion.oldestPossibleTimesliceTask, [ref = services, oldestPossibleOutput, &decongestion, &proxy, &spec, device, &timesliceIndex](size_t id) {
662-
O2_SIGNPOST_ID_GENERATE(cid, async_queue);
663-
cid.value = id;
664-
if (decongestion.lastTimeslice >= oldestPossibleOutput.timeslice.value) {
665-
O2_SIGNPOST_EVENT_EMIT(async_queue, cid, "oldest_possible_timeslice", "Not sending already sent value: %" PRIu64 "> %" PRIu64,
666-
decongestion.lastTimeslice, (uint64_t)oldestPossibleOutput.timeslice.value);
667-
return;
668-
}
669-
O2_SIGNPOST_EVENT_EMIT(async_queue, cid, "oldest_possible_timeslice", "Running oldest possible timeslice %" PRIu64 " propagation.",
670-
(uint64_t)oldestPossibleOutput.timeslice.value);
671-
DataProcessingHelpers::broadcastOldestPossibleTimeslice(ref, oldestPossibleOutput.timeslice.value);
672-
673-
for (int fi = 0; fi < proxy.getNumForwardChannels(); fi++) {
674-
auto& info = proxy.getForwardChannelInfo(ChannelIndex{fi});
675-
auto& state = proxy.getForwardChannelState(ChannelIndex{fi});
676-
// TODO: this we could cache in the proxy at the bind moment.
677-
if (info.channelType != ChannelAccountingType::DPL) {
678-
O2_SIGNPOST_EVENT_EMIT(async_queue, cid, "oldest_possible_timeslice", "Skipping channel %{public}s", info.name.c_str());
679-
continue;
680-
}
681-
if (DataProcessingHelpers::sendOldestPossibleTimeframe(ref, info, state, oldestPossibleOutput.timeslice.value)) {
682-
O2_SIGNPOST_EVENT_EMIT(async_queue, cid, "oldest_possible_timeslice",
683-
"Forwarding to channel %{public}s oldest possible timeslice %" PRIu64 ", priority %d",
684-
info.name.c_str(), (uint64_t)oldestPossibleOutput.timeslice.value, 20);
685-
}
686-
}
687-
decongestion.lastTimeslice = oldestPossibleOutput.timeslice.value;
688-
},
689-
TimesliceId{oldestPossibleTimeslice}, -1);
718+
queue, AsyncTask{ .timeslice = TimesliceId{oldestPossibleTimeslice},
719+
.id = decongestion.oldestPossibleTimesliceTask,
720+
.debounce = -1, .callback = decongestionCallback}
721+
.user<DecongestionContext>(DecongestionContext{.ref = services, .oldestPossibleOutput = oldestPossibleOutput}));
722+
690723
if (decongestion.orderedCompletionPolicyActive) {
691724
AsyncQueueHelpers::post(
692-
queue, decongestion.oldestPossibleTimesliceTask, [ref = services, oldestPossibleOutput, &decongestion, &proxy, &spec, &state, device, &timesliceIndex](size_t id) {
693-
O2_SIGNPOST_ID_GENERATE(cid, async_queue);
694-
int64_t oldNextTimeslice = decongestion.nextTimeslice;
695-
decongestion.nextTimeslice = std::max(decongestion.nextTimeslice, (int64_t)oldestPossibleOutput.timeslice.value);
696-
if (oldNextTimeslice != decongestion.nextTimeslice) {
697-
if (state.transitionHandling != TransitionHandlingState::NoTransition && DefaultsHelpers::onlineDeploymentMode()) {
698-
O2_SIGNPOST_EVENT_EMIT_WARN(async_queue, cid, "oldest_possible_timeslice", "Stop transition requested. Some Lifetime::Timeframe data got dropped starting at %" PRIi64, oldNextTimeslice);
699-
} else {
700-
O2_SIGNPOST_EVENT_EMIT_ERROR(async_queue, cid, "oldest_possible_timeslice", "Some Lifetime::Timeframe data got dropped starting at %" PRIi64, oldNextTimeslice);
701-
}
702-
timesliceIndex.rescan();
703-
}
704-
},
705-
TimesliceId{oldestPossibleOutput.timeslice.value}, -1);
725+
queue, AsyncTask{.timeslice = TimesliceId{oldestPossibleOutput.timeslice.value},.id = decongestion.oldestPossibleTimesliceTask, .debounce = -1,
726+
.callback = decongestionCallbackOrdered}
727+
.user<DecongestionContext>({.ref = services, .oldestPossibleOutput = oldestPossibleOutput}));
706728
} },
707729
.kind = ServiceKind::Serial};
708730
}

Framework/Core/src/DataProcessingDevice.cxx

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,39 @@ static auto toBeforwardedMessageSet = [](std::vector<ChannelIndex>& cachedForwar
636636
return cachedForwardingChoices.empty() == false;
637637
};
638638

639+
struct DecongestionContext {
640+
ServiceRegistryRef ref;
641+
TimesliceIndex::OldestOutputInfo oldestTimeslice;
642+
};
643+
644+
auto decongestionCallbackLate = [](AsyncTask& task, size_t aid) -> void {
645+
auto& oldestTimeslice = task.user<DecongestionContext>().oldestTimeslice;
646+
auto& ref = task.user<DecongestionContext>().ref;
647+
648+
auto& decongestion = ref.get<DecongestionService>();
649+
auto& proxy = ref.get<FairMQDeviceProxy>();
650+
if (oldestTimeslice.timeslice.value <= decongestion.lastTimeslice) {
651+
LOG(debug) << "Not sending already sent oldest possible timeslice " << oldestTimeslice.timeslice.value;
652+
return;
653+
}
654+
for (int fi = 0; fi < proxy.getNumForwardChannels(); fi++) {
655+
auto& info = proxy.getForwardChannelInfo(ChannelIndex{fi});
656+
auto& state = proxy.getForwardChannelState(ChannelIndex{fi});
657+
O2_SIGNPOST_ID_GENERATE(aid, async_queue);
658+
// TODO: this we could cache in the proxy at the bind moment.
659+
if (info.channelType != ChannelAccountingType::DPL) {
660+
O2_SIGNPOST_EVENT_EMIT(async_queue, aid, "forwardInputsCallback", "Skipping channel %{public}s because it's not a DPL channel",
661+
info.name.c_str());
662+
663+
continue;
664+
}
665+
if (DataProcessingHelpers::sendOldestPossibleTimeframe(ref, info, state, oldestTimeslice.timeslice.value)) {
666+
O2_SIGNPOST_EVENT_EMIT(async_queue, aid, "forwardInputsCallback", "Forwarding to channel %{public}s oldest possible timeslice %zu, prio 20",
667+
info.name.c_str(), oldestTimeslice.timeslice.value);
668+
}
669+
}
670+
};
671+
639672
// This is how we do the forwarding, i.e. we push
640673
// the inputs which are shared between this device and others
641674
// to the next one in the daisy chain.
@@ -721,36 +754,13 @@ static auto forwardInputs = [](ServiceRegistryRef registry, TimesliceSlot slot,
721754

722755
auto& asyncQueue = registry.get<AsyncQueue>();
723756
auto& decongestion = registry.get<DecongestionService>();
724-
auto& timesliceIndex = registry.get<TimesliceIndex>();
725757
O2_SIGNPOST_ID_GENERATE(aid, async_queue);
726758
O2_SIGNPOST_EVENT_EMIT(async_queue, aid, "forwardInputs", "Queuing forwarding oldestPossible %zu", oldestTimeslice.timeslice.value);
727-
AsyncQueueHelpers::post(
728-
asyncQueue, decongestion.oldestPossibleTimesliceTask, [&proxy, &decongestion, registry, oldestTimeslice, &timesliceIndex](size_t aid) {
729-
// DataProcessingHelpers::broadcastOldestPossibleTimeslice(proxy, oldestTimeslice.timeslice.value);
730-
if (oldestTimeslice.timeslice.value <= decongestion.lastTimeslice) {
731-
LOG(debug) << "Not sending already sent oldest possible timeslice " << oldestTimeslice.timeslice.value;
732-
return;
733-
}
734-
for (int fi = 0; fi < proxy.getNumForwardChannels(); fi++) {
735-
auto& info = proxy.getForwardChannelInfo(ChannelIndex{fi});
736-
auto& state = proxy.getForwardChannelState(ChannelIndex{fi});
737-
O2_SIGNPOST_ID_GENERATE(aid, async_queue);
738-
// TODO: this we could cache in the proxy at the bind moment.
739-
if (info.channelType != ChannelAccountingType::DPL) {
740-
O2_SIGNPOST_EVENT_EMIT(async_queue, aid, "forwardInputsCallback", "Skipping channel %{public}s because it's not a DPL channel",
741-
info.name.c_str());
742-
743-
continue;
744-
}
745-
if (DataProcessingHelpers::sendOldestPossibleTimeframe(registry, info, state, oldestTimeslice.timeslice.value)) {
746-
O2_SIGNPOST_EVENT_EMIT(async_queue, aid, "forwardInputsCallback", "Forwarding to channel %{public}s oldest possible timeslice %zu, prio 20",
747-
info.name.c_str(), oldestTimeslice.timeslice.value);
748-
}
749-
}
750-
},
751-
oldestTimeslice.timeslice, -1);
759+
AsyncQueueHelpers::post(asyncQueue, AsyncTask{.timeslice = oldestTimeslice.timeslice, .id = decongestion.oldestPossibleTimesliceTask, .debounce = -1, .callback = decongestionCallbackLate}
760+
.user<DecongestionContext>({.ref = registry, .oldestTimeslice = oldestTimeslice}));
752761
O2_SIGNPOST_END(forwarding, sid, "forwardInputs", "Forwarding done");
753762
};
763+
754764
extern volatile int region_read_global_dummy_variable;
755765
volatile int region_read_global_dummy_variable;
756766

0 commit comments

Comments
 (0)