Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Detectors/TPC/workflow/src/ClusterDecoderRawSpec.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ DataProcessorSpec getClusterDecoderRawSpec(bool sendMC)
// containers are created for clusters and MC labels per (sector,globalPadRow) address
char* outputBuffer = nullptr;
auto outputAllocator = [&pc, &fanSpec, &outputBuffer, &rawHeaderStack](size_t size) -> char* {
outputBuffer = pc.outputs().newChunk(Output{ gDataOriginTPC, DataDescription("CLUSTERNATIVE"), fanSpec, Lifetime::Timeframe, std::move(rawHeaderStack) }, size).data;
outputBuffer = pc.outputs().newChunk(Output{ gDataOriginTPC, DataDescription("CLUSTERNATIVE"), fanSpec, Lifetime::Timeframe, std::move(rawHeaderStack) }, size).data();
return outputBuffer;
};
std::vector<MCLabelContainer> mcoutList;
Expand Down
10 changes: 5 additions & 5 deletions Framework/Core/include/Framework/DataAllocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ class DataAllocator
ContextRegistry* contextes,
const AllowedOutputRoutes& routes);

DataChunk newChunk(const Output&, size_t);
DataChunk& newChunk(const Output&, size_t);

inline DataChunk newChunk(OutputRef&& ref, size_t size) { return newChunk(getOutputByBind(std::move(ref)), size); }
inline DataChunk& newChunk(OutputRef&& ref, size_t size) { return newChunk(getOutputByBind(std::move(ref)), size); }

DataChunk adoptChunk(const Output&, char*, size_t, fairmq_free_fn*, void*);
void adoptChunk(const Output&, char*, size_t, fairmq_free_fn*, void*);

// In case no extra argument is provided and the passed type is trivially
// copyable and non polymorphic, the most likely wanted behavior is to create
Expand All @@ -91,12 +91,12 @@ class DataAllocator
typename std::enable_if<is_messageable<T>::value == true, T&>::type
make(const Output& spec)
{
DataChunk chunk = newChunk(spec, sizeof(T));
return *reinterpret_cast<T*>(chunk.data);
return *reinterpret_cast<T*>(newChunk(spec, sizeof(T)).data());
}

// In case an extra argument is provided, we consider this an array /
// collection elements of that type
// FIXME: once the vector functionality with polymorphic allocator is fully in place, this might be dropped
template <typename T>
typename std::enable_if<is_messageable<T>::value == true, gsl::span<T>&>::type
make(const Output& spec, size_t nElements)
Expand Down
31 changes: 25 additions & 6 deletions Framework/Core/include/Framework/DataChunk.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,38 @@
#ifndef FRAMEWORK_DATACHUNK_H
#define FRAMEWORK_DATACHUNK_H

#include "MemoryResources/MemoryResources.h"

