1

I implemented, based on the boost examples, a threadsafe queue-class in the shared memory. It works as expected for a simple producer/consumer model.

As a next step, I defined the interface IConcurrentIPCQueue which is implemented by the ConcurrentIPCQueue class. I need the interface so I can implement an adapter to the queue for another issue.

The only difference between my first version and the current one below is the following:

First version:

template <class T> class ConcurrentIPCQueue

now adding the information, that I want to implement the interface like this:

Current version:

`template <class T> class ConcurrentIPCQueue :public IConcurrentIPCQueue<T>`

results in a read access violation on the consumer side. On the producer side, I can easily push_back and pop_front data correctly on its own. But strangely on the consumer side, I cannot access the shared memory (although the pair from segment.find returns correctly an address and 1). So the question is, why the version with implementing the interface makes a difference on the consumer side and results in this strange error. And how I can solve it?

To keep the example short, I present here a minimalistic model of the queue:

#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/deque.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/sync/interprocess_condition.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/thread/lock_guard.hpp>
#include <sstream>

namespace boost_ipc = boost::interprocess;
static char const *SHMEMNAME= "SHMEM";
static char const *SHQUEUENAME= "MYQUEUE";

template <class T> class IConcurrentIPCQueue
{
public:
    virtual void push_back(T const & data) = 0;
    virtual bool pop_front(T & data) = 0;
virtual unsigned int size() = 0;
};

template <class T> class ConcurrentIPCQueue :public IConcurrentIPCQueue<T>
{
public:
    // allocator for allocating memory from the shared memory
    typedef boost_ipc::allocator<T, boost_ipc::managed_shared_memory::segment_manager> ShmemAlloc;
    typedef boost_ipc::interprocess_mutex IPC_Mutex;
    typedef boost_ipc::interprocess_condition IPC_Cond;
    typedef boost::lock_guard<IPC_Mutex> LockGuard;

    ConcurrentIPCQueue(ShmemAlloc salloc) : mQueue_(salloc) { }

    void push_back(T const & data)
    {
        {
            LockGuard lock(mMutex_);
            mQueue_.push_back(data);
        }
        mWait_.notify_one();
    }

    bool pop_front(T & data)
    {
        LockGuard lock(mMutex_);

        if (mQueue_.empty())
            return false;

        data = mQueue_.front(); // return reference to first element
        mQueue_.pop_front(); // remove the first element

        return true;
    }

unsigned int size()
{
    LockGuard lock(mMutex_);
    return mQueue_.size();
}

private:
    boost_ipc::deque<T, ShmemAlloc> mQueue_;
    IPC_Mutex mMutex_;
    IPC_Cond mWait_;
};

typedef ConcurrentIPCQueue<char> myqueue;

void consumer()
{
    boost_ipc::managed_shared_memory openedSegment(boost_ipc::open_only, SHMEMNAME);

    myqueue*openedQueue = openedSegment.find<myqueue>(SHQUEUENAME).first;
    char tmp;

    while (openedQueue->pop_front(tmp)) {
        std::cout << "Received " << tmp << "\n";
    }
}

void producer() {
    boost_ipc::shared_memory_object::remove(SHMEMNAME);

    boost_ipc::managed_shared_memory mysegment(boost_ipc::create_only, SHMEMNAME, 131072);

    myqueue::ShmemAlloc alloc(mysegment.get_segment_manager());
    myqueue*myQueue = mysegment.construct<myqueue>(SHQUEUENAME)(alloc);
char mychar='A';

    for (int i = 0; i < 10; ++i)
        myQueue->push_back(mychar);

    while (myQueue->size() > 0)
        continue;
}

int main()
{
    //producer(); // delete comment for creating producer process
    consumer();
    return 0;
}
mbed_dev
  • 1,450
  • 16
  • 33
  • why do you have your string constants defined twice? Could you provide a complete runnable program? – didierc Jul 05 '17 at 11:26
  • ok, that was a mistake when creating this minimalistic model. i deleted it and added a main-function() where one has to comment in the function either for the producer() or the consumer() – mbed_dev Jul 05 '17 at 11:44

1 Answers1

1

UPDATE:

I could reproduce it with MSVC15.3 and Boost 1.64.

Turns out that the vtable pointers are the issue: they are different in each process, which leads to Undefined Behaviour as soon as you have runtime polymorphic types (std::is_polymorphic<T>).

It turns out the documentation forbids it clearly: Is it possible to store polymorphic class in shared memory?

sehe
  • 374,641
  • 47
  • 450
  • 633
  • that is really strange, that it works for you. why does it even make a difference when i inherit from the interface and when not. i increased in VS by hand the stack and heap sizes, the initial virtual memory size on windows, increased the "reserve" size like you in the code, but it still does not work (except i don't inherit from the interface). can it be maybe another problem too? – mbed_dev Jul 05 '17 at 14:34
  • Please make sure your code sample demonstrates the issue. RIght now I don't even know what you mean by "I don't inherit from the interface". – sehe Jul 05 '17 at 14:35
  • I tried to explain my issue better, I hope that it is udnerstandable now – mbed_dev Jul 05 '17 at 16:11
  • @mbed_dev Did you recompile both sides and `remove` the existing shared memory area? Otherwise you're clearly invoking [Undefined Behaviour](https://en.wikipedia.org/wiki/Undefined_behavior) due to the changed binary layouts – sehe Jul 05 '17 at 17:47
  • Erm, I just found the real culprit. Rewrote the answer. – sehe Jul 06 '17 at 02:31
  • You can try a `std::variant` or discriminated `union` to replace a polymorphic class. – Davislor Jul 06 '17 at 02:57