0

My understanding is that this code can keep a vector in a file.

using Alloc = boost::interprocess::allocator<int32_t, boost::interprocess::managed_mapped_file::segment_manager>;
using Vec = boost::interprocess::vector<int32_t, Alloc>;

void f() {
    boost::interprocess::managed_mapped_file seg(boost::interprocess::open_or_create, "data.dat", 1024);
    auto vec = seg.find_or_construct<Vec>("vec")(seg.get_segment_manager());

    // ......
}

However, when the vector tried to get too many bytes it throws bad_alloc exception.

I'm not able to predict how many bytes are required so I came up with growing the file when free_memory is reducing. However sometimes assertion failed occurs when growing.
Here is minimum code.

using Alloc = allocator<int32_t, managed_mapped_file::segment_manager>;
using Vec = vector<int32_t, Alloc>;

void test(int, int);
int main()
{   
    test(4096, 4); // Change this value if not reproduced
    return 0;
}

void test(int initialBytes, int extraBytes) {
    fprintf(stderr, "test(%d, %d)\n", initialBytes, extraBytes);

    remove("data.dat");

    auto seg = make_unique<managed_mapped_file>(open_or_create, "data.dat", initialBytes);
    auto vec = seg->find_or_construct<Vec>("vec")(seg->get_segment_manager());
    fprintf(stderr, "vec->capacity=%ld\n", vec->capacity());
    fprintf(stderr, "seg.get_free_memory()=%ld\n\n", seg->get_free_memory());

    seg = nullptr;
    fprintf(stderr, "tag1\n");
    boost::interprocess::managed_mapped_file::grow("data.dat", extraBytes);

    fprintf(stderr, "tag2\n");
    seg = make_unique<managed_mapped_file>(open_only, "data.dat");

    fprintf(stderr, "tag3\n");
    vec = seg->find<Vec>("vec").first;

    fprintf(stderr, "tag4\n");

    fprintf(stderr, "vec->capacity=%ld\n", vec->capacity());
    fprintf(stderr, "seg.get_free_memory()=%ld\n\n", seg->get_free_memory());

}

More smaller

void test(int initialBytes, int extraBytes) {
    remove("data.dat");
    auto seg = std::make_unique<managed_mapped_file>(open_or_create, "data.dat", initialBytes);
    seg = nullptr;
    managed_mapped_file::grow("data.dat", extraBytes);
}

Ask: How can I store elastic vector without assuming maximum size?


I misunderstood grow - it's static function. So I fixed the code. Still fails the assertion.


I found assertion fails when 4 <= extraBytes && extraBytes < 24

v..snow
  • 189
  • 7
  • By writing your own (complex) implementation? Shared memory mappings are always defined in terms of finite number of pages involved. To achieve truly dynamic behavior, you will have to create additional mappings. This is not really different from how "normal" malloc works (at some point in time it also has to "map" memory into process space to be able to fulfill further allocations). – oakad Jan 09 '20 at 01:34
  • @oakad So what `grow` does do? Doesn't it close the mmf and expand? – v..snow Jan 09 '20 at 04:16
  • 1
    In any shared memory situation more than one party is involved. All of those parties have to synchronize somehow regarding the shared memory extent. As to the `grow`, boost documentation is pretty clear: it only has effect if memory segment in question is not used by anybody (0 interested parties). – oakad Jan 09 '20 at 04:27
  • 1
    https://www.boost.org/doc/libs/1_72_0/doc/html/interprocess/managed_memory_segments.html#interprocess.managed_memory_segments.managed_memory_segment_advanced_features.growing_managed_memory – oakad Jan 09 '20 at 04:29
  • @oakad When I found `grow` is static I thought `seg` must be released. Then I fixed the code but nothing changed. I cannot find difference between my code and sample at the link except shared memory vs mmf. – v..snow Jan 09 '20 at 04:40
  • @oakad How to close? Isn't `~managed_mapped_file` enough? – v..snow Jan 09 '20 at 04:50

1 Answers1

1

You can see working example below. This program creates small memory mapped file, creates vector container and then fills vector by push_back. If it is impossible to make next push_back, control is transferred to catch block. In this block the file is closed, the function grow is called, memory mapped file is opened, the vector is founded and the push_back is repeated.

#include <iostream>
#define BOOST_DATE_TIME_NO_LIB
#include <boost/interprocess/managed_mapped_file.hpp>
#include <boost/interprocess/file_mapping.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <string>

namespace IP = boost::interprocess;
template <typename T> using MyVectorT = IP::vector < T, IP::allocator<T, IP::managed_mapped_file::segment_manager>>;

int main(int )
{
    const char *FileName = "file.bin";
    const std::size_t FileSize = 1000;
    using MyVector = MyVectorT<size_t>;
    try
    {
        IP::remove_file_on_destroy tmp{ FileName };
        IP::managed_mapped_file segment(IP::create_only
            , FileName      //Mapped file name
            , FileSize);    //Mapped file initial size

        MyVector *myvector = segment.construct<MyVector>("MyVector")(segment.get_segment_manager());
        for (size_t i = 0; i < 100000; ++i)  //Insert data in the vector
        {
            bool push_failure = true;
            do
            {
                try
                {
                    myvector->push_back(i);
                    push_failure = false;       //success of push_back
                }
                catch (const IP::bad_alloc &)   //memory mapped file is too small for vector
                {
                    const size_t grow_size = std::max<size_t>(FileSize, 2 * (myvector->size() + 1) * sizeof(MyVector::value_type));   //estimate memory for new vector capacity
                    std::cout << "segment size = " << segment.get_size() << " Vector capacity = " << myvector->capacity() << " grow_size = " << grow_size;
                    //free memory mapped file
                    segment.flush();    
                    segment.~basic_managed_mapped_file();
                    IP::managed_mapped_file::grow(FileName, grow_size);
                    new (&segment) IP::managed_mapped_file(IP::open_only, FileName);
                    std::cout << " -> new segment size = " << segment.get_size() << std::endl;
                    myvector = segment.find<MyVector>("MyVector").first;
                    push_failure = true;        //try push_back again!!!
                }
            } while (push_failure);
        }
        std::cout << "Vector size =" << myvector->size() << "\n";
        for (size_t i = 0; i < 100000; ++i)
        {
            if ((*myvector)[i] != i)
            {
                std::cout << "vector error!!! i = " << i << " vector[i] = " << (*myvector)[i] << std::endl;
            }
        }
    }
    catch (const std::exception &e)
    {
        std::cout << "Error " << e.what() << std::endl;
    }
    catch (...)
    {
        std::cout << "Error";
    }
    return 0;
}
SergV
  • 1,269
  • 8
  • 20
  • Why `grow_size` is like that? – v..snow Jan 09 '20 at 10:08
  • @v..snow. When a vector grows in size, it usually allocates a new memory 2 times its current size. So this is my optimization of the number of file extensions. – SergV Jan 09 '20 at 11:52
  • Got. As I edited my question, it seems that the assertion pass when extra bytes >= 24. I'm uneasy but this would work, without guarantee... – v..snow Jan 09 '20 at 12:56