diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index b6875aae0..dfeaf8ed7 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -21,10 +21,12 @@ jobs: run: pip install clang-format - name: check-diff run: | - diff=`git-clang-format --diff HEAD^` - if ! [[ "$diff" = "no modified files to format" || "$diff" = "clang-format did not modify any files" ]]; then - echo "The diff you sent is not formatted correctly." - echo "The suggested format is" - echo "$diff" - exit 1 - fi + diff=$(git-clang-format --diff HEAD^) || true + if [ -n "$diff" ] \ + && [ "$diff" != "no modified files to format" ] \ + && [ "$diff" != "clang-format did not modify any files" ]; then + echo "The diff you sent is not formatted correctly." + echo "The suggested format is:" + echo "$diff" + exit 1 + fi diff --git a/include/ylt/easylog.hpp b/include/ylt/easylog.hpp index f0ccfd3b6..513798b94 100644 --- a/include/ylt/easylog.hpp +++ b/include/ylt/easylog.hpp @@ -38,6 +38,8 @@ #include +#include + #include "easylog/appender.hpp" namespace easylog { @@ -100,10 +102,10 @@ class logger { bool flush_every_time, std::chrono::milliseconds log_sample_interval = {}, std::chrono::milliseconds log_sample_duartion = {}) { - static appender appender(filename, async, enable_console, max_file_size, - max_files, flush_every_time); + appender_ = + std::make_unique(filename, async, enable_console, + max_file_size, max_files, flush_every_time); async_ = async; - appender_ = &appender; min_severity_ = min_severity; enable_console_ = enable_console; log_sample_interval_ = log_sample_interval; @@ -128,7 +130,11 @@ class logger { appenders_.emplace_back(std::move(fn)); } - void stop_async_log() { appender_->stop(); } + void stop_async_log() { + if (appender_) { + appender_->stop(); + } + } // set and get void set_min_severity(Severity severity) { min_severity_ = severity; } @@ -157,14 +163,13 @@ class logger { private: logger() { - static appender appender{}; - appender.start_thread(); - appender.enable_console(true); + appender_ = std::make_unique(); + appender_->enable_console(true); async_ = true; - appender_ = &appender; } - logger(const logger &) = default; + logger(const logger&) = delete; + logger& operator=(const logger&) = delete; void append_record(record_t record) { appender_->write(std::move(record)); } @@ -190,7 +195,7 @@ class logger { std::atomic log_sample_interval_; std::atomic log_sample_duration_; std::chrono::system_clock::time_point init_time_{}; - appender *appender_ = nullptr; + std::unique_ptr appender_ = nullptr; std::vector> appenders_; inline static std::atomic has_destruct_ = false; }; diff --git a/include/ylt/easylog/appender.hpp b/include/ylt/easylog/appender.hpp index 359949366..e0b71d3ba 100644 --- a/include/ylt/easylog/appender.hpp +++ b/include/ylt/easylog/appender.hpp @@ -158,8 +158,6 @@ class appender { buf[0] = '['; auto [ptr, ec] = std::to_chars(buf + 1, buf + 21, tid); - buf[22] = ']'; - buf[23] = ' '; last_tid = tid; last_len = ptr - buf; buf[last_len++] = ']'; @@ -295,29 +293,29 @@ class appender { std::lock_guard guard(mtx_); if (file_.is_open()) { file_.flush(); - file_.sync_with_stdio(); } } void stop() { - std::lock_guard guard(mtx_); if (!write_thd_.joinable()) { return; } - if (stop_) { - return; + { + std::lock_guard guard(que_mtx_); + if (!stop_) { + stop_ = true; + cnd_.notify_one(); + } } - stop_ = true; - cnd_.notify_one(); - } - ~appender() { - stop(); - if (write_thd_.joinable()) + if (write_thd_.joinable()) { write_thd_.join(); + } } + ~appender() { stop(); } + private: void open_log_file() { file_size_ = 0; @@ -347,6 +345,9 @@ class appender { file_size_ += BOM_STR.size(); } } + else { + file_size_ = file_size; + } } } diff --git a/include/ylt/standalone/cinatra/coro_http_client.hpp b/include/ylt/standalone/cinatra/coro_http_client.hpp index e9e50214f..dae0d053e 100644 --- a/include/ylt/standalone/cinatra/coro_http_client.hpp +++ b/include/ylt/standalone/cinatra/coro_http_client.hpp @@ -674,9 +674,13 @@ class coro_http_client : public std::enable_shared_from_this { timer_guard(coro_http_client *self, std::chrono::steady_clock::duration duration, std::string msg) : self(self), dur_(duration) { + if (duration.count() == 0) { + // Zero duration means immediate timeout. + self->socket_->is_timeout_ = true; + return; + } self->socket_->is_timeout_ = false; - - if (duration.count() >= 0) { + if (duration.count() > 0) { self->timeout(self->timer_, duration, std::move(msg)) .start([](auto &&) { }); @@ -1196,6 +1200,9 @@ class coro_http_client : public std::enable_shared_from_this { } auto time_guard = timer_guard(this, req_timeout_duration_, "request timer"); + if (socket_->is_timeout_) { + co_return resp_data{make_error_code(http_errc::request_timeout), 404}; + } std::tie(ec, size) = co_await async_write(asio::buffer(header_str)); if (ec) { handle_upload_timeout_error(ec); @@ -1393,6 +1400,10 @@ class coro_http_client : public std::enable_shared_from_this { CINATRA_LOG_DEBUG << req_head_str; #endif auto guard = timer_guard(this, req_timeout_duration_, "request timer"); + if (socket_->is_timeout_) { + ec = make_error_code(http_errc::request_timeout); + break; + } if (has_body) { std::tie(ec, size) = co_await async_write(vec); } @@ -2249,7 +2260,9 @@ class coro_http_client : public std::enable_shared_from_this { if (socket_->has_closed_) { auto time_out_guard = timer_guard(this, conn_timeout_duration_, "connect timer"); - socket_->is_timeout_ = false; + if (socket_->is_timeout_) { + co_return resp_data{make_error_code(http_errc::connect_timeout), 404}; + } host_ = proxy_host_.empty() ? u.get_host() : proxy_host_; port_ = proxy_port_.empty() ? u.get_port() : proxy_port_; if (eps->empty()) { @@ -2278,6 +2291,9 @@ class coro_http_client : public std::enable_shared_from_this { << ":" << std::to_string((*eps)[0].port()); std::error_code ec; if (ec = co_await coro_io::async_connect(socket_->impl_, *eps); ec) { + if (socket_->is_timeout_) { + ec = make_error_code(http_errc::connect_timeout); + } co_return resp_data{ec, 404}; } #ifdef INJECT_FOR_HTTP_CLIENT_TEST diff --git a/include/ylt/standalone/cinatra/session_manager.hpp b/include/ylt/standalone/cinatra/session_manager.hpp index 6ec39cc2c..3fd00e337 100644 --- a/include/ylt/standalone/cinatra/session_manager.hpp +++ b/include/ylt/standalone/cinatra/session_manager.hpp @@ -61,6 +61,9 @@ class session_manager { void start_check_session_timer() { std::lock_guard lock(timer_mtx_); + if (stop_timer_ || !check_session_timer_) { + return; + } std::weak_ptr timer = check_session_timer_; check_session_timer_->expires_after(check_session_duration_); check_session_timer_->async_wait([this, timer](auto ec) { @@ -69,12 +72,26 @@ class session_manager { return; } - if (ec || stop_timer_) { + if (ec) { return; } + { + std::lock_guard lock(shutdown_mtx_); + if (stop_timer_) { + return; + } + ++active_callbacks_; + } + remove_expire_session(); start_check_session_timer(); + + { + std::lock_guard lock(shutdown_mtx_); + --active_callbacks_; + } + shutdown_cv_.notify_all(); }); } @@ -89,9 +106,32 @@ class session_manager { start_check_session_timer(); } - void stop_timer() { stop_timer_ = true; } + void stop_timer() { + std::lock_guard lock(shutdown_mtx_); + stop_timer_ = true; + } private: + ~session_manager() { + { + std::lock_guard lock(timer_mtx_); + check_session_timer_->cancel(); + check_session_timer_.reset(); + } + { + std::lock_guard lock(shutdown_mtx_); + stop_timer_ = true; + } + { + std::unique_lock lock(shutdown_mtx_); + shutdown_cv_.wait(lock, [this] { + return active_callbacks_ == 0; + }); + } + std::lock_guard lock(mtx_); + map_.clear(); + } + session_manager() : check_session_timer_(std::make_shared( coro_io::get_global_executor()->get_asio_executor())) { @@ -111,6 +151,10 @@ class session_manager { std::shared_ptr check_session_timer_; std::atomic check_session_duration_ = { std::chrono::seconds(15)}; + + std::mutex shutdown_mtx_; + std::condition_variable shutdown_cv_; + int active_callbacks_ = 0; }; } // namespace cinatra \ No newline at end of file diff --git a/src/coro_http/tests/test_cinatra.cpp b/src/coro_http/tests/test_cinatra.cpp index f64ea4059..3ae22faa8 100644 --- a/src/coro_http/tests/test_cinatra.cpp +++ b/src/coro_http/tests/test_cinatra.cpp @@ -2754,6 +2754,89 @@ TEST_CASE("test coro_http_client chunked upload and download") { } } +TEST_CASE("test zero duration timeout returns correct error codes") { + // 验证 set_conn_timeout(0ms) 和 set_req_timeout(0ms) + // 能确定性地返回正确错误码, 不依赖与异步操作的竞争结果。 + coro_http_server server(1, 8091); + server.set_http_handler( + "/timeout_test", [](coro_http_request&, coro_http_response& resp) { + resp.set_status_and_content(status_type::ok, "ok"); + }); + server.async_start(); + + std::string uri = "http://127.0.0.1:8091/timeout_test"; + create_file("timeout_test.txt", 1024); + + // --- conn_timeout(0ms) 场景 --- + + { + // async_upload_chunked:0ms 连接超时应返回 connect_timeout + coro_http_client client{}; + client.set_conn_timeout(0ms); + auto result = async_simple::coro::syncAwait(client.async_upload_chunked( + uri, http_method::PUT, "timeout_test.txt"sv)); + CHECK(result.status != 200); + CHECK(result.net_err == http_errc::connect_timeout); + } + + { + // async_upload:0ms 连接超时应返回 connect_timeout + coro_http_client client{}; + client.set_conn_timeout(0ms); + auto result = async_simple::coro::syncAwait( + client.async_upload(uri, http_method::PUT, "timeout_test.txt"sv)); + CHECK(result.status != 200); + CHECK(result.net_err == http_errc::connect_timeout); + } + + { + // async_request(GET):0ms 连接超时应返回 connect_timeout + coro_http_client client{}; + client.set_conn_timeout(0ms); + auto result = async_simple::coro::syncAwait(client.async_get(uri)); + CHECK(result.status != 200); + CHECK(result.net_err == http_errc::connect_timeout); + } + + // --- req_timeout(0ms) 场景(连接正常,请求立即超时) --- + + { + // async_upload_chunked:0ms 请求超时应返回 request_timeout + coro_http_client client{}; + client.set_conn_timeout(500ms); + client.set_req_timeout(0ms); + client.add_header("filename", "timeout_test.txt"); + auto result = async_simple::coro::syncAwait(client.async_upload_chunked( + uri, http_method::PUT, "timeout_test.txt"sv)); + CHECK(result.status != 200); + CHECK(result.net_err == http_errc::request_timeout); + } + + { + // async_upload:0ms 请求超时应返回 request_timeout + coro_http_client client{}; + client.set_conn_timeout(500ms); + client.set_req_timeout(0ms); + auto result = async_simple::coro::syncAwait( + client.async_upload(uri, http_method::PUT, "timeout_test.txt"sv)); + CHECK(result.status != 200); + CHECK(result.net_err == http_errc::request_timeout); + } + + { + // async_request(GET):0ms 请求超时应返回 request_timeout + coro_http_client client{}; + client.set_conn_timeout(500ms); + client.set_req_timeout(0ms); + auto result = async_simple::coro::syncAwait(client.async_get(uri)); + CHECK(result.status != 200); + CHECK(result.net_err == http_errc::request_timeout); + } + + std::error_code ignore_ec; + std::filesystem::remove("timeout_test.txt", ignore_ec); +} + TEST_CASE("test coro_http_client get") { coro_http_client client{}; client.set_conn_timeout(1s);