David Sackstein
David Sackstein

Reputation: 570

Can boost's interprocess segment manager allocators be themselves shared with other processes?

I am creating a shared interprocess map using boost::interprocess. For this I create an allocator from the segment_manager of the shared memory segment where the map is located.

The element value type of the map is a basic_string which is itself templated to use a char allocator created from the same segment manager. In one process I create the map and in the other I search for an item using the maps iterator in that process and in some cases I call map::erase with the iterator.

This causes an access violation exception and I know that I am making the call with a valid iterator. The access violation occurs in the call to the destructor of the basic_string that is the 'second' of the 'pair' pointed to by the iterator. When I perform the same erase operation using an iterator in the writing process immediately after insertion there is no access violation.

It looks as though the reading process is trying to release the memory of the element using the element's allocator, which was created in the writing process whereas that allocator is only valid in the process that created it.

Does this mean that the allocators themselves cannot be shared?

I would have expected that allocator to be usable in both processes as its state should contain only relative pointers that are valid in both processes. If not, how can I share elements that use shared memory (heap) allocations between processes? Should I have created those allocators in a special way in the writing process before passing them to the basic_string elements to allow me to use them in anerase operation in another process?

What else might be causing the access violation?

Upvotes: 0

Views: 521

Answers (2)

sehe
sehe

Reputation: 393694

The allocators are fine (the magic is in offset_ptr and it's transparent across process boundaries).

If the "client" destructs strings, then you're doing something else than reading. Most likely, you're receiving a copy, like:

auto by_copy = smap.find(key)->second; // makes a copy

Try, e.g. to do

auto const& by_ref = smap.find(key)->second; // doesn't copy

Alternatively you might be doing smap[key] which automatically allocates if the key wasn't there. This could cause an old-fashioned race condition (sharing data between processes is much like sharing data between threads: you need proper synchronization).

Lastly, you didn't mention /anything/ about the key, but if it, too, is a string then just the lookup-by-key is prone to allocate from shared memory (and, it being a temporary, it would destruct). The race condition looms again. See also want to efficiently overcome mismatch between key types in a map in Boost.Interprocess shared memory

Demo

In the absence of a proper SSCCE or MCVE, let me throw one at you. You might spot something you do differently.

#include <iostream>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/container/scoped_allocator.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <boost/interprocess/containers/map.hpp>

namespace bip = boost::interprocess;

namespace shared {
    namespace bc  = boost::container;

    using Segment = bip::managed_shared_memory;
    using Manager = Segment::segment_manager;
    template <typename T>
    using Alloc   = bc::scoped_allocator_adaptor<bip::allocator<T, Manager> >;

    using String  = bip::basic_string<char, std::char_traits<char>, Alloc<char> >;
    template <typename K, typename V, typename Cmp = std::less<K> >
        using Map = bip::map<K, V, Cmp, Alloc<std::pair<K const, V> > >;
};

int main() {
    using namespace shared;

    Segment smt(bip::open_or_create, "de06c60a-0b80-4b20-a805-b3f405f35427", 20ul<<20); // 20 mb
    auto& mat = *smt.find_or_construct<Map<String, String> >("dict")(smt.get_segment_manager());

    if (mat.empty()) {
        mat.emplace("1", "one");
        mat.emplace("2", "two");
        mat.emplace("3", "three");
    } else {
        // shared string factory
        auto ss = [&](auto... stuff) { return String(stuff..., smt.get_segment_manager()); };

        auto  copy = mat.at(ss("3")); // constructs and destructs temp String("3"); constructs copy
        auto& ref  = mat.at(ss("2")); // constructs and destructs temp String("2"); no copy
        std::cout << "copy: " << copy << "\n";
        std::cout << "ref: "  << ref  << "\n";

        // iterate with no shared temps or copies:
        for (auto& p : mat)
            std::cout << "entry '" << p.first << "' -> '" << p.second << "'\n";
    } // destructs copy
}

The same on Coliru, but with memory-mapped files (because shared memory is not allowed on there):

Live On Coliru

using Segment = bip::managed_mapped_file;

Prints nothing first run, subsequent runs:

copy: three
ref: two
entry '1' -> 'one'
entry '2' -> 'two'
entry '3' -> 'three'

Upvotes: 2

David Sackstein
David Sackstein

Reputation: 570

Yes, the allocators provided by boost for use with shared memory can be used by other processes. I will present the short of it first and then explain a little better what the original problem was.

In short - I was using basic_string instantiated with the char, char_traits and the shared memory allocator. I should have used boosts boost::interprocess::basic_string instead.

Here is a little more detail.

In my code I am creating a shared map using boost's interprocess map. The elements in that map were strings. I used boosts segment manager correctly to create an allocator for the map and I used the same segment manager to create a char allocator for the strings that are placed in that map.

However, the strings I was creating and storing in the map were of templated type std::basic_string. I was correctly providing this template with the allocator type and I was correctly providing the allocator instance to the string in its constructor.

Moreover, the string that was stored in the map could be read by another processor that opened the shared memory. However, when the reading process tried to erase the entry, so deleting the string there was an access violation.

After replacing the basic_string with boost::interprocess::basic_string this problem disappeared.

Had I known that boost has its own basic_string in the interprocess namespace I would certainly have used it; I already knew that I need to use boost's map instead of the std one. I just wasn't aware that there was a interprocess::basic_string

As a side note, I would still be interested to know why the std containers are clearly not appropriate for use with allocators of shared memory even though they do provide template parameters that allow implementors to specify which allocator should be used

Upvotes: 0

Related Questions