namespace o2
{
namespace framework
{
/// @class DataChunk A resizable buffer used with DPL's DataAllocator
/// DataChunk derives from std::vector with polymorphic allocator and forbids copying, the underlying
/// buffer is of type char and is through DPL and polymorphic memory resource directly allocated in the
/// message memory.
/// Since MessageContext returns the object by reference, the forbidden copy and assignment makes sure that
/// the code can not accidentally use a copy instead reference.
class DataChunk : public std::vector<char, o2::pmr::polymorphic_allocator<char>>
{
public:
// FIXME: want to have a general forwarding, but then the copy constructor is not deleted any more despite
// it's declared deleted
//template <typename... Args>
//DataChunk(T&& arg, Args&&... args) : std::vector<char, o2::pmr::polymorphic_allocator<char>>(std::forward<Args>(args)...)
//{
//}

/// Simple struct to hold a pointer to the actual FairMQMessage.
/// In principle this could be an iovec...
struct DataChunk {
char *data;
size_t size;
// DataChunk is special and for the moment it's enough to declare the constructor with size and allocator
DataChunk(size_t size, const o2::pmr::polymorphic_allocator<char>& allocator) : std::vector<char, o2::pmr::polymorphic_allocator<char>>(size, allocator)
{
}
DataChunk(const DataChunk&) = delete;
DataChunk& operator=(const DataChunk&) = delete;
DataChunk(DataChunk&&) = default;
DataChunk& operator=(DataChunk&&) = default;
};

} // namespace framework
} // namespace o2

#endif // FRAMEWORK_DATACHUNK_H
154 changes: 138 additions & 16 deletions Framework/Core/include/Framework/MessageContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

#include "Framework/FairMQDeviceProxy.h"
#include "Framework/TypeTraits.h"
#include "MemoryResources/MemoryResources.h"
#include "Headers/DataHeader.h"

#include <fairmq/FairMQMessage.h>
#include <fairmq/FairMQParts.h>
Expand All @@ -20,6 +22,7 @@
#include <cassert>
#include <string>
#include <type_traits>
#include <stdexcept>

class FairMQDevice;

Expand All @@ -35,25 +38,56 @@ class MessageContext {
{
}

// thhis is the virtual interface for context objects
// this is the virtual interface for context objects
class ContextObject
{
public:
ContextObject() = default;
ContextObject(FairMQMessagePtr&& headerMsg, FairMQMessagePtr&& payloadMsg, const std::string& bindingChannel)
: parts{}, channel{ bindingChannel }
: mParts{}, mChannel{ bindingChannel }
{
parts.AddPart(std::move(headerMsg));
parts.AddPart(std::move(payloadMsg));
mParts.AddPart(std::move(headerMsg));
mParts.AddPart(std::move(payloadMsg));
}
ContextObject(FairMQMessagePtr&& headerMsg, const std::string& bindingChannel)
: mParts{}, mChannel{ bindingChannel }
{
mParts.AddPart(std::move(headerMsg));
}
virtual ~ContextObject() = default;

// TODO: we keep this public for the moment to keep the current interface
// the send-handler loops over all objects and can acces the message parts
// directly, needs to be changed when the context is generalized, then the
// information needs to be stored in the derived class
FairMQParts parts;
std::string channel;
/// @brief Finalize the object and return the parts by move
/// This is the default method and can be overloaded by other implmentations to carry out other
/// tasks before returning the parts objects
virtual FairMQParts finalize()
{
FairMQParts parts = std::move(mParts);
assert(parts.Size() == 2);
auto* header = o2::header::get<o2::header::DataHeader*>(parts.At(0)->GetData());
if (header == nullptr) {
throw std::logic_error("No valid header message found");
} else {
// o2::header::get returns const pointer, but here we can change the message
const_cast<o2::header::DataHeader*>(header)->payloadSize = parts.At(1)->GetSize();
}
// return value optimization returns by move
return parts;
}

/// @brief return the channel name
const std::string& channel() const
{
return mChannel;
}

bool empty() const
{
return mParts.Size() == 0;
}

protected:
FairMQParts mParts;
std::string mChannel;
};

/// TrivialObject handles a message object
Expand All @@ -78,13 +112,101 @@ class MessageContext {

auto* data()
{
assert(parts.Size() == 2);
return parts[1].GetData();
assert(mParts.Size() == 2);
return mParts[1].GetData();
}
};

/// ContainerRefObject handles a message object holding an instance of type T
/// The allocator type is required to be o2::pmr::polymorphic_allocator
/// can not adopt an existing message, because the polymorphic_allocator will call type constructor,
/// so this works only with new messages
/// FIXME: not sure if we want to have this for all container types
template <typename T>
class ContainerRefObject : public ContextObject
{
public:
using value_type = typename T::value_type;
using return_type = T;
using buffer_type = return_type;
static_assert(std::is_same<typename T::allocator_type, o2::pmr::polymorphic_allocator<value_type>>::value, "container must have polymorphic allocator");
/// default contructor forbidden, object always has to control message instances
ContainerRefObject() = delete;
/// constructor taking header message by move and creating the paypload message
template <typename ContextType>
ContainerRefObject(ContextType* context, FairMQMessagePtr&& headerMsg, const std::string& bindingChannel, int index, size_t size)
: ContextObject(std::forward<FairMQMessagePtr>(headerMsg), bindingChannel),
// backup of initially allocated size
mAllocatedSize{ size * sizeof(value_type) },
// the transport factory
mFactory{ context->proxy().getTransport(bindingChannel, index) },
// the memory resource takes ownership of the message
mResource{ mFactory ? mFactory->GetMemoryResource() : nullptr },
// create the vector with apropriate underlying memory resource for the message
mData{ size, pmr::polymorphic_allocator<value_type>(mResource) }
{
// FIXME: drop this repeated check and make sure at initial setup of devices that everything is fine
// introduce error policy
if (mFactory == nullptr) {
throw std::runtime_error(std::string("failed to get transport factory for channel ") + bindingChannel);
}
if (mResource == nullptr) {
throw std::runtime_error(std::string("no memory resource for channel ") + bindingChannel);
}
}
~ContainerRefObject() override = default;

/// @brief Finalize object and return parts by move
/// This retrieves the actual message from the vector object and moves it to the parts
FairMQParts finalize() final
{
assert(mParts.Size() == 1);
auto payloadMsg = o2::pmr::getMessage(std::move(mData));
mParts.AddPart(std::move(payloadMsg));
return ContextObject::finalize();
}

/// @brief return reference to the handled vector object
operator return_type&()
{
return mData;
}

/// @brief return reference to the handled vector object
return_type& get()
{
return mData;
}

/// @brief return data pointer of the handled vector object
value_type* data()
{
return mData.data();
}

private:
size_t mAllocatedSize; /// backup of initially allocated size
FairMQTransportFactory* mFactory = nullptr; /// pointer to transport factory
pmr::FairMQMemoryResource* mResource = nullptr; /// message resource
buffer_type mData; /// the data buffer
};

/// VectorObject handles a message object holding std::vector with polymorphic_allocator
/// can not adopt an existing message, because the polymorphic_allocator will call the element constructor,
/// so this works only with new messages
template <typename T, typename _BASE = ContainerRefObject<std::vector<T, o2::pmr::polymorphic_allocator<T>>>>
class VectorObject : public _BASE
{
public:
template <typename... Args>
VectorObject(Args&&... args) : _BASE(std::forward<Args>(args)...)
{
}
};

// SpanObject creates a trivial binary object for an array of elements of
// type T and holds a span over the elements
// FIXME: probably obsolete after introducing of vector with polymorphic_allocator
template <typename T>
class SpanObject : public ContextObject
{
Expand All @@ -101,9 +223,9 @@ class MessageContext {
// TODO: we probably also want to check consistency of the header message, i.e. payloadSize member
auto payloadMsg = context->createMessage(bindingChannel, index, nElements * sizeof(T));
mValue = value_type(reinterpret_cast<T*>(payloadMsg->GetData()), nElements);
parts.AddPart(std::move(headerMsg));
parts.AddPart(std::move(payloadMsg));
channel = bindingChannel;
mParts.AddPart(std::move(headerMsg));
mParts.AddPart(std::move(payloadMsg));
mChannel = bindingChannel;
}
~SpanObject() override = default;

Expand Down Expand Up @@ -152,7 +274,7 @@ class MessageContext {
{
// Verify that everything has been sent on clear.
for (auto &m : mMessages) {
assert(m->parts.Size() == 0);
assert(m->empty());
}
mMessages.clear();
}
Expand Down
15 changes: 7 additions & 8 deletions Framework/Core/src/DataAllocator.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -61,21 +61,21 @@ DataAllocator::matchDataHeader(const Output& spec, size_t timeslice) {
throw std::runtime_error(str.str());
}

DataChunk
DataAllocator::newChunk(const Output& spec, size_t size) {
DataChunk& DataAllocator::newChunk(const Output& spec, size_t size)
{
std::string channel = matchDataHeader(spec, mTimingInfo->timeslice);
auto context = mContextRegistry->get<MessageContext>();

FairMQMessagePtr headerMessage = headerMessageFromOutput(spec, channel, //
o2::header::gSerializationMethodNone, //
size //
);
auto& co = context->add<MessageContext::TrivialObject>(std::move(headerMessage), channel, 0, size);
return DataChunk{ reinterpret_cast<char*>(co.data()), size };
auto& co = context->add<MessageContext::ContainerRefObject<DataChunk>>(std::move(headerMessage), channel, 0, size);
return co;
}

DataChunk
DataAllocator::adoptChunk(const Output& spec, char *buffer, size_t size, fairmq_free_fn *freefn, void *hint = nullptr) {
void DataAllocator::adoptChunk(const Output& spec, char* buffer, size_t size, fairmq_free_fn* freefn, void* hint = nullptr)
{
// Find a matching channel, create a new message for it and put it in the
// queue to be sent at the end of the processing
std::string channel = matchDataHeader(spec, mTimingInfo->timeslice);
Expand All @@ -87,8 +87,7 @@ DataAllocator::adoptChunk(const Output& spec, char *buffer, size_t size, fairmq_

// FIXME: how do we want to use subchannels? time based parallelism?
auto context = mContextRegistry->get<MessageContext>();
auto& co = context->add<MessageContext::TrivialObject>(std::move(headerMessage), channel, 0, buffer, size, freefn, hint);
return DataChunk{ reinterpret_cast<char*>(co.data()), size };
context->add<MessageContext::TrivialObject>(std::move(headerMessage), channel, 0, buffer, size, freefn, hint);
}

FairMQMessagePtr DataAllocator::headerMessageFromOutput(Output const& spec, //
Expand Down
7 changes: 3 additions & 4 deletions Framework/Core/src/DataProcessor.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,10 @@ namespace framework
void DataProcessor::doSend(FairMQDevice &device, MessageContext &context) {
for (auto &message : context) {
// monitoringService.send({ message->parts.Size(), "outputs/total" });
assert(message->parts.Size() == 2);
FairMQParts parts = std::move(message->parts);
assert(message->parts.Size() == 0);
FairMQParts parts = std::move(message->finalize());
assert(message->empty());
assert(parts.Size() == 2);
device.Send(parts, message->channel, 0);
device.Send(parts, message->channel(), 0);
assert(parts.Size() == 2);
}
}
Expand Down
4 changes: 2 additions & 2 deletions Framework/Core/src/Dispatcher.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ void Dispatcher::send(DataAllocator& dataAllocator, const DataRef& inputData, co
dataAllocator.adopt(output, DataRefUtils::as<TObject>(inputData).release());
} else { // POD
// todo: do it non-copy, when API is available
auto outputMessage = dataAllocator.newChunk(output, inputHeader->payloadSize);
memcpy(outputMessage.data, inputData.payload, inputHeader->payloadSize);
auto& outputMessage = dataAllocator.newChunk(output, inputHeader->payloadSize);
memcpy(outputMessage.data(), inputData.payload, inputHeader->payloadSize);
}
}

Expand Down
Loading