Skip to content

Commit 8dfdb27

Browse files
committed
GPU: Clean up multi-threading, fix START/STOP/START for the future, don't poll for FMQ states, and better handling to avoid blocking FMQ state changes
1 parent bfbee15 commit 8dfdb27

4 files changed

Lines changed: 63 additions & 22 deletions

File tree

GPU/Workflow/include/GPUWorkflow/GPUWorkflowSpec.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class TStopwatch;
3333
namespace fair::mq
3434
{
3535
struct RegionInfo;
36+
enum class State : int;
3637
} // namespace fair::mq
3738
namespace o2
3839
{
@@ -160,17 +161,18 @@ class GPURecoWorkflowSpec : public o2::framework::Task
160161
template <class D, class E, class F, class G, class H, class I, class J, class K>
161162
void processInputs(o2::framework::ProcessingContext&, D&, E&, F&, G&, bool&, H&, I&, J&, K&);
162163

164+
int runMain(o2::framework::ProcessingContext* pc, GPUTrackingInOutPointers* ptrs, GPUInterfaceOutputs* outputRegions, int threadIndex = 0, GPUInterfaceInputUpdate* inputUpdateCallback = nullptr);
163165
int runITSTracking(o2::framework::ProcessingContext& pc);
164166

165167
int handlePipeline(o2::framework::ProcessingContext& pc, GPUTrackingInOutPointers& ptrs, gpurecoworkflow_internals::GPURecoWorkflowSpec_TPCZSBuffers& tpcZSmeta, o2::gpu::GPUTrackingInOutZS& tpcZS, std::unique_ptr<gpurecoworkflow_internals::GPURecoWorkflow_QueueObject>& context);
166168
void RunReceiveThread();
167169
void RunWorkerThread(int id);
168-
void TerminateThreads();
170+
void ExitPipeline();
169171
void handlePipelineEndOfStream(o2::framework::EndOfStreamContext& ec);
170172
void initPipeline(o2::framework::InitContext& ic);
171173
void enqueuePipelinedJob(GPUTrackingInOutPointers* ptrs, GPUInterfaceOutputs* outputRegions, gpurecoworkflow_internals::GPURecoWorkflow_QueueObject* context, bool inputFinal);
172174
void finalizeInputPipelinedJob(GPUTrackingInOutPointers* ptrs, GPUInterfaceOutputs* outputRegions, gpurecoworkflow_internals::GPURecoWorkflow_QueueObject* context);
173-
int runMain(o2::framework::ProcessingContext* pc, GPUTrackingInOutPointers* ptrs, GPUInterfaceOutputs* outputRegions, int threadIndex = 0, GPUInterfaceInputUpdate* inputUpdateCallback = nullptr);
175+
void receiveFMQStateCallback(fair::mq::State);
174176

175177
CompletionPolicyData* mPolicyData;
176178
std::function<bool(o2::framework::DataProcessingHeader::StartTime)> mPolicyOrder;

GPU/Workflow/src/GPUWorkflowInternal.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <condition_variable>
2222
#include <queue>
2323
#include <array>
24+
#include <fairmq/States.h>
2425

2526
namespace o2::gpu
2627
{
@@ -59,7 +60,12 @@ struct GPURecoWorkflow_QueueObject {
5960
struct GPURecoWorkflowSpec_PipelineInternals {
6061
std::mutex mutexDecodeInput;
6162

62-
fair::mq::Device* fmqDevice;
63+
fair::mq::Device* fmqDevice = nullptr;
64+
65+
fair::mq::State fmqState = fair::mq::State::Undefined;
66+
bool endOfStreamReceived = false;
67+
std::mutex fmqStateMutex;
68+
std::condition_variable fmqStateCheckNotify;
6369

6470
std::thread receiveThread;
6571
std::mutex threadMutex;

GPU/Workflow/src/GPUWorkflowPipeline.cxx

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
#include <fairmq/Device.h>
3636
#include <fairmq/Channel.h>
37+
#include <fairmq/States.h>
3738

3839
using namespace o2::framework;
3940
using namespace o2::header;
@@ -45,6 +46,8 @@ using namespace o2::gpu::gpurecoworkflow_internals;
4546
namespace o2::gpu
4647
{
4748

49+
static const std::string GPURecoWorkflowSpec_FMQCallbackKey = "GPURecoWorkflowSpec_FMQCallbackKey";
50+
4851
struct pipelinePrepareMessage {
4952
static constexpr size_t MAGIC_WORD = 0X8473957353424134;
5053
size_t magicWord = MAGIC_WORD;
@@ -60,6 +63,7 @@ void GPURecoWorkflowSpec::initPipeline(o2::framework::InitContext& ic)
6063
{
6164
if (mSpecConfig.enableDoublePipeline == 1) {
6265
mPipeline->fmqDevice = ic.services().get<RawDeviceService>().device();
66+
mPipeline->fmqDevice->SubscribeToStateChange(GPURecoWorkflowSpec_FMQCallbackKey, [this](fair::mq::State s) { receiveFMQStateCallback(s); });
6367
mPolicyOrder = [this](o2::framework::DataProcessingHeader::StartTime timeslice) {
6468
std::unique_lock lk(mPipeline->completionPolicyMutex);
6569
mPipeline->completionPolicyNotify.wait(lk, [pipeline = mPipeline.get()] { return pipeline->pipelineSenderTerminating || !pipeline->completionPolicyQueue.empty(); });
@@ -233,7 +237,11 @@ int GPURecoWorkflowSpec::handlePipeline(ProcessingContext& pc, GPUTrackingInOutP
233237
void GPURecoWorkflowSpec::handlePipelineEndOfStream(EndOfStreamContext& ec)
234238
{
235239
if (mSpecConfig.enableDoublePipeline == 1) {
236-
TerminateThreads(); // TODO: Apparently breaks START / STOP / START
240+
{
241+
std::lock_guard lk(mPipeline->fmqStateMutex);
242+
mPipeline->endOfStreamReceived = true;
243+
}
244+
mPipeline->fmqStateCheckNotify.notify_all();
237245
}
238246
if (mSpecConfig.enableDoublePipeline == 2) {
239247
auto* device = ec.services().get<RawDeviceService>().device();
@@ -247,18 +255,37 @@ void GPURecoWorkflowSpec::handlePipelineEndOfStream(EndOfStreamContext& ec)
247255
}
248256
}
249257

258+
void GPURecoWorkflowSpec::receiveFMQStateCallback(fair::mq::State newState)
259+
{
260+
{
261+
std::lock_guard lk(mPipeline->fmqStateMutex);
262+
if (mPipeline->fmqState != fair::mq::State::Running && newState == fair::mq::State::Running) {
263+
mPipeline->endOfStreamReceived = false;
264+
}
265+
mPipeline->fmqState = newState;
266+
if (newState == fair::mq::State::Exiting) {
267+
mPipeline->fmqDevice->UnsubscribeFromStateChange(GPURecoWorkflowSpec_FMQCallbackKey);
268+
}
269+
}
270+
mPipeline->fmqStateCheckNotify.notify_all();
271+
}
272+
250273
void GPURecoWorkflowSpec::RunReceiveThread()
251274
{
252275
auto* device = mPipeline->fmqDevice;
253-
while (device->GetCurrentState() != fair::mq::State::Running) {
254-
usleep(300000);
255-
}
256276
while (!mPipeline->shouldTerminate) {
257277
bool received = false;
258278
int recvTimeot = 1000;
259279
fair::mq::MessagePtr msg;
260280
LOG(debug) << "Waiting for out of band message";
261281
do {
282+
{
283+
std::unique_lock lk(mPipeline->fmqStateMutex);
284+
mPipeline->fmqStateCheckNotify.wait(lk, [this]() { return (mPipeline->fmqState == fair::mq::State::Running && !mPipeline->endOfStreamReceived) || mPipeline->shouldTerminate; }); // Do not check mPipeline->fmqDevice->NewStatePending() since we wait for EndOfStream!
285+
}
286+
if (mPipeline->shouldTerminate) {
287+
break;
288+
}
262289
try {
263290
msg = device->NewMessageFor("gpu-prepare-channel", 0, 0);
264291
do {
@@ -279,8 +306,10 @@ void GPURecoWorkflowSpec::RunReceiveThread()
279306
LOG(fatal) << "Prepare message corrupted, invalid magic word";
280307
}
281308
if (m->flagEndOfStream) {
282-
LOG(info) << "Received end-of-stream from out-of-band channel, terminating receive thread"; // TODO: Breaks START / STOP / START
283-
break;
309+
LOG(info) << "Received end-of-stream from out-of-band channel";
310+
mPipeline->endOfStreamReceived = true;
311+
mPipeline->mNTFReceived = 0;
312+
continue;
284313
}
285314

286315
auto context = std::make_unique<GPURecoWorkflow_QueueObject>();
@@ -317,7 +346,7 @@ void GPURecoWorkflowSpec::RunReceiveThread()
317346
context->ptrs.tpcZS = &context->tpcZS;
318347
context->ptrs.settingsTF = &context->tfSettings;
319348
context->mTFId = mPipeline->mNTFReceived;
320-
if (mPipeline->mNTFReceived++ >= mPipeline->workers.size()) { // Do not inject the first 2 TF, since we need a first round of calib updates from DPL before starting
349+
if (mPipeline->mNTFReceived++ >= mPipeline->workers.size()) { // Do not inject the first workers.size() TFs, since we need a first round of calib updates from DPL before starting
321350
enqueuePipelinedJob(&context->ptrs, nullptr, context.get(), false);
322351
}
323352
{
@@ -338,18 +367,22 @@ void GPURecoWorkflowSpec::RunReceiveThread()
338367
mPipeline->completionPolicyNotify.notify_one();
339368
}
340369

341-
void GPURecoWorkflowSpec::TerminateThreads()
370+
void GPURecoWorkflowSpec::ExitPipeline()
342371
{
343-
mPipeline->shouldTerminate = true;
344-
for (unsigned int i = 0; i < mPipeline->workers.size(); i++) {
345-
mPipeline->workers[i].inputQueueNotify.notify_one();
346-
}
347-
if (mPipeline->receiveThread.joinable()) {
348-
mPipeline->receiveThread.join();
349-
}
350-
for (unsigned int i = 0; i < mPipeline->workers.size(); i++) {
351-
if (mPipeline->workers[i].thread.joinable()) {
352-
mPipeline->workers[i].thread.join();
372+
if (mSpecConfig.enableDoublePipeline == 1 && mPipeline->fmqDevice) {
373+
mPipeline->fmqDevice = nullptr;
374+
mPipeline->shouldTerminate = true;
375+
mPipeline->fmqStateCheckNotify.notify_all();
376+
for (unsigned int i = 0; i < mPipeline->workers.size(); i++) {
377+
mPipeline->workers[i].inputQueueNotify.notify_one();
378+
}
379+
if (mPipeline->receiveThread.joinable()) {
380+
mPipeline->receiveThread.join();
381+
}
382+
for (unsigned int i = 0; i < mPipeline->workers.size(); i++) {
383+
if (mPipeline->workers[i].thread.joinable()) {
384+
mPipeline->workers[i].thread.join();
385+
}
353386
}
354387
}
355388
}

GPU/Workflow/src/GPUWorkflowSpec.cxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1211,7 +1211,7 @@ Outputs GPURecoWorkflowSpec::outputs()
12111211

12121212
void GPURecoWorkflowSpec::deinitialize()
12131213
{
1214-
TerminateThreads();
1214+
ExitPipeline();
12151215
mQA.reset(nullptr);
12161216
mDisplayFrontend.reset(nullptr);
12171217
mGPUReco.reset(nullptr);

0 commit comments

Comments
 (0)