How do I search a Boost::interprocess::map stored within a Boost::managed_shared_memory segment as open_read_only?

Viewed 85

I've got a Boost:interprocess::unordered_map that has a Key of type string and Value of type string. That map is stored in a managed_shared_memory segment. I'm attempting to restrict access to these segments, so the segment is created with rw-rw-r--. The idea is the owner and related group would have read/write access, and 'other' would be restricted to read only.

This works fine if I open_read_only and iterate through the map manually. All of the data is present across processes. I ran into issues when attempting to use mymap->find() or mymap->at(), because both functions require the basic_string (w/ allocator) be passed.

When I attempt to create a basic_string to use in find() or at(), I get an access violation error.

char_string tmpKey(searchKey, alloc_inst); // Access violation crash.

Is there a way to use find() or at() without the basic_string? Or is there a better way to approach this?

Header

typedef boost::interprocess::allocator<char, boost::interprocess::managed_shared_memory::segment_manager> CharAllocator;
typedef boost::container::basic_string<char, std::char_traits<char>, CharAllocator> char_string;
typedef char_string KeyType;
typedef char_string MappedType;
typedef std::pair<const char_string, char_string> ValueType;
typedef boost::interprocess::allocator<ValueType, boost::interprocess::managed_shared_memory::segment_manager> ShmemAllocator;
typedef boost::unordered_map<KeyType, MappedType, boost::hash<KeyType>, std::equal_to<KeyType>, ShmemAllocator> MyShmMap;

Main

managed_shared_memory segment(open_read_only, "segment_name");
MyShmMap* mymap = segment.find<MyShmMap>("segment_name").first;
CharAllocator alloc_inst(segment.get_segment_manager());
char_string tmpKey(searchKey, alloc_inst); // Access violation crash.
MyShmMap::iterator it = mymap->find(tmpKey);
if (it != mymap->end()) {
  auto value = mymap->at(tmpKey);
  // etc
1 Answers

You never check the pointer mymap is not null.

If we make sure that exists, by first executing this (once):

// assuming this exists
Segment s(bip::open_or_create, "segment_name", 10 * 1024);
s.find_or_construct<MyShmMap>("segment_name")(s.get_segment_manager());

Then it all works fine: Live On Coliru

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <boost/unordered_map.hpp>
#include <boost/functional.hpp>

namespace bip = boost::interprocess;
namespace bc  = boost::container;

using Segment = bip::managed_shared_memory;
using Mgr     = Segment::segment_manager;

template <typename T> using Alloc = bip::allocator<T, Segment::segment_manager>;
template <typename K, typename V>
using UnorderedMap = boost::unordered_map<K, V, boost::hash<K>, //
                                          std::equal_to<K>,
                                          Alloc<std::pair<K const, V>>>;

using String   = bc::basic_string<char, std::char_traits<char>, Alloc<char>>;
using MyShmMap = UnorderedMap<String, String>;

int main() {
    {
        // assuming this exists
        Segment s(bip::open_or_create, "segment_name", 10 * 1024);
        s.find_or_construct<MyShmMap>("segment_name")(s.get_segment_manager());
    }

    Segment segment(bip::open_read_only, "segment_name");
    MyShmMap* mymap = segment.find<MyShmMap>("segment_name").first;

    auto it = mymap->find(String("test", segment.get_segment_manager()));
    if (it != mymap->end()) {
        auto const& value = it->second;
    }
}

I also took the opportunity to

  • clean up your type-defs for re-usability
  • note that using .at after .find is very wasteful. It's especially wasteful since you used the /shared/ string as the key.

Smart And Efficient

You can really improve the performance by using a hash/equality comparer that is compatible with string views, and using the advanced lookup:

template <typename K, typename V, typename Hash = boost::hash<K>,
          typename EqCmp = std::equal_to<K>>
using UnorderedMap = boost::unordered_map<K, V, //
                         Hash, EqCmp, Alloc<std::pair<K const, V>>>;

using MyShmMap = UnorderedMap<String, String, boost::hash<std::string_view>, std::equal_to<void>>;

Live On Coliru

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <boost/unordered_map.hpp>
#include <boost/functional.hpp>

namespace bip = boost::interprocess;
namespace bc  = boost::container;

using Segment = bip::managed_shared_memory;
using Mgr     = Segment::segment_manager;

template <typename T> using Alloc = bip::allocator<T, Segment::segment_manager>;
template <typename K, typename V, typename Hash = boost::hash<K>,
          typename EqCmp = std::equal_to<K>>
using UnorderedMap = boost::unordered_map<K, V, //
                         Hash, EqCmp, Alloc<std::pair<K const, V>>>;

using String   = bc::basic_string<char, std::char_traits<char>, Alloc<char>>;
using MyShmMap = UnorderedMap<String, String, boost::hash<std::string_view>, std::equal_to<void>>;

#include <string_view>
#include <iostream>
#include <iomanip>

int main()
{
    {
        // assuming this exists
        Segment s(bip::open_or_create, "segment_name", 10 * 1024);

        auto* m = s.get_segment_manager();
        s.find_or_construct<MyShmMap>("segment_name")(m) //
            ->emplace(std::piecewise_construct,          //
                      std::tuple("stuff", m),            //
                      std::tuple("lives here", m));
    }

    Segment segment(bip::open_read_only, "segment_name");
    MyShmMap* mymap = segment.find<MyShmMap>("segment_name").first;
    auto      hash  = mymap->hash_function();
    auto      eq    = mymap->key_eq();

    if (auto it = mymap->find("stuff", hash, eq); it != mymap->end()) {
        auto const& [k,v] = *it;
        std::cout << std::quoted(k.c_str()) << " -> " << std::quoted(v.c_str()) << "\n";
    }
}

Which prints

"stuff" -> "lives here"

Cherry On Top

To reduce the ugly of allocators like:

Segment s(bip::open_or_create, "segment_name", 10 * 1024);

auto* m = s.get_segment_manager();
s.find_or_construct<MyShmMap>("segment_name")(m) //
    ->emplace(std::piecewise_construct,          //
              std::tuple("stuff", m),            //
              std::tuple("lives here", m));

Consider scoped allocators:

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

Now the container propagates the allocator when using the allocator to construct:

Segment s(bip::open_or_create, "segment_name", 10 * 1024);

auto& m = *s.find_or_construct<MyShmMap>("segment_name")(
    s.get_segment_manager());

m.emplace("stuff", "lives here");

See it Live On Coliru again.

Related