diff --git a/include/pistache/os.h b/include/pistache/os.h index bf72ba492..9a95e21c1 100644 --- a/include/pistache/os.h +++ b/include/pistache/os.h @@ -132,6 +132,7 @@ namespace Pistache { public: NotifyFd(); + ~NotifyFd(); Polling::Tag bind(Polling::Epoll& poller); diff --git a/src/common/os.cc b/src/common/os.cc index 4cb75cd84..ff10cffd3 100644 --- a/src/common/os.cc +++ b/src/common/os.cc @@ -263,6 +263,15 @@ namespace Pistache : event_fd(-1) { } + NotifyFd::~NotifyFd() + { + if (event_fd >= 0) + { + close(event_fd); + event_fd = -1; + } + } + Polling::Tag NotifyFd::bind(Polling::Epoll& poller) { event_fd = TRY_RET(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); diff --git a/src/server/endpoint.cc b/src/server/endpoint.cc index e2527a399..676fc69e8 100644 --- a/src/server/endpoint.cc +++ b/src/server/endpoint.cc @@ -27,6 +27,7 @@ namespace Pistache::Http using Base = Tcp::Transport; explicit TransportImpl(const std::shared_ptr& handler); + ~TransportImpl() override; void registerPoller(Polling::Epoll& poller) override; void onReady(const Aio::FdSet& fds) override; @@ -53,8 +54,18 @@ namespace Pistache::Http TransportImpl::TransportImpl(const std::shared_ptr& handler) : Tcp::Transport(handler) , handler_(handler) + , timerFd(-1) { } + TransportImpl::~TransportImpl() + { + if (timerFd >= 0) + { + close(timerFd); + timerFd = -1; + } + } + void TransportImpl::registerPoller(Polling::Epoll& poller) { Base::registerPoller(poller); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 403147617..73348f2ea 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,6 +4,8 @@ find_package(CURL REQUIRED) +add_subdirectory(helpers) + function(pistache_test test_name) set(TEST_EXECUTABLE run_${test_name}) set(TEST_SOURCE ${test_name}.cc) @@ -16,9 +18,9 @@ function(pistache_test test_name) ) # CMake 3.20 and upstream GTest define GTest::gtest, older versions define GTest::GTest if (TARGET GTest::gtest) - target_link_libraries(${TEST_EXECUTABLE} GTest::gtest GTest::gtest_main pistache_static ${CURL_LIBRARIES}) + target_link_libraries(${TEST_EXECUTABLE} GTest::gtest GTest::gmock_main pistache_static tests_helpers ${CURL_LIBRARIES}) else () - target_link_libraries(${TEST_EXECUTABLE} GTest::GTest GTest::Main pistache_static ${CURL_LIBRARIES}) + target_link_libraries(${TEST_EXECUTABLE} GTest::GTest GMock::Main pistache_static tests_helpers ${CURL_LIBRARIES}) endif () add_test(${test_name} ${TEST_EXECUTABLE}) endfunction() @@ -51,6 +53,7 @@ pistache_test(threadname_test) pistache_test(log_api_test) pistache_test(string_logger_test) pistache_test(endpoint_initialization_test) +pistache_test(helpers_test) if (PISTACHE_USE_SSL) diff --git a/tests/helpers/CMakeLists.txt b/tests/helpers/CMakeLists.txt new file mode 100644 index 000000000..fe4c9e3f4 --- /dev/null +++ b/tests/helpers/CMakeLists.txt @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2023 Mikhail Khachayants +# +# SPDX-License-Identifier: Apache-2.0 + +add_library(tests_helpers STATIC fd_utils.cc fd_utils.h) +target_compile_features(tests_helpers PRIVATE cxx_std_17) diff --git a/tests/helpers/fd_utils.cc b/tests/helpers/fd_utils.cc new file mode 100644 index 000000000..723c561ed --- /dev/null +++ b/tests/helpers/fd_utils.cc @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2023 Mikhail Khachayants + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "fd_utils.h" + +#if __has_include() +#include +namespace filesystem = std::filesystem; +#else +#include +namespace filesystem = std::experimental::filesystem; +#endif + +namespace Pistache +{ + + std::size_t get_open_fds_count() + { + using filesystem::directory_iterator; + const filesystem::path fds_dir { "/proc/self/fd" }; + + if (!filesystem::exists(fds_dir)) + { + return directory_iterator::difference_type(0); + } + + return std::distance(directory_iterator(fds_dir), directory_iterator {}); + } + +} // namespace Pistache diff --git a/tests/helpers/fd_utils.h b/tests/helpers/fd_utils.h new file mode 100644 index 000000000..7a7a3fe2e --- /dev/null +++ b/tests/helpers/fd_utils.h @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2023 Mikhail Khachayants + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +namespace Pistache +{ + + std::size_t get_open_fds_count(); + +} // namespace Pistache diff --git a/tests/helpers/meson.build b/tests/helpers/meson.build new file mode 100644 index 000000000..bd69a12a0 --- /dev/null +++ b/tests/helpers/meson.build @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2023 Mikhail Khachayants +# +# SPDX-License-Identifier: Apache-2.0 + +helpers_src = [ + 'fd_utils.cc' +] + +tests_helpers = library( + 'tests_helpers', + sources: helpers_src +) + +tests_helpers_dep = declare_dependency(link_with: tests_helpers) diff --git a/tests/helpers_test.cc b/tests/helpers_test.cc new file mode 100644 index 000000000..b770f7e19 --- /dev/null +++ b/tests/helpers_test.cc @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: 2023 Mikhail Khachayants + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "helpers/fd_utils.h" +#include +#include +#include + +using namespace Pistache; + +namespace +{ + + class ScopedFd + { + public: + ScopedFd() + : fd_(::eventfd(0, EFD_CLOEXEC)) + { } + + void close() + { + if (fd_ != -1) + { + ::close(fd_); + fd_ = -1; + } + } + + ~ScopedFd() + { + close(); + } + + private: + int fd_; + }; + +} // namespace + +TEST(fd_utils_test, same_result_for_two_calls) +{ + const auto count1 = get_open_fds_count(); + const auto count2 = get_open_fds_count(); + + ASSERT_EQ(count1, count2); +} + +TEST(fd_utils_test, delect_new_descriptor) +{ + const auto count1 = get_open_fds_count(); + const ScopedFd new_fd; + const auto count2 = get_open_fds_count(); + + ASSERT_EQ(count1 + 1, count2); +} + +TEST(fd_utils_test, delect_descriptor_close) +{ + ScopedFd fd; + const auto count1 = get_open_fds_count(); + fd.close(); + const auto count2 = get_open_fds_count(); + + ASSERT_EQ(count1, count2 + 1); +} diff --git a/tests/http_server_test.cc b/tests/http_server_test.cc index 7d432d42f..0a4ca212f 100644 --- a/tests/http_server_test.cc +++ b/tests/http_server_test.cc @@ -30,6 +30,7 @@ #include #include +#include "helpers/fd_utils.h" #include "tcp_client.h" using namespace Pistache; @@ -1043,3 +1044,21 @@ TEST(http_server_test, server_with_content_encoding_deflate) ASSERT_EQ(originalUncompressedData, newlyDecompressedData); } #endif + +TEST(http_server_test, http_server_is_not_leaked) +{ + const auto fds_before = get_open_fds_count(); + const Pistache::Address address("localhost", Pistache::Port(0)); + + auto server = std::make_unique(address); + auto flags = Tcp::Options::ReuseAddr; + auto server_opts = Http::Endpoint::options().flags(flags).threads(4); + server->init(server_opts); + server->setHandler(Http::make_handler()); + server->serveThreaded(); + server->shutdown(); + server.reset(); + + const auto fds_after = get_open_fds_count(); + ASSERT_EQ(fds_before, fds_after); +} diff --git a/tests/meson.build b/tests/meson.build index e8f589494..93a7b4d0c 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -7,6 +7,8 @@ gtest_main_dep = dependency('gtest', main: true, fallback: ['gtest', 'gtest_main gmock_dep = dependency('gmock', version: '>=1.11.0', fallback: ['gtest', 'gmock_dep']) cpp_httplib_dep = dependency('cpp-httplib', fallback: ['cpp-httplib', 'cpp_httplib_dep']) +subdir('helpers') + pistache_test_files = [ 'async_test', 'cookie_test', @@ -33,6 +35,7 @@ pistache_test_files = [ 'threadname_test', 'typeid_test', 'view_test', + 'helpers_test', ] network_tests = ['net_test'] @@ -62,6 +65,7 @@ foreach test_name : pistache_test_files test_name+'.cc', dependencies: [ pistache_dep, + tests_helpers_dep, deps_libpistache, gtest_main_dep, gmock_dep, diff --git a/version.txt b/version.txt index ddf9399ab..7a76718e2 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.2.3.20231022 +0.2.4.20231105