Search code examples
shared-ptrrtos

transfer std::shared_ptr via mailbox


We have a Real Time Operating System which offers Inter-Task-Communication by so called Mailboxes. A Mailbox is described by a Handle of type RTKMailbox. The API looks like:

int RTKPut(RTKMailbox h, const void* data);
int RTKGet(RTKMailbox h, void* data);

The size of data is known by the Mailbox. Data transfer could be thought as doing a memcpy from sender to receiver.

Imagine I have a Producer-Task and a Consumer-Task; is it a good idea to send a shared_ptr by that system?

Since the Mailbox does not know a shared_ptr my idea is to wrap the shared_ptr in a transport structure.

The code could look like:

class MyData    {
    //...
};

struct TransportWrapper {

    void BeforePut();
    void AfterGet();

    std::shared_ptr<MyData> Data;

    TransportWrapper() {}
    TransportWrapper(std::shared_ptr<MyData>& _data) : Data(_data)
    {}
};

void Send(RTKMailbox mbHandle, std::shared_ptr<MyData>& data)
{
    TransportWrapper wrap(data);
    wrap.BeforePut();
    RTKPut(mbHandle, &wrap);
}

std::shared_ptr<MyData> Receive(RTKMailbox mbHandle)
{
    TransportWrapper wrap;
    RTKGet(mbHandle, &wrap);
    wrap.AfterGet();
    return wrap.Data;
}

What do I have to do in BeforePut to prevent the shared_ptr to be deleted if the Lifetime of the wrapper ends?

What do I have to do in AfterGet to restore the shared_ptr to the state it had before Put?

Regards Andreas


Solution

  • Your example code won't work, you can't just memcpy a shared_ptr because all that does is copy the pointers it contains, it doesn't make a new copy of the shared_ptr and increase the reference count. You cannot use memcpy with objects that have non-trivial constructors or destructors.

    Assuming the sender and receiver share an address space (because otherwise this is pretty much impossible to do via your mailbox API, you need shared memory), you need to increase the shared_ptr's reference count on the sender side, to ensure that the sender doesn't drop its last reference to the owned object and delete it before the receiver has received it. Then the receiver has to decrease the reference count, so they need to coordinate.

    If delivery to a mailbox is asynchronous (i.e. the sender does not block until delivery is complete and the receiver has received the data) you can't do that with local variables in the Send function, because those variables will go out of scope as soon as the RTKPut call returns, which will decrease the reference count (and maybe destroy the data) before the receiver has got it.

    The simplest way to solve that is to create a new shared_ptr on the heap and transfer its address.

    void Send(RTKMailbox mbHandle, const std::shared_ptr<MyData>& data)
    {
      std::shared_ptr<MyData>* p = new std::shared_ptr<MyData>(data);
      if (RTKPut(mbHandle, &p) != success)
      {
        delete p;
        // deal with it
      }
    }
    
    std::shared_ptr<MyData> Receive(RTKMailbox mbHandle)
    {
      std::shared_ptr<MyData>* p = nullptr;
      if (RTKGet(mbHandle, &p) == success)
      {
        auto sp = *p;
        delete p;
        return sp;
      }
      // else deal with it
    }
    

    This assumes that if RTKPut returns successfully then delivery will not fail, otherwise you leak the shared_ptr created on the heap, and will never delete the object it owns.