0

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?

Community
  • 1
  • 1
David Sackstein
  • 500
  • 1
  • 5
  • 19
  • The string is templated on the allocator, but are you passing a reference to the allocator instance at each string creation, or are you just letting it be default-constructed? If you could share some of your code, it'd be helpful. –  Jul 08 '17 at 06:09
  • You can't leave it default constructed, because there's no default constructor @Frank – sehe Jul 08 '17 at 09:43
  • @David Instead of talking a lot, just show the code that exhibits the problem. Because now we're left guessing, and this will not lead to the most helpful comments/answers. I tried though. – sehe Jul 08 '17 at 09:49
  • Hi Frank and sehe, Thanks for trying to help and sorry for not bringing the code. The code was rather long and I wasn't sure which part to quote. I solved the problem in the end, and I will explain the solution shortly, along with a better explanation of the problem. – David Sackstein Jul 08 '17 at 13:59

2 Answers2

2

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'
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Added a SSCCE [Live On Coliru](http://coliru.stacked-crooked.com/a/51fb2d872078de33) so you can compare. Also, so you know why self-contained code beats a zillion paragraphs of prose. – sehe Jul 08 '17 at 10:38
  • For fun, I went a bit further and prototyped [a map that allows heterogeneous lookup](http://coliru.stacked-crooked.com/a/7e7013dcb1722e59) (using multi-index). Compare the stats/traces with [regular `map<>`](http://coliru.stacked-crooked.com/a/4ff1a6fc013abc64). Note the efficiency win of heterogeneous lookup keys. – sehe Jul 08 '17 at 19:15
0

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

David Sackstein
  • 500
  • 1
  • 5
  • 19
  • I think the Scott Meyers quotes [here](http://www.boost.org/doc/libs/1_64_0/doc/html/interprocess/allocators_containers.html#interprocess.allocators_containers.containers_explained.stl_container_requirements) are the definitive reason. And it explains why Boost Container made it a design goal to _"Containers' internal pointers should be of the type allocator::pointer and containers may not assume allocator::pointer is a raw pointer."_ – sehe Jul 08 '17 at 18:32
  • The most important lesson here is that _code talks_. Had you shown the code, we could have pointed it out straight away. For fun, I went a bit further and prototyped [a map that allows heterogeneous lookup](http://coliru.stacked-crooked.com/a/7e7013dcb1722e59) (using multi-index). Compare the stats/traces with [regular `map<>`](http://coliru.stacked-crooked.com/a/4ff1a6fc013abc64). Note the efficiency win of heterogeneous lookup keys. – sehe Jul 08 '17 at 19:08