Ошибка сегментации при запросе Rtree, полученного из файла с отображением памяти

Я весьма озадачен. Рассмотрим следующий код, слегка адаптированный из http://www.boost.org/doc/libs/1_57_0/libs/geometry/doc/html/geometry/spatial_indexes/rtree_examples/index_stored_in_mapped_file_using_boost_interprocess.html :

#include <boost/filesystem.hpp>

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/index/rtree.hpp>

#include <boost/interprocess/managed_mapped_file.hpp>

namespace bg = boost::geometry;
namespace bgi = boost::geometry::index;
namespace bi = boost::interprocess;

typedef bg::model::point<float, 2, bg::cs::cartesian> point; 
typedef std::pair<point, int> value_t; // **
typedef bgi::linear<32, 8> params_t;
typedef bgi::indexable<value_t> indexable_t;
typedef bgi::equal_to<value_t> equal_to_t;
typedef bi::allocator<value_t, bi::managed_mapped_file::segment_manager> allocator_t;
typedef bgi::rtree<value_t, params_t, indexable_t, equal_to_t, allocator_t> rtree_t;

using namespace boost::filesystem;

int main(int argc, char * argv[])
{   

    std::string indexFile = "/home/jerome/proteome/index_tree.dat";
    remove(indexFile); 

    int mmfSize = 1200000;

    {
        bi::managed_mapped_file file(bi::open_or_create,indexFile.c_str(), mmfSize);
        allocator_t alloc(file.get_segment_manager());
        rtree_t * rtree_ptr = file.find_or_construct<rtree_t>("rtree")(params_t(), indexable_t(), equal_to_t(), alloc);

        std::cout << "Indexing ... " << std::endl;
        for(int i = 0; i < 1001; i++)
        {
            rtree_ptr->insert(std::make_pair(point(i,i),i*i));  
        }

        std::cout << "Indexing done." << std::endl;
    }

    {
        bi::managed_mapped_file file(bi::open_or_create,indexFile.c_str(), mmfSize);
        allocator_t alloc(file.get_segment_manager());
        rtree_t * rtree_ptr = file.find_or_construct<rtree_t>("rtree")(params_t(), indexable_t(), equal_to_t(), alloc);

        std::cout << "Tree loaded, contains " << rtree_ptr->size() << " elements" << std::endl;

        // query point
        point pt(2, 1);

        std::vector<value_t> results;
        rtree_ptr->query(bgi::nearest(pt, 3), std::back_inserter(results));
        std::cout << "Query performed" << std::endl;    

        for (int i = 0; i < results.size(); i++)
        {
            value_t v = results[i];
            std::cout << "Found the point " << v.second << " at a distance of " << bg::distance(v.first,pt) << std::endl; 
        }
    }

}

Это прекрасно работает. Он создает Rtree и сохраняет его в файле с отображением памяти, а затем извлекает и запрашивает его, без проблем. Однако, как только я пытаюсь разбить этот файл на два (где дерево строится в одном файле, а запрашивается в другом), запросы больше не работают! («...» в приведенном ниже коде относится ко всем включениям и определениям типов из исходного примера, которые были точно скопированы в два файла, но удалены здесь для ясности).

Файл сборки:

...
int main(int argc, char * argv[])
{   

    std::string indexFile = "/home/jerome/proteome/index_tree.dat";
    remove(indexFile); 

    int mmfSize = 1200000;

    {
        bi::managed_mapped_file file(bi::open_or_create,indexFile.c_str(), mmfSize);
        allocator_t alloc(file.get_segment_manager());
        rtree_t * rtree_ptr = file.find_or_construct<rtree_t>("rtree")(params_t(), indexable_t(), equal_to_t(), alloc);

        std::cout << "Indexing ... " << std::endl;
        for(int i = 0; i < 1001; i++)
        {
            rtree_ptr->insert(std::make_pair(point(i,i),i*i));  
        }

        std::cout << "Indexing done." << std::endl;
    }

}

Файл запроса:

...

int main(int argc, char * argv[])
{   

    std::string indexFile = "/home/jerome/proteome/index_tree.dat";

    int mmfSize = 1200000;

    {
        bi::managed_mapped_file file(bi::open_or_create,indexFile.c_str(), mmfSize);
        allocator_t alloc(file.get_segment_manager());
        rtree_t * rtree_ptr = file.find_or_construct<rtree_t>("rtree")(params_t(), indexable_t(), equal_to_t(), alloc);

        std::cout << "Tree loaded, contains " << rtree_ptr->size() << " elements" << std::endl;

        // query point
        point pt(2, 1);

        std::vector<value_t> results;
        rtree_ptr->query(bgi::nearest(pt, 3), std::back_inserter(results));
        std::cout << "Query performed" << std::endl;    

        for (int i = 0; i < results.size(); i++)
        {
            value_t v = results[i];
            std::cout << "Found the point " << v.second << " at a distance of " << bg::distance(v.first,pt) << std::endl; 
        }
    }

}

