From 30902040f4074e5e2656ab32f1a3ef3c6c728d72 Mon Sep 17 00:00:00 2001 From: "yuchen.cc" Date: Wed, 29 Nov 2023 17:49:41 +0800 Subject: [PATCH] io: support reset photon at fork In photon context, event engine based module need reset after fork, if exec will not be called after fork. This is implicitly done by pthread_atfork hook. Signed-off-by: yuchen.cc --- CMakeLists.txt | 1 + io/aio-wrapper.cpp | 36 ++++- io/epoll.cpp | 10 +- io/fstack-dpdk.cpp | 7 +- io/iouring-wrapper.cpp | 14 +- io/kqueue.cpp | 12 +- io/resettable_ee.cpp | 56 +++++++ io/resettable_ee.h | 30 ++++ io/signal.cpp | 26 +++- io/test/CMakeLists.txt | 7 + io/test/test-fork.cpp | 325 +++++++++++++++++++++++++++++++++++++++++ net/curl.cpp | 18 +++ 12 files changed, 528 insertions(+), 14 deletions(-) create mode 100644 io/resettable_ee.cpp create mode 100644 io/resettable_ee.h create mode 100644 io/test/test-fork.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d73dc91..c61343d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,6 +166,7 @@ file(GLOB PHOTON_SRC fs/xfile.cpp fs/httpfs/*.cpp io/signal.cpp + io/resettable_ee.cpp net/*.cpp net/http/*.cpp net/security-context/tls-stream.cpp diff --git a/io/aio-wrapper.cpp b/io/aio-wrapper.cpp index 2b7b9b0d..53fc6a06 100644 --- a/io/aio-wrapper.cpp +++ b/io/aio-wrapper.cpp @@ -186,7 +186,6 @@ namespace photon static void* libaio_polling(void*) { - libaio_ctx->running = 1; DEFER(libaio_ctx->running = 0); while (libaio_ctx->running == 1) { @@ -342,6 +341,26 @@ namespace photon return rst; } + static thread_local bool registed = false; + void fork_hook_libaio() { + if (!registed) return; + LOG_INFO("reset libaio at fork"); + close(libaio_ctx->evfd); + libaio_ctx->evfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + if (libaio_ctx->evfd < 0) { + LOG_ERROR("failed to create eventfd ", ERRNO()); + exit(-1); + } + io_destroy(libaio_ctx->aio_ctx); + libaio_ctx->aio_ctx = {0}; + int ret = io_setup(IODEPTH, &libaio_ctx->aio_ctx); + if (ret < 0) + { + LOG_ERROR("failed to create aio context by io_setup() ", ERRNO(), VALUE(ret)); + exit(-1); + } + thread_interrupt(libaio_ctx->polling_thread, ECANCELED); + } int libaio_wrapper_init() { @@ -356,7 +375,7 @@ namespace photon int ret = io_setup(IODEPTH, &ctx->aio_ctx); if (ret < 0) { - LOG_ERROR("failed to create aio context by io_setup() ", ERRNO()); + LOG_ERROR("failed to create aio context by io_setup() ", ERRNO(), VALUE(ret)); close(ctx->evfd); return ret; } @@ -364,7 +383,12 @@ namespace photon ctx->polling_thread = thread_create(&libaio_polling, nullptr); assert(ctx->polling_thread); libaio_ctx = ctx.release(); - thread_yield_to(libaio_ctx->polling_thread); + libaio_ctx->running = 1; + if (!registed) { + pthread_atfork(nullptr, nullptr, &fork_hook_libaio); + registed = true; + } + LOG_DEBUG("libaio initialized"); return 0; } @@ -384,10 +408,8 @@ namespace photon io_destroy(libaio_ctx->aio_ctx); close(libaio_ctx->evfd); libaio_ctx->evfd = -1; - delete libaio_ctx; - libaio_ctx = nullptr; + safe_delete(libaio_ctx); + LOG_DEBUG("libaio finished"); return 0; } } - - diff --git a/io/epoll.cpp b/io/epoll.cpp index d6340831..e52a0514 100644 --- a/io/epoll.cpp +++ b/io/epoll.cpp @@ -26,6 +26,7 @@ limitations under the License. #include #include #include "events_map.h" +#include "resettable_ee.h" namespace photon { #ifndef EPOLLRDHUP @@ -48,7 +49,7 @@ struct InFlightEvent { void* error_data; }; -class EventEngineEPoll : public MasterEventEngine, public CascadingEventEngine { +class EventEngineEPoll : public MasterEventEngine, public CascadingEventEngine, public ResettableEventEngine { public: int _evfd = -1; int _engine_fd = -1; @@ -71,6 +72,13 @@ class EventEngineEPoll : public MasterEventEngine, public CascadingEventEngine { epfd = evfd = -1; return 0; } + int reset() override { + if_close_fd(_engine_fd); // close original fd + if_close_fd(_evfd); + _inflight_events.clear(); // reset members + _events_remain = 0; + return init(); // re-init + } virtual ~EventEngineEPoll() override { LOG_INFO("Finish event engine: epoll"); if_close_fd(_engine_fd); diff --git a/io/fstack-dpdk.cpp b/io/fstack-dpdk.cpp index 8308b4db..26cfd272 100644 --- a/io/fstack-dpdk.cpp +++ b/io/fstack-dpdk.cpp @@ -27,6 +27,7 @@ limitations under the License. #include "../thread/thread11.h" #include "../common/alog.h" #include "../net/basic_socket.h" +#include "resettable_ee.h" #ifndef EVFILT_EXCEPT #define EVFILT_EXCEPT (-15) @@ -37,7 +38,7 @@ namespace photon { constexpr static EventsMap> evmap; -class FstackDpdkEngine : public MasterEventEngine, public CascadingEventEngine { +class FstackDpdkEngine : public MasterEventEngine, public CascadingEventEngine, public ResettableEventEngine { public: struct InFlightEvent { uint32_t interests = 0; @@ -76,6 +77,10 @@ class FstackDpdkEngine : public MasterEventEngine, public CascadingEventEngine { return 0; } + int reset() override { + assert(false); + } + ~FstackDpdkEngine() override { LOG_INFO("Finish f-stack dpdk engine"); // if (_n > 0) LOG_INFO(VALUE(_events[0].ident), VALUE(_events[0].filter), VALUE(_events[0].flags)); diff --git a/io/iouring-wrapper.cpp b/io/iouring-wrapper.cpp index 7137788c..179e93dd 100644 --- a/io/iouring-wrapper.cpp +++ b/io/iouring-wrapper.cpp @@ -30,6 +30,7 @@ limitations under the License. #include #include #include "events_map.h" +#include "resettable_ee.h" #ifndef _GNU_SOURCE #define _GNU_SOURCE @@ -42,12 +43,22 @@ namespace photon { constexpr static EventsMap> evmap; -class iouringEngine : public MasterEventEngine, public CascadingEventEngine { +class iouringEngine : public MasterEventEngine, public CascadingEventEngine, public ResettableEventEngine { public: explicit iouringEngine(bool master) : m_master(master) {} ~iouringEngine() { LOG_INFO("Finish event engine: iouring ", VALUE(m_master)); + fini(); + } + + int reset() override { + fini(); + m_event_contexts.clear(); + return init(); + } + + int fini() { if (m_eventfd >= 0 && !m_master) { if (io_uring_unregister_eventfd(m_ring) != 0) { LOG_ERROR("iouring: failed to unregister cascading event fd"); @@ -61,6 +72,7 @@ class iouringEngine : public MasterEventEngine, public CascadingEventEngine { } delete m_ring; m_ring = nullptr; + return 0; } int init() { diff --git a/io/kqueue.cpp b/io/kqueue.cpp index c2b8121e..e8d046a3 100644 --- a/io/kqueue.cpp +++ b/io/kqueue.cpp @@ -21,13 +21,14 @@ limitations under the License. #include #include #include "events_map.h" +#include "resettable_ee.h" namespace photon { constexpr static EventsMap> evmap; -class KQueue : public MasterEventEngine, public CascadingEventEngine { +class KQueue : public MasterEventEngine, public CascadingEventEngine, public ResettableEventEngine { public: struct InFlightEvent { uint32_t interests = 0; @@ -55,6 +56,15 @@ class KQueue : public MasterEventEngine, public CascadingEventEngine { return 0; } + int reset() override { + LOG_INFO("Reset event engine: kqueue"); + _kq = -1; // kqueue fd is not inherited from the parent process + _inflight_events.clear(); // reset members + _n = 0; + _tm = {0, 0}; + return init(); // re-init + } + ~KQueue() override { LOG_INFO("Finish event engine: kqueue"); // if (_n > 0) LOG_INFO(VALUE(_events[0].ident), VALUE(_events[0].filter), VALUE(_events[0].flags)); diff --git a/io/resettable_ee.cpp b/io/resettable_ee.cpp new file mode 100644 index 00000000..06dac69e --- /dev/null +++ b/io/resettable_ee.cpp @@ -0,0 +1,56 @@ +/* +Copyright 2022 The Photon Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "resettable_ee.h" +#include +#include +#include + +namespace photon { + +static thread_local bool registed = false; + +static intrusive_list &ree_list() { + static photon::thread_local_ptr, + ResettableEventEngine *> list(nullptr); + return *list; +} + +void fork_hook_event_engine() { + if (!registed || ree_list().empty()) + return; + LOG_INFO("reset event engine at fork"); + for (auto ree : ree_list()) { + LOG_DEBUG("reset event engine ", VALUE(ree)); + ree->reset(); + } +} + +ResettableEventEngine::ResettableEventEngine() { + if (!registed) { + pthread_atfork(nullptr, nullptr, &fork_hook_event_engine); + registed = true; + } + LOG_DEBUG("push ", VALUE(this)); + ree_list().push_back(this); +} + +ResettableEventEngine::~ResettableEventEngine() { + LOG_DEBUG("erase ", VALUE(this)); + ree_list().erase(this); +} + +} // namespace photon diff --git a/io/resettable_ee.h b/io/resettable_ee.h new file mode 100644 index 00000000..3e2f3865 --- /dev/null +++ b/io/resettable_ee.h @@ -0,0 +1,30 @@ +/* +Copyright 2022 The Photon Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#pragma once +#include + +namespace photon { + +class ResettableEventEngine : public intrusive_list_node { +public: + ResettableEventEngine(); + virtual ~ResettableEventEngine(); + + virtual int reset() = 0; +}; + +} // namespace photon diff --git a/io/signal.cpp b/io/signal.cpp index 77bf2cd8..22c9da58 100644 --- a/io/signal.cpp +++ b/io/signal.cpp @@ -227,13 +227,30 @@ namespace photon #endif } + static thread_local bool registed = false; // should be invoked in child process after forked, to clear signal mask - static void fork_hook_child(void) + static void fork_hook_signalfd(void) { - LOG_DEBUG("Fork hook"); + if (!registed || sgfd < 0) + return; + LOG_INFO("reset signalfd at fork"); sigset_t sigset0; // can NOT use photon::clear_signal_mask(), sigemptyset(&sigset0); // as memory may be shared with parent, when vfork()ed sigprocmask(SIG_SETMASK, &sigset0, nullptr); + // reset sgfd + memset(sighandlers, 0, sizeof(sighandlers)); +#ifdef __APPLE__ + sgfd = kqueue(); // kqueue fd is not inherited from the parent process +#else + close(sgfd); + sigfillset(&sigset); + sgfd = signalfd(-1, &sigset, SFD_CLOEXEC | SFD_NONBLOCK); +#endif + if (sgfd == -1) + LOG_ERROR("failed to create signalfd() or kqueue()"); + // interrupt event loop by ETIMEDOUT to replace sgfd + if (eloop) + thread_interrupt(eloop->loop_thread(), ETIMEDOUT); } int sync_signal_init() @@ -264,7 +281,10 @@ namespace photon eloop->async_run(); LOG_INFO("signalfd initialized"); thread_yield(); // give a chance let eloop to execute do_wait - pthread_atfork(nullptr, nullptr, &fork_hook_child); + if (!registed) { + pthread_atfork(nullptr, nullptr, &fork_hook_signalfd); + registed = true; + } return clear_signal_mask(); } diff --git a/io/test/CMakeLists.txt b/io/test/CMakeLists.txt index 5a3ff5d6..de102709 100644 --- a/io/test/CMakeLists.txt +++ b/io/test/CMakeLists.txt @@ -8,6 +8,13 @@ add_executable(signalfdboom signalfdboom.cpp) target_link_libraries(signalfdboom PRIVATE photon_shared) add_test(NAME signalfdboom COMMAND $) +add_executable(test-fork test-fork.cpp) +target_link_libraries(test-fork PRIVATE photon_shared) +if (PHOTON_ENABLE_URING) + target_compile_definitions(test-fork PRIVATE PHOTON_URING=on) +endif() +add_test(NAME test-fork COMMAND $) + if (NOT APPLE) add_executable(test-syncio test-syncio.cpp) target_link_libraries(test-syncio PRIVATE photon_shared) diff --git a/io/test/test-fork.cpp b/io/test/test-fork.cpp new file mode 100644 index 00000000..1f8f8b8a --- /dev/null +++ b/io/test/test-fork.cpp @@ -0,0 +1,325 @@ +/* +Copyright 2022 The Photon Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +bool exit_flag = false; +bool exit_normal = false; + +void sigint_handler(int signal = SIGINT) { + LOG_INFO("signal ` received, pid `", signal, getpid()); + exit_flag = true; +} + +inline int check_process_exit_stat(int &statVal, int &pid) { + if (WIFEXITED(statVal)) { // child exit normally + LOG_INFO("process with pid ` finished with code `.", pid, WEXITSTATUS(statVal)); + return WEXITSTATUS(statVal); + } else { + if (WIFSIGNALED(statVal)) { // child terminated due to uncaptured signal + LOG_INFO("process with pid ` terminated due to uncaptured signal `.", pid, + WTERMSIG(statVal)); + } else if (WIFSTOPPED(statVal)) { // child terminated unexpectedly + LOG_INFO("process with pid ` terminated unexpectedly with signal `.", pid, + WSTOPSIG(statVal)); + } else { + LOG_INFO("process with pid ` terminated abnormally.", pid); + } + return -1; + } +} + +void wait_process_end(pid_t pid) { + if (pid > 0) { + int statVal; + if (waitpid(pid, &statVal, 0) > 0) { + check_process_exit_stat(statVal, pid); + } else { + /// EINTR + if (EINTR == errno) { + LOG_INFO("process with pid ` waitpid is interrupted.", pid); + } else { + LOG_INFO("process with pid ` waitpid exception, strerror: `.", pid, + strerror(errno)); + } + } + } +} + +void wait_process_end_no_hang(pid_t pid) { + if (pid > 0) { + int statVal; + int retry = 100; + again: + if (waitpid(pid, &statVal, WNOHANG) <= 0) { + if (retry--) { + photon::thread_usleep(50 * 1000); + goto again; + } else { + if (kill(pid, SIGKILL) == 0) { + LOG_WARN("force kill child process with pid `", pid); + } else { + LOG_ERROR("force kill child process with pid ` error, errno:`:`", pid, errno, + strerror(errno)); + } + wait_process_end(pid); + } + } else { + if (check_process_exit_stat(statVal, pid) == 0) { + exit_normal = true; + } + } + } +} + +int fork_child_process() { + pid_t pid = fork(); + if (pid < 0) { + LOG_ERRNO_RETURN(0, -1, "fork error"); + return -1; + } + + if (pid == 0) { + photon::block_all_signal(); + photon::sync_signal(SIGTERM, &sigint_handler); + + LOG_INFO("child hello, pid `", getpid()); + while (!exit_flag) { + photon::thread_usleep(200 * 1000); + } + photon::fini(); + LOG_INFO("child exited, pid `", getpid()); + exit(0); + } else { + LOG_INFO("parent hello, pid `", getpid()); + return pid; + } +} + +int fork_parent_process(uint64_t event_engine) { + pid_t m_pid = fork(); + if (m_pid < 0) { + LOG_ERRNO_RETURN(0, -1, "fork error"); + return -1; + } + + if (m_pid > 0) { + LOG_INFO("main hello, pid `", getpid()); + return m_pid; + } + photon::fini(); + photon::init(event_engine, photon::INIT_IO_LIBCURL); + + photon::block_all_signal(); + photon::sync_signal(SIGINT, &sigint_handler); + + LOG_INFO("parent hello, pid `", getpid()); + photon::thread_sleep(1); + auto pid = fork_child_process(); + photon::thread_sleep(1); + + int statVal; + if (waitpid(pid, &statVal, WNOHANG) == 0) { + if (kill(pid, SIGTERM) == 0) { + LOG_INFO("kill child process with pid `", pid); + } else { + ERRNO eno; + LOG_ERROR("kill child process with pid ` error, `", pid, eno); + } + wait_process_end_no_hang(pid); + } else { + check_process_exit_stat(statVal, pid); + LOG_ERROR("child process exit unexpected"); + } + + LOG_INFO("child process exit status `", exit_normal); + EXPECT_EQ(true, exit_normal); + + while (!exit_flag) { + photon::thread_usleep(200 * 1000); + } + LOG_INFO("parent exited, pid `", getpid()); + photon::fini(); + exit(exit_normal ? 0 : -1); +} + +TEST(ForkTest, Fork) { + photon::init(photon::INIT_EVENT_NONE, photon::INIT_IO_NONE); + DEFER(photon::fini()); + exit_flag = false; + exit_normal = false; +#if defined(__linux__) + auto pid = fork_parent_process(photon::INIT_EVENT_EPOLL | photon::INIT_EVENT_SIGNAL); +#else // macOS, FreeBSD ... + auto pid = fork_parent_process(photon::INIT_EVENT_DEFAULT); +#endif + photon::thread_sleep(5); + + int statVal; + if (waitpid(pid, &statVal, WNOHANG) == 0) { + if (kill(pid, SIGINT) == 0) { + LOG_INFO("kill parent process with pid `", pid); + } else { + ERRNO eno; + LOG_ERROR("kill parent process with pid ` error, `", pid, eno); + } + wait_process_end_no_hang(pid); + } else { + check_process_exit_stat(statVal, pid); + LOG_ERROR("parent process exit unexpected"); + } + + LOG_INFO("parent process exit status `", exit_normal); + EXPECT_EQ(true, exit_normal); +} + +TEST(ForkTest, ForkInThread) { + photon::init(photon::INIT_EVENT_DEFAULT, photon::INIT_IO_LIBCURL); + DEFER(photon::fini()); + + int ret = -1; + std::thread th([&]() { + pid_t pid = fork(); + ASSERT_GE(pid, 0); + + if (pid == 0) { + LOG_INFO("child hello, pid `", getpid()); + exit(0); + } else { + LOG_INFO("parent hello, pid `", getpid()); + int statVal; + waitpid(pid, &statVal, 0); + ret = check_process_exit_stat(statVal, pid); + } + }); + th.join(); + EXPECT_EQ(0, ret); +} + +TEST(ForkTest, PopenInThread) { + photon::init(photon::INIT_EVENT_DEFAULT, photon::INIT_IO_LIBCURL); + DEFER(photon::fini()); + + photon::semaphore sem(0); + auto cmd = "du -s \"/tmp\""; + ssize_t size = -1; + std::thread([&] { + auto f = popen(cmd, "r"); + EXPECT_NE(nullptr, f); + DEFER(fclose(f)); + fscanf(f, "%lu", &size); + sem.signal(1); + LOG_INFO("popen done"); + }).detach(); + sem.wait(1); + EXPECT_NE(-1, size); + LOG_INFO(VALUE(size)); +} + +#if defined(__linux__) && defined(PHOTON_URING) +TEST(ForkTest, Iouring) { + photon::init(photon::INIT_EVENT_NONE, photon::INIT_IO_NONE); + DEFER(photon::fini()); + exit_flag = false; + exit_normal = false; + auto pid = fork_parent_process(photon::INIT_EVENT_IOURING | photon::INIT_EVENT_SIGNAL); + + photon::thread_sleep(5); + + int statVal; + if (waitpid(pid, &statVal, WNOHANG) == 0) { + if (kill(pid, SIGINT) == 0) { + LOG_INFO("kill parent process with pid `", pid); + } else { + ERRNO eno; + LOG_ERROR("kill parent process with pid ` error, `", pid, eno); + } + wait_process_end_no_hang(pid); + } else { + check_process_exit_stat(statVal, pid); + LOG_ERROR("parent process exit unexpected"); + } + + LOG_INFO("parent process exit status `", exit_normal); + EXPECT_EQ(true, exit_normal); +} +#endif + +#if defined(__linux__) +TEST(ForkTest, LIBAIO) { + photon::init(photon::INIT_EVENT_EPOLL, photon::INIT_IO_LIBAIO); + DEFER(photon::fini()); + + std::unique_ptr fs( + photon::fs::new_localfs_adaptor("/tmp/", photon::fs::ioengine_libaio)); + std::unique_ptr lf( + fs->open("test_local_fs_fork_parent", O_RDWR | O_CREAT, 0755)); + void* buf = nullptr; + ::posix_memalign(&buf, 4096, 4096); + DEFER(free(buf)); + int ret = lf->pwrite(buf, 4096, 0); + EXPECT_EQ(ret, 4096); + + ret = -1; + pid_t pid = fork(); + ASSERT_GE(pid, 0); + + if (pid == 0) { + std::unique_ptr fs( + photon::fs::new_localfs_adaptor("/tmp/", photon::fs::ioengine_libaio)); + std::unique_ptr lf( + fs->open("test_local_fs_fork", O_RDWR | O_CREAT, 0755)); + void* buf = nullptr; + ::posix_memalign(&buf, 4096, 4096); + DEFER(free(buf)); + auto ret = lf->pwrite(buf, 4096, 0); + EXPECT_EQ(ret, 4096); + ret = lf->close(); + photon::fini(); + exit(ret); + } else { + int statVal; + waitpid(pid, &statVal, 0); + ret = check_process_exit_stat(statVal, pid); + } + EXPECT_EQ(0, ret); + + ret = lf->pwrite(buf, 4096, 0); + EXPECT_EQ(ret, 4096); + ret = lf->close(); + EXPECT_EQ(0, ret); +} +#endif + +int main(int argc, char **argv) { + set_log_output_level(0); + + ::testing::InitGoogleTest(&argc, argv); + auto ret = RUN_ALL_TESTS(); + if (ret) LOG_ERROR_RETURN(0, ret, VALUE(ret)); +} diff --git a/net/curl.cpp b/net/curl.cpp index f2eeb33f..f6a82746 100644 --- a/net/curl.cpp +++ b/net/curl.cpp @@ -159,6 +159,8 @@ class cURLLoop : public Object { void stop() { loop->stop(); } + photon::thread* loop_thread() { return loop->loop_thread(); } + protected: EventLoop* loop; int cnt; @@ -210,6 +212,16 @@ void __OpenSSLGlobalInit(); // curl_global_cleanup(); // } +static thread_local bool registed = false; +void fork_hook_libcurl() { + if (!registed || !cctx.g_loop) + return; + LOG_INFO("reset libcurl at fork"); + // interrupt g_loop by ETIMEDOUT to replace g_poller + if (cctx.g_loop) + thread_interrupt(cctx.g_loop->loop_thread(), ETIMEDOUT); +} + int libcurl_init(long flags, long pipelining, long maxconn) { if (cctx.g_loop == nullptr) { __OpenSSLGlobalInit(); @@ -240,6 +252,11 @@ int libcurl_init(long flags, long pipelining, long maxconn) { libcurl_set_pipelining(pipelining); libcurl_set_maxconnects(maxconn); + if (!registed) { + pthread_atfork(nullptr, nullptr, &fork_hook_libcurl); + registed = true; + } + LOG_INFO("libcurl initialized"); } return 0; @@ -256,6 +273,7 @@ void libcurl_fini() { if (ret != CURLM_OK) LOG_ERROR("libcurl-multi cleanup error: ", curl_multi_strerror(ret)); cctx.g_libcurl_multi = nullptr; + LOG_INFO("libcurl finished"); } std::string url_escape(const char* str) {