Search code examples
c++boostshared-ptrboost-interprocess

Shared lifecycle cross processes with boost::interprocess::shared_ptr


I'm trying to use the boost::interprocess in order to share data between processes and utilize the shared_ptr for lifecycle management. I have a map residing in the shared memory and two processes should access it.

    boost::shared_ptr<boost::interprocess::managed_mapped_file> segment =
          boost::make_shared<boost::interprocess::managed_mapped_file>
                 (boost::interprocess::open_or_create,
                  "./some-mmap.txt", //file name
                  65536);           //segment size in bytes

    pair_allocator_type alloc_inst(segment->get_segment_manager());

    elements = boost::interprocess::make_managed_shared_ptr(
            segment->find_or_construct<map_type>("elements")
            (std::less<IdType>(), alloc_inst),
            *segment
    );

In a test program I have, a Parent and a Child process which essentially both use the piece of code from above. Therefore, they use the same underlying file, same name of the shared object ("elements"), same types, etc.

However, I noticed that whenever a child process dies, the size of the collection dropped to 0. Strange. I investigated and it seemed that it had to do with the destruction of elements (when this shared pointer goes out of scope). Whenever elements went out of scope, the size of the underlying collection went to 0.

I also saw that the elements has the use_count exactly 1 in both Parent and Child process. For Parent that makes sense, but I don't get it why is it the case for Child. My assumption is that when the Child process dies, the use_count drops to 0, and then the collection is cleared.

What I want is that the pointed object (map) is not destroyed when the Child process dies. I should not make assumptions which processes are active and which ones not.

  • Am I initializing the boost::interprocess::shared_ptr in a wrong way?
  • Am I missing completely the semantics of this pointer --> is it used only to manage shared-memory objects lifecycle only within one process and not across processes?
  • How to have a shared_ptr whose use_count is shared across processes?

EDIT - clarifications on collection

The elements is a boost::interprocess::map that maps a certain IdType to a shared-memory shared pointer to ShmemType. The size of elements drops to 0 when the Child process dies.

typedef boost::interprocess::managed_mapped_file::segment_manager segment_manager_type;
typedef std::pair<const IdType, ShmemType::pointer_type> pair_type;
typedef boost::interprocess::allocator<pair_type, segment_manager_type> pair_allocator_type;
typedef boost::interprocess::map<IdType, ShmemType::pointer_type, std::less<IdType>, pair_allocator_type> map_type;

EDIT - example from boost docs

I've taken the examples from boost docs and expanded on it to track down the root cause of my original problem.

#include <boost/interprocess/managed_mapped_file.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/smart_ptr/shared_ptr.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <string>
#include <cstdlib> //std::system

using namespace boost::interprocess;

typedef allocator<int, managed_mapped_file::segment_manager>  ShmemAllocator;
typedef vector<int, ShmemAllocator> MyVector;

#include <iostream>

//Main function. For parent process argc == 1, for child process argc == 2
int main(int argc, char *argv[])
{
   if(argc == 1){ //Parent process

      //Create a new segment with given name and size
       managed_mapped_file segment(open_or_create, "./a_MySharedMemory.txt", 65536);

      //Initialize shared memory STL-compatible allocator
      const ShmemAllocator alloc_inst (segment.get_segment_manager());

//      MyVector* elements = segment.find_or_construct<MyVector>("some-vector")      //object name
//                      (alloc_inst);
       typedef boost::interprocess::managed_shared_ptr<MyVector, boost::interprocess::managed_mapped_file>::type map_pointer_type;

        map_pointer_type elements = boost::interprocess::make_managed_shared_ptr(
                segment.find_or_construct<MyVector>("some-vector")      //object name
                (alloc_inst),
                segment
        );

      for(int i = 0; i < 100; ++i)  //Insert data in the vector
          elements->push_back(i);

      std::cout << elements->size() << std::endl;
        std::cout << elements->at(0) << std::endl;
        std::cout << elements->at(30) << std::endl;

      //Launch child process
      std::string s(argv[0]); s += " child ";
      if(0 != std::system(s.c_str()))
         return 1;

      std::cout << elements->size() << std::endl;
      std::cout << elements->at(0) << std::endl;
      std::cout << elements->at(30) << std::endl;

   }
   else{ //Child process
      //Open the managed segment
       managed_mapped_file segment(open_only, "./a_MySharedMemory.txt");
       const ShmemAllocator alloc_inst (segment.get_segment_manager());

       typedef boost::interprocess::managed_shared_ptr<MyVector, boost::interprocess::managed_mapped_file>::type map_pointer_type;

       map_pointer_type elements = boost::interprocess::make_managed_shared_ptr(
            segment.find_or_construct<MyVector>("some-vector")      //object name
            (alloc_inst),
            segment
    );

//       MyVector* elements = segment.find_or_construct<MyVector>("some-vector")      //object name
//                      (alloc_inst);

      //Use vector in reverse order
      std::sort(elements->rbegin(), elements->rend());

   }

   return 0;
}

In this case the vector has size == 0 in the parent process after the child process dies. If I use the raw pointer (MyVector* elements = segment.find_or_construct...), then the collection can be used as expected in the parent process.

So I still have my doubts about the behavior of the shared pointer


Solution

  • I managed to solve the problem. The issue was related to how the shared pointers are made

    If you call boost::interprocess::make_managed_shared_ptr N times to created a shared pointer to an object in shared memory, you get essentially different (completely unrelated) shared pointers to the same object in the shared memory. I was doing that in the Parent and the Child process, and then when the Child process died, the use count went to 0 and erased the pointed object (the map).

    The solution was to create a named shared pointer explicitly.

    typedef boost::interprocess::allocator<void, segment_manager_type>  void_allocator_type;
    typedef boost::interprocess::deleter<map_type, segment_manager_type>  map_deleter_type;
    typedef boost::interprocess::shared_ptr<map_type, void_allocator_type, map_deleter_type> map_pointer_type;
    
    segment_manager_type* segment_manager = segment->get_segment_manager();
    pair_allocator_type alloc_inst(segment_manager);
    
    segment->construct<map_pointer_type>("elements ptr")(
                segment->construct<map_type>("elements")(std::less<IdClass>(), alloc_inst),      //object to own
                void_allocator_type(segment_manager),  //allocator
                map_deleter_type(segment_manager)         //deleter
                );
    

    And then in the Child process to only get a copy of this pointer

    map_pointer_type elements = *segment->find<map_pointer_type>("elements ptr").first;