Segfault в деструкторе std::function

В настоящее время я поддерживаю C++ REST Server, разработанный на C++. Он предоставляет некоторые функции, такие как промежуточное ПО и маршруты.

Маршруты хранятся во внутренней структуре класса маршрутизатора:

//! The http router
//!
//! allow us to parse route on a server using regex to match the good route for a given url
//! and extract the possible url variables
class router {
private:
    //! routes datas
    //!
    //! contains:
    //! * the regex to parse the routes
    //! * an std::vector with the list of variable inside the routes
    //! * 4 std::function, one for each REST methods
    struct rest_routes {
        std::regex regex;
        std::vector<std::string> vars_name;
        std::pair<std::function<http::response(http::request&)>, std::vector<std::string>> get;
        std::pair<std::function<http::response(http::request&)>, std::vector<std::string>> post;
        std::pair<std::function<http::response(http::request&)>, std::vector<std::string>> put;
        std::pair<std::function<http::response(http::request&)>, std::vector<std::string>> del;
    };
};

Во время выполнения все работает хорошо: маршруты можно настроить и добавить в маршрутизатор, и если кто-то запрашивает сервер по существующему маршруту, выполняется обратный вызов, и сервер отправляет ответ, как и ожидалось.

Вот пример конфигурации маршрута, где мы создаем маршрут /admin/cameras/:cam_id для запроса HTTP DELETE:

// delete a camera
router.del("/admin/cameras/:cam_id",
           std::bind(&admin_service::remove_camera, service, std::placeholders::_1));

В этом примере admin_service::remove_camera — это функция-член, а service — это shared_ptrсодержащий указатель на admin_serviceобъект. Если кто-то запрашивает этот маршрут, вызывается admin_service::remove_camera.

Однако сервер segfaults в конце выполнения (когда мы выходим из сервера).

Я проследил происхождение segfault, и он исходит от деструктора... std::function. Точнее, это произошло во время уничтожения одного из std::function, содержащихся в std::pairс get, post, put и del.

Я могу сказать это, потому что, когда я помещаю следующий код отладки:

struct rest_routes {

    ~rest_routes() {
        std::cout << "BEGIN DTOR rest_routes" << std::endl;
        std::cout << "BEGIN get" << std::endl;
        get.first = nullptr;
        std::cout << "END get" << std::endl;
        std::cout << "BEGIN post" << std::endl;
        post.first = nullptr;
        std::cout << "END post" << std::endl;
        std::cout << "BEGIN put" << std::endl;
        put.first = nullptr;
        std::cout << "END put" << std::endl;
        std::cout << "BEGIN del" << std::endl;
        del.first = nullptr;
        std::cout << "END del" << std::endl;
        std::cout << "END DTOR rest_routes" << std::endl;
    }

    std::regex regex;
    std::vector<std::string> vars_name;
    std::pair<std::function<http::response(http::request&)>, std::vector<std::string>> get;
    std::pair<std::function<http::response(http::request&)>, std::vector<std::string>> post;
    std::pair<std::function<http::response(http::request&)>, std::vector<std::string>> put;
    std::pair<std::function<http::response(http::request&)>, std::vector<std::string>> del;
};

Я получил следующий вывод:

BEGIN DTOR rest_routes
BEGIN get
END get
BEGIN post
END post
BEGIN put
END put
BEGIN del
Segmentation Fault

Я не могу понять, как std::function может segfault во время уничтожения или назначения...

Сначала я подумал, что, может быть, std::function взял ссылку на std::shared_ptr service вместо того, чтобы брать его по значению, и удалил необработанный указатель, который он содержит, более одного раза. Но когда я помещаю отладочный вывод, я вижу, что счетчик shared_ptr увеличивается после вызова router.del.

Есть ли у кого-нибудь идеи по этому вопросу?


person Simon Ninon    schedule 17.04.2015    source источник
comment
I can't figure how a std::function can segfault during destruction or assignment Сделайте еще один шаг и изолируйте строку кода или шаг, вызвавший segfault в деструкторе. Затем используйте это как отправную точку.   -  person PaulMcKenzie    schedule 17.04.2015
comment
Вот что я сделал во второй цитате кода: я просто изменил значение, содержащееся в std::function (del.first = nullptr). Это вызывает segfault во время назначения. Этот вызов оператора присваивания может иметь некоторые операции, аналогичные операциям деструктора (например, удаление внутренних атрибутов). Но теперь у меня есть идея, как пойти дальше, кроме как открыть исходный код стандартной библиотеки.   -  person Simon Ninon    schedule 17.04.2015
comment
Попробуйте указать необработанный указатель на std::function вместо shared_ptr. Вот так ... std::bind( &admin_service::remove_camera, service.get(), std::placeholders::_1)   -  person borisbn    schedule 17.04.2015
comment
Результат тот же. Я также пытался передать напрямую объект (std::bind(&admin_service::remove_camera, *(service.get()), std::placeholders::_1)), но ошибка сегментации продолжает происходить в том же месте. И, очевидно, когда я удаляю конфигурацию маршрутов, программа завершается правильно.   -  person Simon Ninon    schedule 17.04.2015
comment
@SimonNinon Экземпляр del все еще действителен? Это единственный способ увидеть, где произойдет сбой присваивания указателя, и это базовый объект, содержащий указатель, недействителен.   -  person PaulMcKenzie    schedule 17.04.2015
comment
вальгринд твой друг   -  person pm100    schedule 17.04.2015
comment
ты когда-нибудь мог это понять?   -  person rstr1112    schedule 14.01.2021


Ответы (1)


Похоже на проблему с повреждением памяти. Я бы попробовал:

  • Запустите его под valgrind. Если возможно, потому что valgrind эмулирует ЦП, поэтому приложение работает примерно в 50 раз медленнее в одном виртуальном потоке. И ошибки, вызванные условиями гонки, могут не проявляться в valgrind.
  • Используйте gcc/clang дезинфицирующее средство для адресов, повторно скомпилировав и связав с параметром командной строки -fsanitize=address. Это работает потрясающе из-за относительно низких накладных расходов: программа, оснащенная Address Sanitizer, обычно работает в два раза медленнее, чем ее неинструментальная версия, и потребляет на 20 % больше памяти.
person Maxim Egorushkin    schedule 17.04.2015