1

I'm currently maintaining a C++ REST Server developed in C++. It provides some features like middleware and routes.

Routes are stored inside an inner structure of the router class:

//! 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;
    };
};

Everything works well during the execution: routes can be configured and added to the router and if someone request the server on an existing route, callback are executed and the server send a response as expected.

Here is an example of a route configuration where we create a route /admin/cameras/:cam_id for an HTTP DELETE request:

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

In this example, admin_service::remove_camera is a member function and service is a shared_ptrcontaining a pointer to an admin_serviceobject. If someone requests this route, admin_service::remove_camera is called.

However, the server segfaults at the end of the execution (when we exit the server).

I have traced the origin of the segfault, and it comes from the destructor of... std::function. To be more precise, it happened during the destruction of one of the std::function contained in the std::pairs get, post, put and del.

I can say this because, when I put the following debug code:

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;
};

I got the following output:

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

I can't figure how a std::function can segfault during destruction or assignment...

I first thought that, maybe, std::function took a reference to the std::shared_ptr service instead of taking it by value and deleted the raw pointer it contains more than once. But when I put some debug output, I can see that the shared_ptr counter is incremented after the call to router.del.

Does someone have some idea about this issue?

Simon Ninon
  • 2,371
  • 26
  • 43
  • `I can't figure how a std::function can segfault during destruction or assignment` Go one step further and isolate the line of code or step that caused the segfault within the destructor. Then use that as a starting point. – PaulMcKenzie Apr 17 '15 at 15:32
  • This is what I did in the second code quote: I just changed the value contained in the `std::function` (`del.first = nullptr`). It causes a segfault during the assignation. This assignment operator call may have some operations which are similar to the operations of the destructor (like destructing internal attributes). But I have now idea of how to go further now, except opening the standard library source code. – Simon Ninon Apr 17 '15 at 16:10
  • Try to give raw pointer to `std::function` instead of `shared_ptr`. Like this `... std::bind( &admin_service::remove_camera, service.get(), std::placeholders::_1)` – borisbn Apr 17 '15 at 16:24
  • The result is the same. I have also tried to pass directly an object (`std::bind(&admin_service::remove_camera, *(service.get()), std::placeholders::_1)`) but the segfault continue to happen at the same location. And obviously, when I remove the routes configuration, the program exits properly. – Simon Ninon Apr 17 '15 at 16:37
  • @SimonNinon Is the `del` instance still valid? That's the only way I can see where a pointer assignment would crash, and that is the underlying object holding the pointer is invalid. – PaulMcKenzie Apr 17 '15 at 17:12
  • valgrind is your friend – pm100 Apr 17 '15 at 18:25
  • were you ever able to figure this out ? – rstr1112 Jan 14 '21 at 11:42

1 Answers1

1

It looks like a memory corruption issue. I would try:

  • Running it under valgrind. If possible, because valgrind emulates CPU so that the application runs ~50 times slower under a single virtual thread. And errors caused by race conditions may not manifest under valgrind.
  • Use gcc/clang address sanitizer by re-compiling and linking with -fsanitize=address command line option. This one works amazingly because of its relatively low overhead: a program instrumented with Address Sanitizer would typically run twice as slow as its non-instrumented counterpart and would typically consume 20% more memory.
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271