oatpp-swagger icon indicating copy to clipboard operation
oatpp-swagger copied to clipboard

crash on exit

Open mheyman opened this issue 3 years ago • 5 comments

Not sure exactly what is going on to cause the crash when using the swagger controller but the symptom is memory corruption in the environment causing a SEGV when the ObjectMapper component gets cleaned up during shutdown.

This occurs when creating a swagger controller without first creating a Generator::Config component.

To get past the issue in our code, we've done a:

auto generator_config = 
    oatpp::base::Environment::Component<
        std::shared_ptr<
            oatpp::swagger::Generator::Config>>(
                std::make_shared<oatpp::swagger::Generator::Config>());

prior to doing the oatpp::swagger::Controller::createShared(doc_endpoints).

I believe changing https://github.com/oatpp/oatpp-swagger/blob/d0495421914cc2f9ed0a4c625d215c3d140a339f/src/oatpp-swagger/Controller.hpp#L89 to something like:

generatorConfig = 
    oatpp::base::Environment::Component<std::shared_ptr<Generator::Config>>(
        std::make_shared<Generator::Config>()).getObject();

will make the SEGV go away for all users.

That line gets called when no existing Generator::Config component is found in the environment.

mheyman avatar Nov 05 '20 15:11 mheyman

Hello @mheyman ,

This looks weird as Generator::Config is not related to ObjectMapper component by any means. Also, after swagger document is generated(during controller construction) there is no need in Generator::Config any more.

I believe the cause of the crash is something else, and the fix by creating a generator config component is most probably some kind of magical coincidence.

Can you please share your App.cpp where you create components, and where you do application shutdown?

lganzzzo avatar Nov 05 '20 15:11 lganzzzo

I agree that it is strange. Especially since the error occurs deep in stl and I've run with MSVC's stl debugging that should find the problem.

I ripped out a unit test I used to figure out the work around. It should be close to compilable - there may be missing include files at the top because I had to sanitize the source from the real source in order to put it here (I quickly made the dummy_controller instead of using our "real" controller).

Also, there are object instances in the unit test that probably don't have to be components (they are not components in the real code and that code no longer crashes given the "fix"). They are still there as components in the unit test from the diagnosing phase...

Just below the // swagger_controller comment is the line that, when comment it out, reliably gives me the crash at the end of the test.

#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include <gtest/gtest.h>
#include <oatpp/network/ConnectionProvider.hpp>
#include <oatpp/network/Server.hpp>
#include <oatpp/network/tcp/server/ConnectionProvider.hpp>
#include <oatpp/parser/json/mapping/Deserializer.hpp>
#include <oatpp/parser/json/mapping/ObjectMapper.hpp>
#include <oatpp/parser/json/mapping/Serializer.hpp>
#include <oatpp/web/server/api/ApiController.hpp>
#include <oatpp/web/protocol/http/encoding/Chunked.hpp>
#include <oatpp-swagger/Controller.hpp>
#include <oatpp-swagger/Model.hpp>
#include <oatpp-swagger/Resources.hpp>

namespace web_service_base_test
{

    // dummy_controller class to demo the swagger crash
#include "oatpp/core/macro/codegen.hpp"
#include OATPP_CODEGEN_BEGIN(ApiController) ///< Begin ApiController codegen section
    class dummy_controller : public oatpp::web::server::api::ApiController {
    public:

        dummy_controller(OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper) /* Inject object mapper */)
            : oatpp::web::server::api::ApiController(objectMapper)
        {}

        ENDPOINT("GET", "/", root) {
            return createResponse(Status::CODE_200, "Hello World!");
        }

        // TODO - more endpoints here

    };