(Команда remove() предотвращает перезапись существующего файла и каждый раз начинает заново.)

Строительный код работает нормально, но код запроса не работает:

Tree loaded, contains 1001 elements Segmentation fault (core dumped)

Любые идеи? Я ожидаю, что каким-то образом, когда поиск дерева будет выполнен, чего-то не хватает, поэтому полученное дерево будет искажено и приведет к проблемам с памятью при запросе. Но почему это происходит, когда он находится в двух разных файлах, а не в одном и том же файле, а в двух разных областях? Разве он не должен иметь точно такое же поведение?

Редактировать: я использовал boost 1.54.


person jerorx    schedule 27.02.2015    source источник
comment
Woooa, много кода здесь ... есть ли шанс получить более краткий MCV?   -  person Paul Evans    schedule 27.02.2015
comment
@PaulEvans Учитывая, что я действительно понятия не имею, в чем причина проблемы, я не могу определить, какие части кода определенно не имеют значения, поэтому я должен поместить большую его часть. Я попытался удалить лишние части для ясности.   -  person jerorx    schedule 27.02.2015


Ответы (2)


Внутри R-дерево может использовать различные типы узлов, хотя интерфейс их определения и выбора не документирован и, вероятно, никогда не будет документирован. В Boost 1.56 тип узлов по умолчанию был изменен на вариантный именно из-за проблемы, с которой вы столкнулись.

Таким образом, чтобы без проблем использовать rtree с Interprocess, вы можете:

См. также это обсуждение: http://boost-geometry.203548.n3.nabble.com/rtree-crash-when-used-with-inter-process-td4026037.html

В конце приведенного выше обсуждения упоминается еще одно решение, но оно более сложное и зависит от внутреннего устройства библиотеки. В какой-то момент он может перестать компилироваться (на самом деле он должен работать только для Boost 1.56 и ниже). Но если бы вы использовали его, вашей программе потребовался бы только официальный Boost для компиляции без каких-либо модификаций.

person Adam Wulkiewicz    schedule 05.03.2015
comment
Спасибо! Я использовал boost 1.54; простой переход на 1.57, кажется, решает проблему, по крайней мере, в игрушечном примере. - person jerorx; 09.03.2015

Этот дузи.

Следуя коду в отладчике, что на самом деле сохраняется в узлах R-дерева?

Получается, что реализация R-дерева хранит объект типа boost::geometry::index::detail::rtree::dynamic_leaf<...>. Это происходит от boost::geometry::index::detail::rtree::dynamic_node<...>. Этот объект правильно размещается в куче в сопоставленном файле, сохраняя данные узла вместе с указателем vtable объекта. Виртуальная таблица создается один раз для исполняемого файла, но для чтения она находится в другом месте, чем для записи. Когда в считывателе вызывается виртуальный метод dynamic_node, адрес для перехода ищется из адреса vtable, который был сохранен писателем, который, с точки зрения читателя, находится где-то в гиперпространстве.

Вот откуда крах!

Обойти это непросто: boost::interprocess явно не поддерживает совместное использование динамических объектов.

person halfflat    schedule 27.02.2015
comment
Спасибо! Но разве это не удивительно? Библиотека boost предоставляет способ хранения R-деревьев в файлах с отображением памяти, но, видимо, оказывается, что это не работает для динамических объектов, а R-деревья в boost — это именно то, что нужно. В чем тогда смысл? Действительно ли полезен MMF, если его можно использовать только во время одного запуска программы? Как тогда использовать один для длительного постоянного хранения? :/ - person jerorx; 02.03.2015
comment
Ответ на мой собственный комментарий: в документации по boost файлы с отображением памяти появляются в контексте обмена данными между процессами, что действительно необходимо только во время одного запуска программы. Я думаю, он не предназначен для постоянного хранения. - person jerorx; 02.03.2015
comment
Я также считаю позорным то, что в реализации R-дерева используются динамические объекты, поскольку это исключает такого рода совместное использование между процессами с использованием общей памяти или отображаемых в память файлов. Файлы с отображением памяти могут использоваться для постоянного хранения данных в родном двоичном представлении, но эти данные, как мы видели, не могут иметь летающие указатели vtable. Я не знаю вашего конечного приложения, но, возможно, стоит рассмотреть возможность использования пользовательской реализации R-дерева? - person halfflat; 02.03.2015