c++boostallocatorboost-interprocess

Opening Boost Interprocess segment in Constructor of object


I create a map<int, vector<int>> in a Boost Interprocess managed_shared_memory in one process and then want to open it in another process to write to it continually. The other process tries to open the shared memory in the constructor of an object Communication and then presents a method addData(int _data).

Example:

// Typedefs for shared data structures

using boost::interprocess;
using std::pair;
using std::scoped_allocator_adaptor;

typedef managed_shared_memory::segment_manager man;

typedef allocator <int , man> Int_Allocator;
typedef vector <int, Int_Allocator> Int_Vector;

typedef pair <const int, Int_Vector> Int_Vector_Map_Type;
typedef scoped_allocator_adaptor <allocator<Int_Vector_Map_Type, man>> Int_Vector_Map_Type_Allocator;
typedef map <int, Int_Vector, std::less<int>, Int_Vector_Map_Type_Allocator> Int_Vector_Map;
// Let's call this process 'parent'

managed_shared_memory segment(create_only, "Shared");
Int_Vector_Map_Type_Allocator alloc = segment.get_segment_manager();
segment.construct<Int_Vector_Map>("Map")(std::less<int>(), alloc);
// 'child' process

class Communciation{
    Int_Vector_Map* map;

public:
    Communication{
        managed_shared_memory segment(open_only, "Shared");
        map = segment.find<Int_Vector_Map>("Map").first;
    }
    void addData(int _key, int _value){
        if(map->size() == 0 || map->find(_key) == map->end(){
            managed_shared_memory segment(open_only, "Shared");
            Int_Allocator alloc = segment.get_segment_manager();
            map->insert(Int_Vector_Map_Type(_key, Int_Vector(alloc)));
        } // create the vector and its allocator, otherwise runtime error

        map->at(_key).push_back(_value);
    }
}

This is a broken down example of what I am looking at, but I do not see the immediate error. When trying to run this code I get the runtime error "Unhandled Exception: EXCEPTION_ACCESS_VIOLATION reading address (...)". Changing the addData(...) method to:

void addData(int _key, int _value){
    managed_shared_memory segment(open_only, "Shared");
    map = segment.find<Int_Vector_Map>("Map").first;

    if(map->size() == 0 || map->find(_key) == map->end(){
        Int_Allocator alloc = segment.get_segment_manager();
        map->insert(Int_Vector_Map_Type(_key, Int_Vector(alloc)));
    } // create the vector and its allocator, otherwise runtime error

    map->at(_key).push_back(_value);
}

i.e. opening the map every call fixes this issue but is not applicable in my case as I want to be able to call this method many times per frame without impacting fps too much.

What is the cause of this issue and is it even possible to use boost's interprocess as described?

Edit: For added context, the 'child' process is running in another process in which I inject a dll into.


Solution

  • The answer has many components:

    I have good experience using Boost Container's implementations, so let's show that.

    Simplified type aliases

    // Typedefs for shared data structures
    namespace bip = boost::interprocess;
    
    namespace Shared {
        namespace bc  = boost::container;
        using Segment = bip::managed_shared_memory;
        using Mgr     = Segment::segment_manager;
    
        template <typename T> using Alloc  = bc::scoped_allocator_adaptor<bip::allocator<T, Mgr>>;
        template <typename T> using Vector = bc::vector<T, Alloc<T>>;
        template <typename K, typename V, typename Cmp = std::less<K>, typename P = std::pair<K const, V>>
        using Map = bc::map<K, V, Cmp, Alloc<P>>;
    } // namespace Shared
    

    Now you can simply state:

    using IVMap = Shared::Map<int, Shared::Vector<int>>; // Int_Vector_Map
    

    And it will expand to the right set of comparators, allocators etc.

    Lifetime

    To construct the segment from the constructor, you will need to use the initializer list. E.g.:

    class Communication {
      public:
        Communication() //
            : segment_{bip::open_only, "Shared"}
            , map_(*segment_.find<IVMap>("Map").first) {}
    
        void addData(int key, int value);
    
      private:
        Shared::Segment segment_;
        IVMap&          map_;
    };
    

    Emplace

    To insert new elements, implement addData like:

    void addData(int key, int value) {
        auto it = map_.find(key);
    
        if (it == map_.end())
            it = map_.emplace(key).first;
    
        it->second.push_back(value);
    }
    

    Note that the segment simply exists. Note that the allocator is propagated due to the scoped allocator adaptor in combination with the emplace construction.

    Edit Sadly I cannot make the emplace compile. I'm sure I'm missing something trivial, but I have worked around it like:

    if (it == map_.end()) {
        IVMap::mapped_type v(map_.get_allocator());
        it = map_.emplace(key, std::move(v)).first;
    }
    

    Live Demo

    Live On Coliru

    #include <boost/container/scoped_allocator.hpp>
    #include <boost/interprocess/allocators/allocator.hpp>
    #include <boost/interprocess/containers/map.hpp>
    #include <boost/interprocess/containers/vector.hpp>
    #include <boost/interprocess/managed_mapped_file.hpp>
    #include <boost/interprocess/managed_shared_memory.hpp>
    #include <set>
    
    // Typedefs for shared data structures
    namespace bip = boost::interprocess;
    
    namespace Shared {
        namespace bc  = boost::container;
    #ifdef COLIRU // online compiler
        using Segment = bip::managed_mapped_file;
    #else
        using Segment = bip::managed_shared_memory;
    #endif
        using Mgr     = Segment::segment_manager;
    
        template <typename T> using Alloc  = bc::scoped_allocator_adaptor<bip::allocator<T, Mgr>>;
        template <typename T> using Vector = bc::vector<T, Alloc<T>>;
        template <typename K, typename V, typename Cmp = std::less<K>, typename P = std::pair<K const, V>>
        using Map = bc::map<K, V, Cmp, Alloc<P>>;
    } // namespace Shared
    
    using IVMap = Shared::Map<int, Shared::Vector<int>>; // Int_Vector_Map
    
    class Communication {
      public:
        Communication() //
            : segment_{bip::open_only, "Shared"}
            , map_(*segment_.find<IVMap>("Map").first) {}
    
        void addData(int key, int value) {
            auto it = map_.find(key);
    
            if (it == map_.end()) {
                IVMap::mapped_type v(map_.get_allocator());
                it = map_.emplace(key, std::move(v)).first;
            }
    
            it->second.push_back(value);
        }
    
        auto const& get() const { return map_; }
    
      private:
        Shared::Segment segment_;
        IVMap&          map_;
    };
    
    #include <fmt/ranges.h>
    #include <functional>
    #include <random>
    static auto vals = bind(std::uniform_int_distribution(100, 999), std::mt19937{std::random_device{}()});
    
    int main(int argc, char** argv) {
        auto const args = std::set<std::string_view>(argv + 1, argv + argc);
    
        if (args.contains("parent")) {
            Shared::Segment seg{bip::open_or_create, "Shared", 10 << 10};
            seg.find_or_construct<IVMap>("Map")(seg.get_segment_manager());
        }
    
        if (args.contains("child")) {
            Communication comm;
    
            for (unsigned n = 10; n--;) {
                auto v = vals();
                comm.addData(v / 100, v);
            }
    
            fmt::print("After insertion:\n - {}\n", fmt::join(comm.get(), "\n - "));
        }
    }
    

    Printing

    + ./a.out parent
    + ./a.out child
    After insertion:
     - (1, [155, 170])
     - (2, [248])
     - (4, [418])
     - (5, [542, 562])
     - (6, [642, 674, 659])
     - (7, [783])
    + ./a.out child
    After insertion:
     - (1, [155, 170, 143, 130])
     - (2, [248, 222])
     - (3, [325])
     - (4, [418, 428])
     - (5, [542, 562, 556])
     - (6, [642, 674, 659, 671])
     - (7, [783, 793, 733, 745])
    + ./a.out child
    After insertion:
     - (1, [155, 170, 143, 130])
     - (2, [248, 222])
     - (3, [325, 320, 362])
     - (4, [418, 428, 486, 437])
     - (5, [542, 562, 556])
     - (6, [642, 674, 659, 671, 695, 609])
     - (7, [783, 793, 733, 745, 786, 777, 793])
     - (9, [995])
    

    Live Demo locally: enter image description here