#include OATPP_CODEGEN_END(ApiController) ///< End ApiController codegen section

    TEST(WebApp, OatppEnvironment)
    {
        static const std::string name(fmt::format("{}.{}", test_info_->test_suite_name(), test_info_->name()));
        oatpp::base::Environment::init();

    	// swagger_component
        auto const swagger_document_info = 
            oatpp::base::Environment::Component<
				std::shared_ptr<oatpp::swagger::DocumentInfo>>(
                    oatpp::swagger::DocumentInfo::Builder()
                    .setTitle("title")
                    .setDescription("description")
                    .setVersion("version")
                    .setContactName("contact name")
                    .setContactUrl("contact url")
                    .setLicenseName("All Rights Reserved.")
                    .addServer("http://{localhost}:{45678}", "description")
                    .build());
        auto const resources = oatpp::base::Environment::Component<std::shared_ptr<oatpp::swagger::Resources>>(
            oatpp::swagger::Resources::loadResources("web_resources_dir"));

    	// serializer_config
        auto serializer_config = 
			oatpp::base::Environment::Component<
				std::shared_ptr<oatpp::parser::json::mapping::Serializer::Config>>(
                    oatpp::parser::json::mapping::Serializer::Config::createShared());

    	// deserializer_config
        auto deserializer_config = 
            oatpp::base::Environment::Component<
				std::shared_ptr<oatpp::parser::json::mapping::Deserializer::Config>>(
                    oatpp::parser::json::mapping::Deserializer::Config::createShared());

    	// server_connection_provider
        auto server_connection_provider = 
            oatpp::base::Environment::Component<
				std::shared_ptr<oatpp::network::ServerConnectionProvider>>(
                    std::make_shared<oatpp::network::tcp::server::ConnectionProvider>(
            oatpp::network::Address{ "localhost", 56789, oatpp::network::Address::IP_4 }));

    	// http_router
        auto http_router = 
            oatpp::base::Environment::Component<
				std::shared_ptr<oatpp::web::server::HttpRouter>>(
                    oatpp::web::server::HttpRouter::createShared());

    	// server_connection_handler
        auto server_connection_handler = 
            oatpp::base::Environment::Component<
				std::shared_ptr<oatpp::network::ConnectionHandler>>(
                    std::make_shared<oatpp::web::server::HttpConnectionHandler>(http_router.getObject()));

    	// api_object_mapper
        auto object_mapper = 
            oatpp::base::Environment::Component<
				std::shared_ptr<oatpp::data::mapping::ObjectMapper>>(
					oatpp::parser::json::mapping::ObjectMapper::createShared(
						serializer_config.getObject(), deserializer_config.getObject()));

    	// user_controller
        auto user_controller = 
            oatpp::base::Environment::Component<std::shared_ptr<dummy_controller>>(
                std::make_shared<dummy_controller>());

    	// doc_endpoints
        auto doc_endpoints = 
            oatpp::base::Environment::Component<
				std::shared_ptr<oatpp::swagger::Controller::Endpoints>>(
                    oatpp::swagger::Controller::Endpoints::createShared());
        std::shared_ptr<oatpp::collection::LinkedList<std::shared_ptr<oatpp::web::server::api::Endpoint>>> const endpoints =
            user_controller.getObject()->getEndpoints();
        doc_endpoints.getObject()->pushBackAll(endpoints);

    	// swagger_controller
        auto generator_config = 
            oatpp::base::Environment::Component<
				std::shared_ptr<
					oatpp::swagger::Generator::Config>>(
						std::make_shared<oatpp::swagger::Generator::Config>());

        auto swagger_controller = 
            oatpp::base::Environment::Component<
				std::shared_ptr<oatpp::swagger::Controller>>(
                    oatpp::swagger::Controller::createShared(doc_endpoints.getObject()));
        swagger_controller.getObject()->addEndpointsToRouter(http_router.getObject());

    	// server
        auto server = oatpp::network::Server::createShared(server_connection_provider.getObject(), server_connection_handler.getObject());
    }
}

mheyman avatar Nov 06 '20 22:11 mheyman

Hey @mheyman ,

I've played with the code, but I couldn't reproduce the exact crash. The closest crash I managed to get is by disabling C++ exceptions with -fno-exceptions.- In which case I have an exception thrown when (as you've said) commenting out the following line:

        auto generator_config = 
            oatpp::base::Environment::Component<
				std::shared_ptr<
					oatpp::swagger::Generator::Config>>(
						std::make_shared<oatpp::swagger::Generator::Config>());

Please note, I've removed the gtest and fmt, and reduced the code a bit - just to have less text before the eyes (hopefully I didn't remove the cause of the crash).

Systems I tried it on - Ubuntu 16.04, Mac, Windows (on Azure CI)

Here is the code:

#include <oatpp/network/ConnectionProvider.hpp>
#include <oatpp/network/Server.hpp>
#include <oatpp/network/tcp/server/ConnectionProvider.hpp>
#include <oatpp/parser/json/mapping/Deserializer.hpp>
#include <oatpp/parser/json/mapping/ObjectMapper.hpp>
#include <oatpp/parser/json/mapping/Serializer.hpp>
#include <oatpp/web/server/api/ApiController.hpp>
#include <oatpp/web/protocol/http/encoding/Chunked.hpp>
#include <oatpp/core/macro/codegen.hpp>

#include <oatpp-swagger/Controller.hpp>
#include <oatpp-swagger/Model.hpp>
#include <oatpp-swagger/Resources.hpp>

namespace web_service_base_test {

// dummy_controller class to demo the swagger crash
#include OATPP_CODEGEN_BEGIN(ApiController) ///< Begin ApiController codegen section

class dummy_controller : public oatpp::web::server::api::ApiController {
public:

  dummy_controller(OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper) /* Inject object mapper */)
    : oatpp::web::server::api::ApiController(objectMapper)
  {}

  ENDPOINT("GET", "/", root) {
    return createResponse(Status::CODE_200, "Hello World!");
  }

};

#include OATPP_CODEGEN_END(ApiController) ///< End ApiController codegen section

void test() {

  auto swaggerDocInfo = oatpp::swagger::DocumentInfo::Builder()
    .setTitle("title")
    .setDescription("description")
    .setVersion("version")
    .setContactName("contact name")
    .setContactUrl("contact url")
    .setLicenseName("All Rights Reserved.")
    .addServer("http://{localhost}:{45678}", "description")
    .build();

  auto const resources = oatpp::base::Environment::Component<std::shared_ptr<oatpp::swagger::Resources>>(
    oatpp::swagger::Resources::loadResources(OATPP_SWAGGER_RES_PATH));

  // server_connection_provider
  auto server_connection_provider = std::make_shared<oatpp::network::tcp::server::ConnectionProvider>(
    oatpp::network::Address{ "localhost", 56789, oatpp::network::Address::IP_4 });

  // http_router
  auto http_router = oatpp::web::server::HttpRouter::createShared();

  // server_connection_handler
  auto server_connection_handler = std::make_shared<oatpp::web::server::HttpConnectionHandler>(http_router);

  // api_object_mapper
  auto object_mapper =
    oatpp::base::Environment::Component<
      std::shared_ptr<oatpp::data::mapping::ObjectMapper>>(
      oatpp::parser::json::mapping::ObjectMapper::createShared());

  // user_controller
  auto user_controller = std::make_shared<dummy_controller>();

  // doc_endpoints
  auto doc_endpoints = oatpp::swagger::Controller::Endpoints::createShared();

  doc_endpoints->pushBackAll(user_controller->getEndpoints());

  // swagger_controller
//  auto generator_config =
//    oatpp::base::Environment::Component<
//      std::shared_ptr<
//        oatpp::swagger::Generator::Config>>(
//      std::make_shared<oatpp::swagger::Generator::Config>());

  auto swagger_controller = oatpp::swagger::Controller::createShared(doc_endpoints, swaggerDocInfo);
  swagger_controller->addEndpointsToRouter(http_router);

  // server
  auto server = oatpp::network::Server::createShared(server_connection_provider, server_connection_handler);
}

}

int main() {

  // init env
  oatpp::base::Environment::init();

  // run test
  web_service_base_test::test();

  // Assert there is no leaking objects.
  OATPP_ASSERT(oatpp::base::Environment::getObjectsCount() == 0);

  // Destroy env. Should be called after all components are destroyed.
  oatpp::base::Environment::destroy();

  return 0;
}

Edit

This code is pushed to the dummy branch of crud-example project (tests) You may clone the code and run the tests.

To reproduce the crash - add the following to the CMakeLists.txt

if (MSVC)
	target_compile_options(crud-test PUBLIC /EHs-c-)
else()
	target_compile_options(crud-test PUBLIC -fno-exceptions)
endif()

lganzzzo avatar Nov 07 '20 03:11 lganzzzo

Hey @mheyman ,

Do you have any updates on this issue?

lganzzzo avatar Nov 20 '20 15:11 lganzzzo

Been too busy until this morning. Just reproduced the same behavior on a clean vcpkg, brand new CMake project with your source.

The MSVC command line:

cl.exe   /TP -DFMT_LOCALE -DFMT_SHARED -DOATPP_SWAGGER_RES_PATH=\"C:/source/oatpp_crash/out/build/x64-Debug/vcpkg_installed/x64-windows/include/oatpp-1.2.0//bin/oatpp-swagger/res\" -Ivcpkg_installed\x64-windows\include -Ivcpkg_installed\x64-windows\include\oatpp-1.2.0\oatpp -Ivcpkg_installed\x64-windows\include\oatpp-1.2.0\oatpp-swagger /DWIN32 /D_WINDOWS /W3 /GR /EHsc /MDd /Zi /Ob0 /Od /RTC1 /showIncludes /Fooatpp_crash\CMakeFiles\oatpp_crash.dir\oatpp_crash.cpp.obj /Fdoatpp_crash\CMakeFiles\oatpp_crash.dir\ /FS -c ..\..\..\oatpp_crash\oatpp_crash.cpp

I cannot reproduce the error on linux.

It appears because Oat++ uses exceptions in some included types, I cannot compile with exceptions turned off.

mheyman avatar Nov 23 '20 15:11 mheyman