From ce585af3089bc58c1c44e48b4bfd35cff02c4a47 Mon Sep 17 00:00:00 2001 From: Petr Shumilov Date: Tue, 25 Nov 2025 11:42:34 +0300 Subject: [PATCH 01/15] Add curl_multi_init Signed-off-by: Petr Shumilov --- .../kphp-light/stdlib/curl-functions.txt | 5 +- .../stdlib/curl/curl-multi-functions.h | 13 ++++ runtime-light/stdlib/curl/defs.h | 1 + .../web-transfer-lib/composite-transfer.h | 68 +++++++++++++++++++ runtime-light/stdlib/web-transfer-lib/defs.h | 4 ++ .../stdlib/web-transfer-lib/web-state.h | 2 +- runtime-light/tl/tl-functions.h | 41 ++++++++--- runtime-light/tl/tl-types.h | 31 +++++++-- 8 files changed, 147 insertions(+), 18 deletions(-) create mode 100644 runtime-light/stdlib/web-transfer-lib/composite-transfer.h diff --git a/builtin-functions/kphp-light/stdlib/curl-functions.txt b/builtin-functions/kphp-light/stdlib/curl-functions.txt index ee7297d544..e2f984d7da 100644 --- a/builtin-functions/kphp-light/stdlib/curl-functions.txt +++ b/builtin-functions/kphp-light/stdlib/curl-functions.txt @@ -268,10 +268,11 @@ function curl_errno ($curl_handle ::: int) ::: int; /** @kphp-extern-func-info interruptible */ function curl_getinfo ($curl_handle ::: int, $option ::: int = 0) ::: mixed; +/** @kphp-extern-func-info interruptible */ +function curl_multi_init () ::: int; + // ===== UNSUPPORTED ===== -/** @kphp-extern-func-info stub generation-required */ -function curl_multi_init () ::: int; /** @kphp-extern-func-info stub generation-required */ function curl_multi_add_handle ($multi_handle ::: int, $curl_handle ::: int) ::: int|false; /** @kphp-extern-func-info stub generation-required */ diff --git a/runtime-light/stdlib/curl/curl-multi-functions.h b/runtime-light/stdlib/curl/curl-multi-functions.h index 3fa7714c77..81f40e71f1 100644 --- a/runtime-light/stdlib/curl/curl-multi-functions.h +++ b/runtime-light/stdlib/curl/curl-multi-functions.h @@ -9,7 +9,20 @@ #include #include "runtime-common/core/runtime-core.h" +#include "runtime-light/stdlib/curl/defs.h" +#include "runtime-light/stdlib/curl/details/diagnostics.h" #include "runtime-light/stdlib/diagnostics/logs.h" +#include "runtime-light/stdlib/fork/fork-functions.h" +#include "runtime-light/stdlib/web-transfer-lib/composite-transfer.h" + +inline auto f$curl_multi_init() noexcept -> kphp::coro::task { + auto open_res{co_await kphp::forks::id_managed(kphp::web::composite_transfer_open(kphp::web::transfer_backend::CURL))}; + if (!open_res.has_value()) [[unlikely]] { + kphp::web::curl::print_error("Could not initialize a new curl multi handle", std::move(open_res.error())); + co_return 0; + } + co_return (*open_res).descriptor; +} inline Optional> f$curl_multi_info_read([[maybe_unused]] int64_t /*unused*/, [[maybe_unused]] Optional>> /*unused*/ = {}) { diff --git a/runtime-light/stdlib/curl/defs.h b/runtime-light/stdlib/curl/defs.h index 4efa725d9b..3ee1e8e08e 100644 --- a/runtime-light/stdlib/curl/defs.h +++ b/runtime-light/stdlib/curl/defs.h @@ -16,6 +16,7 @@ enum class PHPCURL : uint64_t { }; using easy_type = simple_transfer::descriptor_type; +using multi_type = composite_transfer::descriptor_type; constexpr auto CURL_ERROR_SIZE = 256; diff --git a/runtime-light/stdlib/web-transfer-lib/composite-transfer.h b/runtime-light/stdlib/web-transfer-lib/composite-transfer.h new file mode 100644 index 0000000000..4d7935beae --- /dev/null +++ b/runtime-light/stdlib/web-transfer-lib/composite-transfer.h @@ -0,0 +1,68 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2025 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include +#include +#include +#include + +#include "runtime-light/coroutine/task.h" +#include "runtime-light/stdlib/diagnostics/logs.h" +#include "runtime-light/stdlib/web-transfer-lib/defs.h" +#include "runtime-light/stdlib/web-transfer-lib/details/web-error.h" +#include "runtime-light/stdlib/web-transfer-lib/web-state.h" +#include "runtime-light/tl/tl-core.h" +#include "runtime-light/tl/tl-functions.h" +#include "runtime-light/tl/tl-types.h" + +namespace kphp::web { + +inline auto composite_transfer_open(transfer_backend backend) noexcept -> kphp::coro::task> { + auto session{WebInstanceState::get().session_get_or_init()}; + if (!session.has_value()) [[unlikely]] { + kphp::log::error("failed to start or get session with Web component"); + } + + if ((*session).get() == nullptr) [[unlikely]] { + kphp::log::error("session with Web components has been closed"); + } + + tl::CompositeWebTransferOpen web_transfer_open{.web_backend = {static_cast(backend)}}; + tl::storer tls{web_transfer_open.footprint()}; + web_transfer_open.store(tls); + + kphp::stl::vector resp_buf{}; + const auto response_buffer_provider{[&resp_buf](size_t size) noexcept -> std::span { + resp_buf.resize(size); + return {resp_buf.data(), size}; + }}; + + auto resp{co_await (*session).get()->client.query(tls.view(), std::move(response_buffer_provider))}; + if (!resp.has_value()) [[unlikely]] { + kphp::log::error("failed to send request for Composite descriptor creation"); + } + + tl::Either transfer_open_resp{}; + tl::fetcher tlf{*resp}; + if (!transfer_open_resp.fetch(tlf)) [[unlikely]] { + kphp::log::error("failed to parse response for Composite descriptor creation"); + } + + const auto result{transfer_open_resp.value}; + if (std::holds_alternative(result)) { + co_return std::unexpected{details::process_error(std::get(result))}; + } + + const auto descriptor{std::get<0>(result).descriptor.value}; + + auto& composite2config{WebInstanceState::get().composite_transfer2config}; + kphp::log::assertion(composite2config.contains(descriptor) == false); // NOLINT + composite2config.emplace(descriptor, composite_transfer_config{}); + + co_return std::expected{descriptor}; +} + +} // namespace kphp::web diff --git a/runtime-light/stdlib/web-transfer-lib/defs.h b/runtime-light/stdlib/web-transfer-lib/defs.h index e0b5279672..0c258f929c 100644 --- a/runtime-light/stdlib/web-transfer-lib/defs.h +++ b/runtime-light/stdlib/web-transfer-lib/defs.h @@ -99,6 +99,10 @@ struct simple_transfer_config { properties_type properties{}; }; +struct composite_transfer_config { + properties_type properties{}; +}; + struct response { string headers; string body; diff --git a/runtime-light/stdlib/web-transfer-lib/web-state.h b/runtime-light/stdlib/web-transfer-lib/web-state.h index 3de8b7669b..7412a9a298 100644 --- a/runtime-light/stdlib/web-transfer-lib/web-state.h +++ b/runtime-light/stdlib/web-transfer-lib/web-state.h @@ -26,7 +26,7 @@ struct WebInstanceState final : private vk::not_copyable { bool session_is_finished{false}; kphp::stl::unordered_map simple_transfer2config{}; - kphp::stl::unordered_map + kphp::stl::unordered_map composite_transfer2config{}; inline auto session_get_or_init() noexcept -> std::expected; diff --git a/runtime-light/tl/tl-functions.h b/runtime-light/tl/tl-functions.h index 74919208ef..912563cfb5 100644 --- a/runtime-light/tl/tl-functions.h +++ b/runtime-light/tl/tl-functions.h @@ -396,6 +396,28 @@ struct CacheFetch final { // ===== WEB TRANSFER LIB ===== +class WebTransferGetProperties final { + static constexpr uint32_t WEB_TRANSFER_GET_PROPERTIES_MAGIC = 0x72B7'16DD; + +public: + tl::u8 is_simple; + tl::u64 descriptor; + tl::Maybe property_id; + + void store(tl::storer& tls) const noexcept { + tl::magic{.value = WEB_TRANSFER_GET_PROPERTIES_MAGIC}.store(tls); + is_simple.store(tls); + descriptor.store(tls); + property_id.store(tls); + } + + constexpr size_t footprint() const noexcept { + return tl::magic{.value = WEB_TRANSFER_GET_PROPERTIES_MAGIC}.footprint() + is_simple.footprint() + descriptor.footprint() + property_id.footprint(); + } +}; + +// ===== Simple ===== + class SimpleWebTransferOpen final { static constexpr uint32_t SIMPLE_WEB_TRANSFER_OPEN_MAGIC = 0x24F8'98AA; @@ -463,23 +485,22 @@ class SimpleWebTransferReset final { } }; -class WebTransferGetProperties final { - static constexpr uint32_t WEB_TRANSFER_GET_PROPERTIES_MAGIC = 0x72B7'16DD; +// ===== Composite ===== + +class CompositeWebTransferOpen final { + static constexpr uint32_t COMPOSITE_WEB_TRANSFER_OPEN_MAGIC = 0x428F'89FF; public: - tl::u8 is_simple; - tl::u64 descriptor; - tl::Maybe property_id; + using web_backend_type = tl::u8; + web_backend_type web_backend; void store(tl::storer& tls) const noexcept { - tl::magic{.value = WEB_TRANSFER_GET_PROPERTIES_MAGIC}.store(tls); - is_simple.store(tls); - descriptor.store(tls); - property_id.store(tls); + tl::magic{.value = COMPOSITE_WEB_TRANSFER_OPEN_MAGIC}.store(tls); + web_backend.store(tls); } constexpr size_t footprint() const noexcept { - return tl::magic{.value = WEB_TRANSFER_GET_PROPERTIES_MAGIC}.footprint() + is_simple.footprint() + descriptor.footprint() + property_id.footprint(); + return tl::magic{.value = COMPOSITE_WEB_TRANSFER_OPEN_MAGIC}.footprint() + web_backend.footprint(); } }; diff --git a/runtime-light/tl/tl-types.h b/runtime-light/tl/tl-types.h index e18078108f..94c5d2a8a3 100644 --- a/runtime-light/tl/tl-types.h +++ b/runtime-light/tl/tl-types.h @@ -1236,6 +1236,25 @@ struct simpleWebTransferConfig final { } }; +class WebTransferGetPropertiesResultOk final { + static constexpr uint32_t WEB_TRANSFER_GET_PROPERTIES_RESULT_OK_MAGIC = 0x48A7'16CC; + +public: + tl::vector properties; + + bool fetch(tl::fetcher& tlf) noexcept { + tl::magic magic{}; + bool ok{magic.fetch(tlf) && magic.expect(WEB_TRANSFER_GET_PROPERTIES_RESULT_OK_MAGIC) && properties.fetch(tlf)}; + return ok; + } + + constexpr size_t footprint() const noexcept { + return tl::magic{.value = WEB_TRANSFER_GET_PROPERTIES_RESULT_OK_MAGIC}.footprint(); + } +}; + +// ===== Simple ===== + class SimpleWebTransferOpenResultOk final { static constexpr uint32_t SIMPLE_WEB_TRANSFER_OPEN_RESULT_OK_MAGIC = 0x24A8'98FF; @@ -1298,20 +1317,22 @@ class SimpleWebTransferResetResultOk final { } }; -class WebTransferGetPropertiesResultOk final { - static constexpr uint32_t WEB_TRANSFER_GET_PROPERTIES_RESULT_OK_MAGIC = 0x48A7'16CC; +// ===== Composite ===== + +class CompositeWebTransferOpenResultOk final { + static constexpr uint32_t COMPOSITE_WEB_TRANSFER_OPEN_RESULT_OK_MAGIC = 0x428A'89DD; public: - tl::vector properties; + tl::u64 descriptor; bool fetch(tl::fetcher& tlf) noexcept { tl::magic magic{}; - bool ok{magic.fetch(tlf) && magic.expect(WEB_TRANSFER_GET_PROPERTIES_RESULT_OK_MAGIC) && properties.fetch(tlf)}; + bool ok{magic.fetch(tlf) && magic.expect(COMPOSITE_WEB_TRANSFER_OPEN_RESULT_OK_MAGIC) && descriptor.fetch(tlf)}; return ok; } constexpr size_t footprint() const noexcept { - return tl::magic{.value = WEB_TRANSFER_GET_PROPERTIES_RESULT_OK_MAGIC}.footprint(); + return tl::magic{.value = COMPOSITE_WEB_TRANSFER_OPEN_RESULT_OK_MAGIC}.footprint() + descriptor.footprint(); } }; From 3df38f0d20f772f96ab4f19c3e1c77be6c0f7748 Mon Sep 17 00:00:00 2001 From: Petr Shumilov Date: Thu, 27 Nov 2025 19:25:16 +0300 Subject: [PATCH 02/15] Make easy context resetting more clear Signed-off-by: Petr Shumilov --- runtime-light/stdlib/curl/curl-context.h | 9 +++++++++ runtime-light/stdlib/curl/curl-easy-functions.h | 3 +-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/runtime-light/stdlib/curl/curl-context.h b/runtime-light/stdlib/curl/curl-context.h index 9892a55b3c..0c0af0838d 100644 --- a/runtime-light/stdlib/curl/curl-context.h +++ b/runtime-light/stdlib/curl/curl-context.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include @@ -21,6 +22,14 @@ struct easy_context { std::array error_description{std::byte{0}}; bool has_been_executed{false}; // Need for providing same as in PHP semantics of curl_getinfo(..., CURLINFO_EFFECTIVE_URL) + inline auto reset() noexcept { + return_transfer = false; + private_data = std::nullopt; + error_code = 0; + error_description.fill(std::byte{0}); + has_been_executed = false; + } + inline auto set_errno(int64_t code, std::string_view description) noexcept { // If Web Transfer Lib specific error if (code == kphp::web::WEB_INTERNAL_ERROR_CODE) [[unlikely]] { diff --git a/runtime-light/stdlib/curl/curl-easy-functions.h b/runtime-light/stdlib/curl/curl-easy-functions.h index 698f5bf580..48aa9cfaa0 100644 --- a/runtime-light/stdlib/curl/curl-easy-functions.h +++ b/runtime-light/stdlib/curl/curl-easy-functions.h @@ -431,8 +431,7 @@ inline auto f$curl_reset(kphp::web::curl::easy_type easy_id) noexcept -> kphp::c kphp::web::curl::print_error("could not reset curl easy handle", std::move(res.error())); co_return; } - easy_ctx.return_transfer = false; - easy_ctx.private_data = std::nullopt; + easy_ctx.reset(); } inline auto f$curl_exec_concurrently(kphp::web::curl::easy_type easy_id, double timeout_sec = 1.0) noexcept -> kphp::coro::task> { From 93826f5275baec7e04a532935cc4f5affb1e7554 Mon Sep 17 00:00:00 2001 From: Petr Shumilov Date: Thu, 27 Nov 2025 20:53:18 +0300 Subject: [PATCH 03/15] Refactor contexts management Signed-off-by: Petr Shumilov --- runtime-light/stdlib/curl/curl-context.h | 38 +++++++++----- .../stdlib/curl/curl-easy-functions.h | 50 +++++++++++++++---- .../stdlib/curl/curl-multi-functions.h | 15 ++++++ runtime-light/stdlib/curl/curl-state.h | 29 ++++++++++- 4 files changed, 110 insertions(+), 22 deletions(-) diff --git a/runtime-light/stdlib/curl/curl-context.h b/runtime-light/stdlib/curl/curl-context.h index 0c0af0838d..dc7378390d 100644 --- a/runtime-light/stdlib/curl/curl-context.h +++ b/runtime-light/stdlib/curl/curl-context.h @@ -8,6 +8,7 @@ #include #include +#include "common/mixin/movable_only.h" #include "runtime-common/core/runtime-core.h" #include "runtime-light/stdlib/curl/defs.h" #include "runtime-light/stdlib/diagnostics/logs.h" @@ -15,20 +16,9 @@ namespace kphp::web::curl { -struct easy_context { - bool return_transfer{false}; - std::optional private_data{std::nullopt}; +struct curl_context : vk::movable_only { int64_t error_code{0}; std::array error_description{std::byte{0}}; - bool has_been_executed{false}; // Need for providing same as in PHP semantics of curl_getinfo(..., CURLINFO_EFFECTIVE_URL) - - inline auto reset() noexcept { - return_transfer = false; - private_data = std::nullopt; - error_code = 0; - error_description.fill(std::byte{0}); - has_been_executed = false; - } inline auto set_errno(int64_t code, std::string_view description) noexcept { // If Web Transfer Lib specific error @@ -61,6 +51,30 @@ struct easy_context { kphp::log::warning("{}", msg); set_errno(CURLE::BAD_FUNCTION_ARGUMENT, {{msg, N}}); } + + inline auto reset() noexcept { + error_code = 0; + error_description.fill(std::byte{0}); + } +}; + +struct easy_context : curl_context { + using curl_context::curl_context; + + bool return_transfer{false}; + std::optional private_data{std::nullopt}; + bool has_been_executed{false}; // Need for providing same as in PHP semantics of curl_getinfo(..., CURLINFO_EFFECTIVE_URL) + + inline auto reset() noexcept { + curl_context::reset(); + return_transfer = false; + private_data = std::nullopt; + has_been_executed = false; + } +}; + +struct multi_context : curl_context { + using curl_context::curl_context; }; } // namespace kphp::web::curl diff --git a/runtime-light/stdlib/curl/curl-easy-functions.h b/runtime-light/stdlib/curl/curl-easy-functions.h index 48aa9cfaa0..bd6cfe9694 100644 --- a/runtime-light/stdlib/curl/curl-easy-functions.h +++ b/runtime-light/stdlib/curl/curl-easy-functions.h @@ -26,10 +26,10 @@ inline auto f$curl_init(string url = string{""}) noexcept -> kphp::coro::task(kphp::web::curl::CURLOPT::URL), kphp::web::property_value::as_string(url))}; if (!setopt_res.has_value()) [[unlikely]] { - auto& easy_ctx{CurlInstanceState::get().easy_ctx.get_or_init(st.descriptor)}; easy_ctx.set_errno(setopt_res.error().code, setopt_res.error().description); kphp::web::curl::print_error("could not set URL for a new curl easy handle", std::move(setopt_res.error())); co_return 0; @@ -38,7 +38,11 @@ inline auto f$curl_init(string url = string{""}) noexcept -> kphp::coro::task bool { // NOLINT - auto& easy_ctx{CurlInstanceState::get().easy_ctx.get_or_init(easy_id)}; + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.easy_ctx.has(easy_id)) { + return false; + } + auto& easy_ctx{curl_state.easy_ctx.get_or_init(easy_id)}; auto st{kphp::web::simple_transfer{.descriptor = easy_id}}; if (option == static_cast(kphp::web::curl::CURLINFO::HEADER_OUT)) { @@ -399,8 +403,12 @@ inline auto f$curl_setopt_array(kphp::web::curl::easy_type easy_id, const array< } inline auto f$curl_exec(kphp::web::curl::easy_type easy_id) noexcept -> kphp::coro::task { + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.easy_ctx.has(easy_id)) { + co_return false; + } auto res{co_await kphp::forks::id_managed(kphp::web::simple_transfer_perform(kphp::web::simple_transfer{easy_id}))}; - auto& easy_ctx{CurlInstanceState::get().easy_ctx.get_or_init(easy_id)}; + auto& easy_ctx{curl_state.easy_ctx.get_or_init(easy_id)}; easy_ctx.has_been_executed = true; if (!res.has_value()) [[unlikely]] { easy_ctx.set_errno(res.error().code, res.error().description); @@ -415,7 +423,11 @@ inline auto f$curl_exec(kphp::web::curl::easy_type easy_id) noexcept -> kphp::co } inline auto f$curl_close(kphp::web::curl::easy_type easy_id) noexcept -> kphp::coro::task { - auto& easy_ctx{CurlInstanceState::get().easy_ctx.get_or_init(easy_id)}; + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.easy_ctx.has(easy_id)) { + co_return; + } + auto& easy_ctx{curl_state.easy_ctx.get_or_init(easy_id)}; auto res{co_await kphp::forks::id_managed(kphp::web::simple_transfer_close(kphp::web::simple_transfer{easy_id}))}; if (!res.has_value()) [[unlikely]] { easy_ctx.set_errno(res.error().code, res.error().description); @@ -424,7 +436,11 @@ inline auto f$curl_close(kphp::web::curl::easy_type easy_id) noexcept -> kphp::c } inline auto f$curl_reset(kphp::web::curl::easy_type easy_id) noexcept -> kphp::coro::task { - auto& easy_ctx{CurlInstanceState::get().easy_ctx.get_or_init(easy_id)}; + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.easy_ctx.has(easy_id)) { + co_return; + } + auto& easy_ctx{curl_state.easy_ctx.get_or_init(easy_id)}; auto res{co_await kphp::forks::id_managed(kphp::web::simple_transfer_reset(kphp::web::simple_transfer{easy_id}))}; if (!res.has_value()) [[unlikely]] { easy_ctx.set_errno(res.error().code, res.error().description); @@ -435,7 +451,11 @@ inline auto f$curl_reset(kphp::web::curl::easy_type easy_id) noexcept -> kphp::c } inline auto f$curl_exec_concurrently(kphp::web::curl::easy_type easy_id, double timeout_sec = 1.0) noexcept -> kphp::coro::task> { - auto& easy_ctx{CurlInstanceState::get().easy_ctx.get_or_init(easy_id)}; + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.easy_ctx.has(easy_id)) { + co_return false; + } + auto& easy_ctx{curl_state.easy_ctx.get_or_init(easy_id)}; auto sched_res{co_await kphp::coro::io_scheduler::get().schedule( kphp::forks::id_managed(kphp::web::simple_transfer_perform(kphp::web::simple_transfer{easy_id})), kphp::web::curl::details::normalize_timeout(std::chrono::duration_cast(std::chrono::duration{timeout_sec})))}; @@ -456,7 +476,11 @@ inline auto f$curl_exec_concurrently(kphp::web::curl::easy_type easy_id, double } inline auto f$curl_error(kphp::web::curl::easy_type easy_id) noexcept -> string { - auto& easy_ctx{CurlInstanceState::get().easy_ctx.get_or_init(easy_id)}; + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.easy_ctx.has(easy_id)) { + return {}; + } + auto& easy_ctx{curl_state.easy_ctx.get_or_init(easy_id)}; if (easy_ctx.error_code != static_cast(kphp::web::curl::CURLE::OK)) [[likely]] { const auto* const desc_data{reinterpret_cast(easy_ctx.error_description.data())}; const auto desc_size{static_cast(easy_ctx.error_description.size())}; @@ -466,12 +490,20 @@ inline auto f$curl_error(kphp::web::curl::easy_type easy_id) noexcept -> string } inline auto f$curl_errno(kphp::web::curl::easy_type easy_id) noexcept -> int64_t { - auto& easy_ctx{CurlInstanceState::get().easy_ctx.get_or_init(easy_id)}; + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.easy_ctx.has(easy_id)) { + return 0; + } + auto& easy_ctx{curl_state.easy_ctx.get_or_init(easy_id)}; return easy_ctx.error_code; } inline auto f$curl_getinfo(kphp::web::curl::easy_type easy_id, int64_t option = 0) noexcept -> kphp::coro::task { - auto& easy_ctx{CurlInstanceState::get().easy_ctx.get_or_init(easy_id)}; + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.easy_ctx.has(easy_id)) { + co_return false; + } + auto& easy_ctx{curl_state.easy_ctx.get_or_init(easy_id)}; switch (static_cast(option)) { case kphp::web::curl::CURLINFO::NONE: { auto res{co_await kphp::forks::id_managed(kphp::web::get_transfer_properties(kphp::web::simple_transfer{easy_id}, std::nullopt))}; diff --git a/runtime-light/stdlib/curl/curl-multi-functions.h b/runtime-light/stdlib/curl/curl-multi-functions.h index 81f40e71f1..1c292ec6d2 100644 --- a/runtime-light/stdlib/curl/curl-multi-functions.h +++ b/runtime-light/stdlib/curl/curl-multi-functions.h @@ -24,6 +24,21 @@ inline auto f$curl_multi_init() noexcept -> kphp::coro::task f$curl_multi_add_handle(kphp::web::curl::multi_type multi_id, kphp::web::curl::easy_type easy_id) noexcept { +// if (auto* multi_context = get_context(multi_id)) { +// if (auto* easy_context = get_context(easy_id)) { +// if (kphp_tracing::is_turned_on()) { +// string url = easy_context->get_info(CURLINFO_EFFECTIVE_URL).as_string(); +// kphp_tracing::on_curl_multi_add_handle(multi_context->uniq_id, easy_context->uniq_id, url); +// } +// easy_context->cleanup_for_next_request(); +// multi_context->error_num = dl::critical_section_call(curl_multi_add_handle, multi_context->multi_handle, easy_context->easy_handle); +// return multi_context->error_num; +// } +// } +// return false; +// } + inline Optional> f$curl_multi_info_read([[maybe_unused]] int64_t /*unused*/, [[maybe_unused]] Optional>> /*unused*/ = {}) { kphp::log::error("call to unsupported function : curl_multi_info_read"); diff --git a/runtime-light/stdlib/curl/curl-state.h b/runtime-light/stdlib/curl/curl-state.h index e12540bde1..2b93506482 100644 --- a/runtime-light/stdlib/curl/curl-state.h +++ b/runtime-light/stdlib/curl/curl-state.h @@ -8,6 +8,7 @@ #include "runtime-common/core/allocator/script-allocator.h" #include "runtime-common/core/std/containers.h" #include "runtime-light/stdlib/curl/curl-context.h" +#include "runtime-light/stdlib/curl/defs.h" struct CurlInstanceState final : private vk::not_copyable { public: @@ -18,16 +19,42 @@ struct CurlInstanceState final : private vk::not_copyable { class easy_ctx { public: inline auto get_or_init(kphp::web::curl::easy_type easy_id) noexcept -> kphp::web::curl::easy_context&; + inline auto has(kphp::web::curl::easy_type easy_id) noexcept -> bool; private: kphp::stl::unordered_map ctx{}; } easy_ctx; + + class multi_ctx { + public: + inline auto get_or_init(kphp::web::curl::multi_type multi_id) noexcept -> kphp::web::curl::multi_context&; + inline auto has(kphp::web::curl::multi_type multi_id) noexcept -> bool; + + private: + kphp::stl::unordered_map ctx{}; + } multi_ctx; }; inline auto CurlInstanceState::easy_ctx::get_or_init(kphp::web::curl::easy_type easy_id) noexcept -> kphp::web::curl::easy_context& { const auto& it{ctx.find(easy_id)}; if (it == ctx.end()) { - return ctx.insert({easy_id, kphp::web::curl::easy_context{}}).first->second; + return ctx.emplace(easy_id, kphp::web::curl::easy_context{}).first->second; } return it->second; } + +inline auto CurlInstanceState::easy_ctx::has(kphp::web::curl::easy_type easy_id) noexcept -> bool { + return ctx.find(easy_id) != ctx.end(); +} + +inline auto CurlInstanceState::multi_ctx::get_or_init(kphp::web::curl::multi_type multi_id) noexcept -> kphp::web::curl::multi_context& { + const auto& it{ctx.find(multi_id)}; + if (it == ctx.end()) { + return ctx.emplace(multi_id, kphp::web::curl::multi_context{}).first->second; + } + return it->second; +} + +inline auto CurlInstanceState::multi_ctx::has(kphp::web::curl::multi_type multi_id) noexcept -> bool { + return ctx.find(multi_id) != ctx.end(); +} From 0bb0828c6eeeb3c083b85519ffbb9b57b16f0572 Mon Sep 17 00:00:00 2001 From: Petr Shumilov Date: Sun, 30 Nov 2025 00:04:23 +0300 Subject: [PATCH 04/15] Add curl_multi_add_handle and curl_multi_remove_handle support Signed-off-by: Petr Shumilov --- .../kphp-light/stdlib/curl-functions.txt | 9 +- runtime-light/stdlib/curl/curl-context.h | 4 +- .../stdlib/curl/curl-multi-functions.h | 65 ++++-- .../web-transfer-lib/composite-transfer.h | 68 ------ runtime-light/stdlib/web-transfer-lib/defs.h | 2 + .../web-transfer-lib/details/web-property.h | 8 +- .../web-transfer-lib/web-composite-transfer.h | 213 ++++++++++++++++++ .../web-transfer-lib/web-simple-transfer.h | 40 +++- .../stdlib/web-transfer-lib/web-state.h | 4 + runtime-light/tl/tl-functions.h | 38 ++++ runtime-light/tl/tl-types.h | 32 +++ 11 files changed, 379 insertions(+), 104 deletions(-) delete mode 100644 runtime-light/stdlib/web-transfer-lib/composite-transfer.h create mode 100644 runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h diff --git a/builtin-functions/kphp-light/stdlib/curl-functions.txt b/builtin-functions/kphp-light/stdlib/curl-functions.txt index e2f984d7da..44c33b9d21 100644 --- a/builtin-functions/kphp-light/stdlib/curl-functions.txt +++ b/builtin-functions/kphp-light/stdlib/curl-functions.txt @@ -270,11 +270,12 @@ function curl_getinfo ($curl_handle ::: int, $option ::: int = 0) ::: mixed; /** @kphp-extern-func-info interruptible */ function curl_multi_init () ::: int; +/** @kphp-extern-func-info interruptible */ +function curl_multi_add_handle ($multi_handle ::: int, $curl_handle ::: int) ::: int|false; +/** @kphp-extern-func-info interruptible */ +function curl_multi_remove_handle ($multi_handle ::: int, $curl_handle ::: int) ::: int|false; // ===== UNSUPPORTED ===== - -/** @kphp-extern-func-info stub generation-required */ -function curl_multi_add_handle ($multi_handle ::: int, $curl_handle ::: int) ::: int|false; /** @kphp-extern-func-info stub generation-required */ function curl_multi_getcontent ($curl_handle ::: int ) ::: string|false|null; /** @kphp-extern-func-info stub generation-required */ @@ -287,8 +288,6 @@ function curl_multi_select ($multi_handle ::: int, $timeout ::: float = 1.0) ::: /** @kphp-extern-func-info stub */ function curl_multi_info_read ($multi_handle ::: int, &$msgs_in_queue ::: int = TODO) ::: int[]|false; /** @kphp-extern-func-info stub generation-required */ -function curl_multi_remove_handle ($multi_handle ::: int, $curl_handle ::: int) ::: int|false; -/** @kphp-extern-func-info stub generation-required */ function curl_multi_errno ($multi_handle ::: int) ::: int|false; /** @kphp-extern-func-info stub generation-required */ function curl_multi_close ($multi_handle ::: int) ::: void; diff --git a/runtime-light/stdlib/curl/curl-context.h b/runtime-light/stdlib/curl/curl-context.h index dc7378390d..77c6f5be9e 100644 --- a/runtime-light/stdlib/curl/curl-context.h +++ b/runtime-light/stdlib/curl/curl-context.h @@ -52,7 +52,7 @@ struct curl_context : vk::movable_only { set_errno(CURLE::BAD_FUNCTION_ARGUMENT, {{msg, N}}); } - inline auto reset() noexcept { + inline auto errors_reset() noexcept { error_code = 0; error_description.fill(std::byte{0}); } @@ -66,7 +66,7 @@ struct easy_context : curl_context { bool has_been_executed{false}; // Need for providing same as in PHP semantics of curl_getinfo(..., CURLINFO_EFFECTIVE_URL) inline auto reset() noexcept { - curl_context::reset(); + curl_context::errors_reset(); return_transfer = false; private_data = std::nullopt; has_been_executed = false; diff --git a/runtime-light/stdlib/curl/curl-multi-functions.h b/runtime-light/stdlib/curl/curl-multi-functions.h index 1c292ec6d2..dc53c6d651 100644 --- a/runtime-light/stdlib/curl/curl-multi-functions.h +++ b/runtime-light/stdlib/curl/curl-multi-functions.h @@ -9,11 +9,15 @@ #include #include "runtime-common/core/runtime-core.h" +#include "runtime-light/coroutine/task.h" +#include "runtime-light/stdlib/curl/curl-context.h" +#include "runtime-light/stdlib/curl/curl-state.h" #include "runtime-light/stdlib/curl/defs.h" #include "runtime-light/stdlib/curl/details/diagnostics.h" #include "runtime-light/stdlib/diagnostics/logs.h" #include "runtime-light/stdlib/fork/fork-functions.h" -#include "runtime-light/stdlib/web-transfer-lib/composite-transfer.h" +#include "runtime-light/stdlib/web-transfer-lib/defs.h" +#include "runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h" inline auto f$curl_multi_init() noexcept -> kphp::coro::task { auto open_res{co_await kphp::forks::id_managed(kphp::web::composite_transfer_open(kphp::web::transfer_backend::CURL))}; @@ -21,23 +25,52 @@ inline auto f$curl_multi_init() noexcept -> kphp::coro::task f$curl_multi_add_handle(kphp::web::curl::multi_type multi_id, kphp::web::curl::easy_type easy_id) noexcept { -// if (auto* multi_context = get_context(multi_id)) { -// if (auto* easy_context = get_context(easy_id)) { -// if (kphp_tracing::is_turned_on()) { -// string url = easy_context->get_info(CURLINFO_EFFECTIVE_URL).as_string(); -// kphp_tracing::on_curl_multi_add_handle(multi_context->uniq_id, easy_context->uniq_id, url); -// } -// easy_context->cleanup_for_next_request(); -// multi_context->error_num = dl::critical_section_call(curl_multi_add_handle, multi_context->multi_handle, easy_context->easy_handle); -// return multi_context->error_num; -// } -// } -// return false; -// } +inline auto f$curl_multi_add_handle(kphp::web::curl::multi_type multi_id, kphp::web::curl::easy_type easy_id) noexcept -> kphp::coro::task> { + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.multi_ctx.has(multi_id)) { + co_return false; + } + auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; + + if (!curl_state.easy_ctx.has(easy_id)) { + co_return false; + } + auto& easy_ctx{curl_state.easy_ctx.get_or_init(easy_id)}; + easy_ctx.errors_reset(); + + auto res{co_await kphp::forks::id_managed(kphp::web::composite_transfer_add(kphp::web::composite_transfer{multi_id}, kphp::web::simple_transfer{easy_id}))}; + if (!res.has_value()) [[unlikely]] { + multi_ctx.set_errno(res.error().code, res.error().description); + kphp::web::curl::print_error("Could not add a curl easy handler into multi handle", std::move(res.error())); + co_return multi_ctx.error_code; + } + multi_ctx.set_errno(kphp::web::curl::CURLE::OK); + co_return multi_ctx.error_code; +} + +inline auto f$curl_multi_remove_handle(kphp::web::curl::multi_type multi_id, kphp::web::curl::easy_type easy_id) noexcept -> kphp::coro::task> { + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.multi_ctx.has(multi_id)) { + co_return false; + } + auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; + if (!curl_state.easy_ctx.has(easy_id)) { + co_return false; + } + auto res{co_await kphp::forks::id_managed(kphp::web::composite_transfer_remove(kphp::web::composite_transfer{multi_id}, kphp::web::simple_transfer{easy_id}))}; + if (!res.has_value()) [[unlikely]] { + multi_ctx.set_errno(res.error().code, res.error().description); + kphp::web::curl::print_error("Could not remove a curl easy handler from multi handle", std::move(res.error())); + co_return multi_ctx.error_code; + } + multi_ctx.set_errno(kphp::web::curl::CURLE::OK); + co_return multi_ctx.error_code; +} inline Optional> f$curl_multi_info_read([[maybe_unused]] int64_t /*unused*/, [[maybe_unused]] Optional>> /*unused*/ = {}) { diff --git a/runtime-light/stdlib/web-transfer-lib/composite-transfer.h b/runtime-light/stdlib/web-transfer-lib/composite-transfer.h deleted file mode 100644 index 4d7935beae..0000000000 --- a/runtime-light/stdlib/web-transfer-lib/composite-transfer.h +++ /dev/null @@ -1,68 +0,0 @@ -// Compiler for PHP (aka KPHP) -// Copyright (c) 2025 LLC «V Kontakte» -// Distributed under the GPL v3 License, see LICENSE.notice.txt - -#pragma once - -#include -#include -#include -#include - -#include "runtime-light/coroutine/task.h" -#include "runtime-light/stdlib/diagnostics/logs.h" -#include "runtime-light/stdlib/web-transfer-lib/defs.h" -#include "runtime-light/stdlib/web-transfer-lib/details/web-error.h" -#include "runtime-light/stdlib/web-transfer-lib/web-state.h" -#include "runtime-light/tl/tl-core.h" -#include "runtime-light/tl/tl-functions.h" -#include "runtime-light/tl/tl-types.h" - -namespace kphp::web { - -inline auto composite_transfer_open(transfer_backend backend) noexcept -> kphp::coro::task> { - auto session{WebInstanceState::get().session_get_or_init()}; - if (!session.has_value()) [[unlikely]] { - kphp::log::error("failed to start or get session with Web component"); - } - - if ((*session).get() == nullptr) [[unlikely]] { - kphp::log::error("session with Web components has been closed"); - } - - tl::CompositeWebTransferOpen web_transfer_open{.web_backend = {static_cast(backend)}}; - tl::storer tls{web_transfer_open.footprint()}; - web_transfer_open.store(tls); - - kphp::stl::vector resp_buf{}; - const auto response_buffer_provider{[&resp_buf](size_t size) noexcept -> std::span { - resp_buf.resize(size); - return {resp_buf.data(), size}; - }}; - - auto resp{co_await (*session).get()->client.query(tls.view(), std::move(response_buffer_provider))}; - if (!resp.has_value()) [[unlikely]] { - kphp::log::error("failed to send request for Composite descriptor creation"); - } - - tl::Either transfer_open_resp{}; - tl::fetcher tlf{*resp}; - if (!transfer_open_resp.fetch(tlf)) [[unlikely]] { - kphp::log::error("failed to parse response for Composite descriptor creation"); - } - - const auto result{transfer_open_resp.value}; - if (std::holds_alternative(result)) { - co_return std::unexpected{details::process_error(std::get(result))}; - } - - const auto descriptor{std::get<0>(result).descriptor.value}; - - auto& composite2config{WebInstanceState::get().composite_transfer2config}; - kphp::log::assertion(composite2config.contains(descriptor) == false); // NOLINT - composite2config.emplace(descriptor, composite_transfer_config{}); - - co_return std::expected{descriptor}; -} - -} // namespace kphp::web diff --git a/runtime-light/stdlib/web-transfer-lib/defs.h b/runtime-light/stdlib/web-transfer-lib/defs.h index 0c258f929c..8b6b758b82 100644 --- a/runtime-light/stdlib/web-transfer-lib/defs.h +++ b/runtime-light/stdlib/web-transfer-lib/defs.h @@ -93,6 +93,8 @@ class property_value { inline auto to_mixed() const noexcept -> mixed; }; +using simple_transfers = kphp::stl::unordered_set; + using properties_type = kphp::stl::unordered_map; struct simple_transfer_config { diff --git a/runtime-light/stdlib/web-transfer-lib/details/web-property.h b/runtime-light/stdlib/web-transfer-lib/details/web-property.h index 6faea6593f..087dff3b89 100644 --- a/runtime-light/stdlib/web-transfer-lib/details/web-property.h +++ b/runtime-light/stdlib/web-transfer-lib/details/web-property.h @@ -5,13 +5,9 @@ #pragma once #include -#include #include -#include -#include #include #include -#include #include #include @@ -127,8 +123,8 @@ inline auto get_transfer_properties(std::variant(r).properties}; for (const auto& p : tl_props) { - const auto k{p.id.value}; - const auto v{property_value::deserialize(std::move(p.value))}; + auto k{p.id.value}; + auto v{property_value::deserialize(p.value)}; props.emplace(k, std::move(v)); } if (prop_id.has_value() && !props.contains(*prop_id)) { diff --git a/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h b/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h new file mode 100644 index 0000000000..9884e73878 --- /dev/null +++ b/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h @@ -0,0 +1,213 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2025 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include +#include +#include +#include + +#include "runtime-light/coroutine/task.h" +#include "runtime-light/stdlib/diagnostics/logs.h" +#include "runtime-light/stdlib/web-transfer-lib/defs.h" +#include "runtime-light/stdlib/web-transfer-lib/details/web-error.h" +#include "runtime-light/stdlib/web-transfer-lib/web-state.h" +#include "runtime-light/tl/tl-core.h" +#include "runtime-light/tl/tl-functions.h" +#include "runtime-light/tl/tl-types.h" + +namespace kphp::web { + +inline auto composite_transfer_open(transfer_backend backend) noexcept -> kphp::coro::task> { + auto& web_state{WebInstanceState::get()}; + + auto session{web_state.session_get_or_init()}; + if (!session.has_value()) [[unlikely]] { + kphp::log::error("failed to start or get session with Web component"); + } + + if ((*session).get() == nullptr) [[unlikely]] { + kphp::log::error("session with Web components has been closed"); + } + + tl::CompositeWebTransferOpen web_transfer_open{.web_backend = {static_cast(backend)}}; + tl::storer tls{web_transfer_open.footprint()}; + web_transfer_open.store(tls); + + kphp::stl::vector resp_buf{}; + const auto response_buffer_provider{[&resp_buf](size_t size) noexcept -> std::span { + resp_buf.resize(size); + return {resp_buf.data(), size}; + }}; + + auto resp{co_await (*session).get()->client.query(tls.view(), std::move(response_buffer_provider))}; + if (!resp.has_value()) [[unlikely]] { + kphp::log::error("failed to send request for Composite descriptor creation"); + } + + tl::Either transfer_open_resp{}; + tl::fetcher tlf{*resp}; + if (!transfer_open_resp.fetch(tlf)) [[unlikely]] { + kphp::log::error("failed to parse response for Composite descriptor creation"); + } + + const auto result{transfer_open_resp.value}; + if (std::holds_alternative(result)) { + co_return std::unexpected{details::process_error(std::get(result))}; + } + + const auto descriptor{std::get(result).descriptor.value}; + + auto& composite2config{web_state.composite_transfer2config}; + kphp::log::assertion(composite2config.contains(descriptor) == false); // NOLINT + composite2config.emplace(descriptor, composite_transfer_config{}); + + auto& composite2simple_transfers{web_state.composite_transfer2simple_transfers}; + kphp::log::assertion(composite2simple_transfers.contains(descriptor) == false); // NOLINT + composite2simple_transfers.emplace(descriptor, simple_transfers{}); + + co_return std::expected{descriptor}; +} + +inline auto composite_transfer_add(composite_transfer ct, simple_transfer st) noexcept -> kphp::coro::task> { + auto& web_state{WebInstanceState::get()}; + + auto& composite2config{web_state.composite_transfer2config}; + if (!composite2config.contains(ct.descriptor)) [[unlikely]] { + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"unknown Composite transfer"}}}; + } + + auto& simple2config{web_state.simple_transfer2config}; + if (!simple2config.contains(st.descriptor)) [[unlikely]] { + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"unknown Simple transfer"}}}; + } + + auto session{web_state.session_get_or_init()}; + if (!session.has_value()) [[unlikely]] { + kphp::log::error("failed to start or get session with Web component"); + } + + if ((*session).get() == nullptr) [[unlikely]] { + kphp::log::error("session with Web components has been closed"); + } + + // Ensure that simple transfer hasn't been added in some composite yet + auto& simple_transfers{web_state.composite_transfer2simple_transfers[ct.descriptor]}; + if (simple_transfers.contains(st.descriptor)) { + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"a Composite transfer contains this Simple transfer"}}}; + } + simple_transfers.emplace(st.descriptor); + + // Set a holder for simple transfer + auto& composite_holder{web_state.simple_transfer2holder[st.descriptor]}; + if (composite_holder.has_value()) { + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"a Simple transfer is held by some Composite transfer already"}}}; + } + composite_holder.emplace(ct.descriptor); + + // Prepare simple config + kphp::stl::vector tl_simple_props{}; + auto& props{simple2config[st.descriptor].properties}; + for (const auto& [id, val] : props) { + tl::webProperty p{.id = tl::u64{id}, .value = val.serialize()}; + tl_simple_props.emplace_back(std::move(p)); + } + tl::simpleWebTransferConfig tl_simple_config{tl::vector{std::move(tl_simple_props)}}; + // Prepare `CompositeWebTransferAdd` method + tl::CompositeWebTransferAdd tl_add{ + .composite_descriptor = tl::u64{ct.descriptor}, .simple_descriptor = tl::u64{st.descriptor}, .simple_config = std::move(tl_simple_config)}; + tl::storer tls{tl_add.footprint()}; + tl_add.store(tls); + + kphp::stl::vector resp_buf{}; + auto response_buffer_provider{[&resp_buf](size_t size) noexcept -> std::span { + resp_buf.resize(size); + return {resp_buf.data(), size}; + }}; + + auto resp{co_await (*session).get()->client.query(tls.view(), std::move(response_buffer_provider))}; + if (!resp.has_value()) [[unlikely]] { + kphp::log::error("failed to send request of adding Simple into Composite transfer"); + } + + tl::Either composite_add_resp{}; + tl::fetcher tlf{*resp}; + if (!composite_add_resp.fetch(tlf)) [[unlikely]] { + kphp::log::error("failed to parse response of adding Simple into Composite transfer"); + } + + if (auto r{composite_add_resp.value}; std::holds_alternative(r)) { + co_return std::unexpected{details::process_error(std::get(r))}; + } + + co_return std::expected{}; +} + +inline auto composite_transfer_remove(composite_transfer ct, simple_transfer st) noexcept -> kphp::coro::task> { + auto& web_state{WebInstanceState::get()}; + + auto& composite2config{web_state.composite_transfer2config}; + if (!composite2config.contains(ct.descriptor)) [[unlikely]] { + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"unknown Composite transfer"}}}; + } + + auto& simple2config{web_state.simple_transfer2config}; + if (!simple2config.contains(st.descriptor)) [[unlikely]] { + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"unknown Simple transfer"}}}; + } + + auto session{web_state.session_get_or_init()}; + if (!session.has_value()) [[unlikely]] { + kphp::log::error("fastruct composite_transfer_config;iled to start or get session with Web component"); + } + + if ((*session).get() == nullptr) [[unlikely]] { + kphp::log::error("session with Web components has been closed"); + } + + // Ensure that simple transfer has been added in some composite yet + auto& simple_transfers{web_state.composite_transfer2simple_transfers[ct.descriptor]}; + if (!simple_transfers.contains(st.descriptor)) { + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"a Composite transfer doesn't contain this Simple transfer"}}}; + } + simple_transfers.erase(st.descriptor); + + // Unset a holder for simple transfer + auto& composite_holder{web_state.simple_transfer2holder[st.descriptor]}; + if (!composite_holder.has_value()) { + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"a Simple transfer isn't held by some Composite transfer yet"}}}; + } + composite_holder.reset(); + + // Prepare `CompositeWebTransferRemove` method + tl::CompositeWebTransferRemove tl_remove{.composite_descriptor = tl::u64{ct.descriptor}, .simple_descriptor = tl::u64{st.descriptor}}; + tl::storer tls{tl_remove.footprint()}; + tl_remove.store(tls); + + kphp::stl::vector resp_buf{}; + auto response_buffer_provider{[&resp_buf](size_t size) noexcept -> std::span { + resp_buf.resize(size); + return {resp_buf.data(), size}; + }}; + + auto resp{co_await (*session).get()->client.query(tls.view(), std::move(response_buffer_provider))}; + if (!resp.has_value()) [[unlikely]] { + kphp::log::error("failed to send request of removing Simple into Composite transfer"); + } + + tl::Either composite_remove_resp{}; + tl::fetcher tlf{*resp}; + if (!composite_remove_resp.fetch(tlf)) [[unlikely]] { + kphp::log::error("failed to parse response of removing Simple into Composite transfer"); + } + + if (auto r{composite_remove_resp.value}; std::holds_alternative(r)) { + co_return std::unexpected{details::process_error(std::get(r))}; + } + + co_return std::expected{}; +} + +} // namespace kphp::web diff --git a/runtime-light/stdlib/web-transfer-lib/web-simple-transfer.h b/runtime-light/stdlib/web-transfer-lib/web-simple-transfer.h index 06618406d4..42af7d95da 100644 --- a/runtime-light/stdlib/web-transfer-lib/web-simple-transfer.h +++ b/runtime-light/stdlib/web-transfer-lib/web-simple-transfer.h @@ -4,6 +4,7 @@ #pragma once +#include <__expected/unexpected.h> #include #include #include @@ -14,7 +15,7 @@ #include "runtime-light/stdlib/diagnostics/logs.h" #include "runtime-light/stdlib/web-transfer-lib/defs.h" #include "runtime-light/stdlib/web-transfer-lib/details/web-error.h" -#include "runtime-light/stdlib/web-transfer-lib/details/web-property.h" // for convenient passing into simple-transfer users +#include "runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h" #include "runtime-light/stdlib/web-transfer-lib/web-state.h" #include "runtime-light/tl/tl-core.h" #include "runtime-light/tl/tl-functions.h" @@ -23,7 +24,8 @@ namespace kphp::web { inline auto simple_transfer_open(transfer_backend backend) noexcept -> kphp::coro::task> { - auto session{WebInstanceState::get().session_get_or_init()}; + auto& web_state{WebInstanceState::get()}; + auto session{web_state.session_get_or_init()}; if (!session.has_value()) [[unlikely]] { kphp::log::error("failed to start or get session with Web component"); } @@ -60,10 +62,14 @@ inline auto simple_transfer_open(transfer_backend backend) noexcept -> kphp::cor const auto descriptor{std::get(result).descriptor.value}; - auto& simple2config{WebInstanceState::get().simple_transfer2config}; + auto& simple2config{web_state.simple_transfer2config}; kphp::log::assertion(simple2config.contains(descriptor) == false); // NOLINT simple2config.emplace(descriptor, simple_transfer_config{}); + auto& composite_holder{web_state.simple_transfer2holder}; + kphp::log::assertion(composite_holder.contains(descriptor) == false); // NOLINT + composite_holder.emplace(descriptor, std::nullopt); + co_return std::expected{descriptor}; } @@ -135,7 +141,7 @@ inline auto simple_transfer_perform(simple_transfer st) noexcept -> kphp::coro:: case 1: frame_num += 1; return kphp::component::inter_component_session::client::response_readiness::pending; - case 2: + case 2: // NOLINT return kphp::component::inter_component_session::client::response_readiness::ready; default: return kphp::component::inter_component_session::client::response_readiness::ready; @@ -152,7 +158,13 @@ inline auto simple_transfer_perform(simple_transfer st) noexcept -> kphp::coro:: } inline auto simple_transfer_reset(simple_transfer st) noexcept -> kphp::coro::task> { - auto session{WebInstanceState::get().session_get_or_init()}; + auto& web_state{WebInstanceState::get()}; + + if (!web_state.simple_transfer2config.contains(st.descriptor)) { + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"unknown Simple transfer"}}}; + } + + auto session{web_state.session_get_or_init()}; if (!session.has_value()) [[unlikely]] { kphp::log::error("failed to start or get session with Web component"); } @@ -186,7 +198,7 @@ inline auto simple_transfer_reset(simple_transfer st) noexcept -> kphp::coro::ta co_return std::unexpected{details::process_error(std::get(r))}; } - auto& simple2config{WebInstanceState::get().simple_transfer2config}; + auto& simple2config{web_state.simple_transfer2config}; if (simple2config.contains(st.descriptor)) [[likely]] { simple2config[st.descriptor] = simple_transfer_config{}; } @@ -195,7 +207,13 @@ inline auto simple_transfer_reset(simple_transfer st) noexcept -> kphp::coro::ta } inline auto simple_transfer_close(simple_transfer st) noexcept -> kphp::coro::task> { - auto session{WebInstanceState::get().session_get_or_init()}; + auto& web_state{WebInstanceState::get()}; + + if (!web_state.simple_transfer2config.contains(st.descriptor)) { + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"unknown Simple transfer"}}}; + } + + auto session{web_state.session_get_or_init()}; if (!session.has_value()) [[unlikely]] { kphp::log::error("failed to start or get session with Web component"); } @@ -204,6 +222,14 @@ inline auto simple_transfer_close(simple_transfer st) noexcept -> kphp::coro::ta kphp::log::error("session with Web components has been closed"); } + // Checking that Simple transfer is held by some Composite transfer + auto& composite_holder{web_state.simple_transfer2holder[st.descriptor]}; + if (composite_holder.has_value()) { + if (auto close_res{co_await kphp::web::composite_transfer_remove(kphp::web::composite_transfer{*composite_holder}, kphp::web::simple_transfer{st.descriptor})}; !close_res.has_value()) { + co_return std::move(close_res); + }; + } + tl::SimpleWebTransferClose web_transfer_close{.descriptor = tl::u64{st.descriptor}}; tl::storer tls{web_transfer_close.footprint()}; web_transfer_close.store(tls); diff --git a/runtime-light/stdlib/web-transfer-lib/web-state.h b/runtime-light/stdlib/web-transfer-lib/web-state.h index 7412a9a298..b2c1350293 100644 --- a/runtime-light/stdlib/web-transfer-lib/web-state.h +++ b/runtime-light/stdlib/web-transfer-lib/web-state.h @@ -26,8 +26,12 @@ struct WebInstanceState final : private vk::not_copyable { bool session_is_finished{false}; kphp::stl::unordered_map simple_transfer2config{}; + kphp::stl::map, kphp::memory::script_allocator> + simple_transfer2holder{}; + kphp::stl::unordered_map composite_transfer2config{}; + kphp::stl::map composite_transfer2simple_transfers{}; inline auto session_get_or_init() noexcept -> std::expected; diff --git a/runtime-light/tl/tl-functions.h b/runtime-light/tl/tl-functions.h index 912563cfb5..281a58dc8b 100644 --- a/runtime-light/tl/tl-functions.h +++ b/runtime-light/tl/tl-functions.h @@ -504,4 +504,42 @@ class CompositeWebTransferOpen final { } }; +class CompositeWebTransferAdd final { + static constexpr uint32_t COMPOSITE_WEB_TRANSFER_ADD_MAGIC = 0x1223'16CC; + +public: + tl::u64 composite_descriptor; + tl::u64 simple_descriptor; + tl::simpleWebTransferConfig simple_config; + + void store(tl::storer& tls) const noexcept { + tl::magic{.value = COMPOSITE_WEB_TRANSFER_ADD_MAGIC}.store(tls); + composite_descriptor.store(tls); + simple_descriptor.store(tls); + simple_config.store(tls); + } + + constexpr size_t footprint() const noexcept { + return tl::magic{.value = COMPOSITE_WEB_TRANSFER_ADD_MAGIC}.footprint() + composite_descriptor.footprint() + simple_descriptor.footprint() + simple_config.footprint(); + } +}; + +class CompositeWebTransferRemove final { + static constexpr uint32_t COMPOSITE_WEB_TRANSFER_REMOVE_MAGIC = 0x8981'22AA; + +public: + tl::u64 composite_descriptor; + tl::u64 simple_descriptor; + + void store(tl::storer& tls) const noexcept { + tl::magic{.value = COMPOSITE_WEB_TRANSFER_REMOVE_MAGIC}.store(tls); + composite_descriptor.store(tls); + simple_descriptor.store(tls); + } + + constexpr size_t footprint() const noexcept { + return tl::magic{.value = COMPOSITE_WEB_TRANSFER_REMOVE_MAGIC}.footprint() + composite_descriptor.footprint() + simple_descriptor.footprint(); + } +}; + } // namespace tl diff --git a/runtime-light/tl/tl-types.h b/runtime-light/tl/tl-types.h index 94c5d2a8a3..ae3b25fed8 100644 --- a/runtime-light/tl/tl-types.h +++ b/runtime-light/tl/tl-types.h @@ -1336,4 +1336,36 @@ class CompositeWebTransferOpenResultOk final { } }; +class CompositeWebTransferAddResultOk final { + static constexpr uint32_t COMPOSITE_WEB_TRANSFER_ADD_RESULT_OK_MAGIC = 0x3161'22DD; + +public: + + bool fetch(tl::fetcher& tlf) noexcept { + tl::magic magic{}; + bool ok{magic.fetch(tlf) && magic.expect(COMPOSITE_WEB_TRANSFER_ADD_RESULT_OK_MAGIC)}; + return ok; + } + + constexpr size_t footprint() const noexcept { + return tl::magic{.value = COMPOSITE_WEB_TRANSFER_ADD_RESULT_OK_MAGIC}.footprint(); + } +}; + +class CompositeWebTransferRemoveResultOk final { + static constexpr uint32_t COMPOSITE_WEB_TRANSFER_REMOVE_RESULT_OK_MAGIC = 0x8981'22FF; + +public: + + bool fetch(tl::fetcher& tlf) noexcept { + tl::magic magic{}; + bool ok{magic.fetch(tlf) && magic.expect(COMPOSITE_WEB_TRANSFER_REMOVE_RESULT_OK_MAGIC)}; + return ok; + } + + constexpr size_t footprint() const noexcept { + return tl::magic{.value = COMPOSITE_WEB_TRANSFER_REMOVE_RESULT_OK_MAGIC}.footprint(); + } +}; + } // namespace tl From 6c5bbe86246ffe464523ac12a6dd41f731814382 Mon Sep 17 00:00:00 2001 From: Petr Shumilov Date: Sun, 30 Nov 2025 15:02:45 +0300 Subject: [PATCH 05/15] Add curl_multi_close support Signed-off-by: Petr Shumilov --- .../kphp-light/stdlib/curl-functions.txt | 4 +- .../stdlib/curl/curl-multi-functions.h | 30 +++++++-- .../web-transfer-lib/web-composite-transfer.h | 65 +++++++++++++++++-- .../web-transfer-lib/web-simple-transfer.h | 9 ++- .../stdlib/web-transfer-lib/web-state.h | 3 +- runtime-light/tl/tl-functions.h | 19 +++++- runtime-light/tl/tl-types.h | 17 ++++- tests/phpt/curl/5_curl_multi_init.php | 2 +- tests/phpt/curl/6_curl_multi_add_handle.php | 2 +- 9 files changed, 129 insertions(+), 22 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/curl-functions.txt b/builtin-functions/kphp-light/stdlib/curl-functions.txt index 44c33b9d21..0c50aa6afd 100644 --- a/builtin-functions/kphp-light/stdlib/curl-functions.txt +++ b/builtin-functions/kphp-light/stdlib/curl-functions.txt @@ -274,6 +274,8 @@ function curl_multi_init () ::: int; function curl_multi_add_handle ($multi_handle ::: int, $curl_handle ::: int) ::: int|false; /** @kphp-extern-func-info interruptible */ function curl_multi_remove_handle ($multi_handle ::: int, $curl_handle ::: int) ::: int|false; +/** @kphp-extern-func-info interruptible */ +function curl_multi_close ($multi_handle ::: int) ::: void; // ===== UNSUPPORTED ===== /** @kphp-extern-func-info stub generation-required */ @@ -290,6 +292,4 @@ function curl_multi_info_read ($multi_handle ::: int, &$msgs_in_queue ::: int = /** @kphp-extern-func-info stub generation-required */ function curl_multi_errno ($multi_handle ::: int) ::: int|false; /** @kphp-extern-func-info stub generation-required */ -function curl_multi_close ($multi_handle ::: int) ::: void; -/** @kphp-extern-func-info stub generation-required */ function curl_multi_strerror ($errornum ::: int) ::: string|null; diff --git a/runtime-light/stdlib/curl/curl-multi-functions.h b/runtime-light/stdlib/curl/curl-multi-functions.h index dc53c6d651..8fa33a8750 100644 --- a/runtime-light/stdlib/curl/curl-multi-functions.h +++ b/runtime-light/stdlib/curl/curl-multi-functions.h @@ -32,12 +32,12 @@ inline auto f$curl_multi_init() noexcept -> kphp::coro::task kphp::coro::task> { auto& curl_state{CurlInstanceState::get()}; - if (!curl_state.multi_ctx.has(multi_id)) { + if (!curl_state.multi_ctx.has(multi_id)) [[unlikely]] { co_return false; } auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; - if (!curl_state.easy_ctx.has(easy_id)) { + if (!curl_state.easy_ctx.has(easy_id)) [[unlikely]] { co_return false; } auto& easy_ctx{curl_state.easy_ctx.get_or_init(easy_id)}; @@ -53,16 +53,18 @@ inline auto f$curl_multi_add_handle(kphp::web::curl::multi_type multi_id, kphp:: co_return multi_ctx.error_code; } -inline auto f$curl_multi_remove_handle(kphp::web::curl::multi_type multi_id, kphp::web::curl::easy_type easy_id) noexcept -> kphp::coro::task> { +inline auto f$curl_multi_remove_handle(kphp::web::curl::multi_type multi_id, + kphp::web::curl::easy_type easy_id) noexcept -> kphp::coro::task> { auto& curl_state{CurlInstanceState::get()}; - if (!curl_state.multi_ctx.has(multi_id)) { + if (!curl_state.multi_ctx.has(multi_id)) [[unlikely]] { co_return false; } auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; - if (!curl_state.easy_ctx.has(easy_id)) { + if (!curl_state.easy_ctx.has(easy_id)) [[unlikely]] { co_return false; } - auto res{co_await kphp::forks::id_managed(kphp::web::composite_transfer_remove(kphp::web::composite_transfer{multi_id}, kphp::web::simple_transfer{easy_id}))}; + auto res{ + co_await kphp::forks::id_managed(kphp::web::composite_transfer_remove(kphp::web::composite_transfer{multi_id}, kphp::web::simple_transfer{easy_id}))}; if (!res.has_value()) [[unlikely]] { multi_ctx.set_errno(res.error().code, res.error().description); kphp::web::curl::print_error("Could not remove a curl easy handler from multi handle", std::move(res.error())); @@ -72,6 +74,22 @@ inline auto f$curl_multi_remove_handle(kphp::web::curl::multi_type multi_id, kph co_return multi_ctx.error_code; } +inline auto f$curl_multi_close(kphp::web::curl::multi_type multi_id) noexcept -> kphp::coro::task { + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.multi_ctx.has(multi_id)) [[unlikely]] { + co_return; + } + auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; + auto res{co_await kphp::forks::id_managed(kphp::web::composite_transfer_close(kphp::web::composite_transfer{multi_id}))}; + if (!res.has_value()) [[unlikely]] { + multi_ctx.set_errno(res.error().code, res.error().description); + kphp::web::curl::print_error("Could not close curl multi handle", std::move(res.error())); + co_return; + } + multi_ctx.set_errno(kphp::web::curl::CURLE::OK); + co_return; +} + inline Optional> f$curl_multi_info_read([[maybe_unused]] int64_t /*unused*/, [[maybe_unused]] Optional>> /*unused*/ = {}) { kphp::log::error("call to unsupported function : curl_multi_info_read"); diff --git a/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h b/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h index 9884e73878..7346a482d9 100644 --- a/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h +++ b/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h @@ -96,14 +96,15 @@ inline auto composite_transfer_add(composite_transfer ct, simple_transfer st) no // Ensure that simple transfer hasn't been added in some composite yet auto& simple_transfers{web_state.composite_transfer2simple_transfers[ct.descriptor]}; if (simple_transfers.contains(st.descriptor)) { - co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"a Composite transfer contains this Simple transfer"}}}; + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"the Composite transfer already includes this Simple transfer"}}}; } simple_transfers.emplace(st.descriptor); // Set a holder for simple transfer auto& composite_holder{web_state.simple_transfer2holder[st.descriptor]}; if (composite_holder.has_value()) { - co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"a Simple transfer is held by some Composite transfer already"}}}; + co_return std::unexpected{ + error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"this Simple transfer is already part of another Composite transfer"}}}; } composite_holder.emplace(ct.descriptor); @@ -167,17 +168,17 @@ inline auto composite_transfer_remove(composite_transfer ct, simple_transfer st) kphp::log::error("session with Web components has been closed"); } - // Ensure that simple transfer has been added in some composite yet + // Ensure that simple transfer alreadt has been added in some composite auto& simple_transfers{web_state.composite_transfer2simple_transfers[ct.descriptor]}; if (!simple_transfers.contains(st.descriptor)) { - co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"a Composite transfer doesn't contain this Simple transfer"}}}; + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"this Composite transfer does not include this Simple transfer"}}}; } simple_transfers.erase(st.descriptor); // Unset a holder for simple transfer auto& composite_holder{web_state.simple_transfer2holder[st.descriptor]}; if (!composite_holder.has_value()) { - co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"a Simple transfer isn't held by some Composite transfer yet"}}}; + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"this Simple transfer is not yet part of any Composite transfer"}}}; } composite_holder.reset(); @@ -210,4 +211,58 @@ inline auto composite_transfer_remove(composite_transfer ct, simple_transfer st) co_return std::expected{}; } +inline auto composite_transfer_close(composite_transfer ct) noexcept -> kphp::coro::task> { + auto& web_state{WebInstanceState::get()}; + + auto& composite2config{web_state.composite_transfer2config}; + if (!composite2config.contains(ct.descriptor)) [[unlikely]] { + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"unknown Composite transfer"}}}; + } + + auto session{web_state.session_get_or_init()}; + if (!session.has_value()) [[unlikely]] { + kphp::log::error("fastruct composite_transfer_config;iled to start or get session with Web component"); + } + + if ((*session).get() == nullptr) [[unlikely]] { + kphp::log::error("session with Web components has been closed"); + } + + // Enumerate over all included simple transfers and close them + auto& simple_transfers{web_state.composite_transfer2simple_transfers[ct.descriptor]}; + for (const auto st : simple_transfers) { + if (auto remove_res{co_await kphp::web::composite_transfer_remove(ct, kphp::web::simple_transfer{st})}; !remove_res.has_value()) { + co_return std::move(remove_res); + }; + } + simple_transfers.clear(); + + tl::CompositeWebTransferClose tl_close{tl::u64{ct.descriptor}}; + tl::storer tls{tl_close.footprint()}; + tl_close.store(tls); + + kphp::stl::vector resp_buf{}; + auto response_buffer_provider{[&resp_buf](size_t size) noexcept -> std::span { + resp_buf.resize(size); + return {resp_buf.data(), size}; + }}; + + auto resp{co_await (*session).get()->client.query(tls.view(), std::move(response_buffer_provider))}; + if (!resp.has_value()) [[unlikely]] { + kphp::log::error("failed to send request of closing Composite transfer"); + } + + tl::Either composite_close_resp{}; + tl::fetcher tlf{*resp}; + if (!composite_close_resp.fetch(tlf)) [[unlikely]] { + kphp::log::error("failed to parse response of closing Composite transfer"); + } + + if (auto r{composite_close_resp.value}; std::holds_alternative(r)) { + co_return std::unexpected{details::process_error(std::get(r))}; + } + + co_return std::expected{}; +} + } // namespace kphp::web diff --git a/runtime-light/stdlib/web-transfer-lib/web-simple-transfer.h b/runtime-light/stdlib/web-transfer-lib/web-simple-transfer.h index 42af7d95da..863a750e3b 100644 --- a/runtime-light/stdlib/web-transfer-lib/web-simple-transfer.h +++ b/runtime-light/stdlib/web-transfer-lib/web-simple-transfer.h @@ -75,9 +75,10 @@ inline auto simple_transfer_open(transfer_backend backend) noexcept -> kphp::cor inline auto simple_transfer_perform(simple_transfer st) noexcept -> kphp::coro::task> { auto& web_state{WebInstanceState::get()}; + auto& simple2config{web_state.simple_transfer2config}; if (!simple2config.contains(st.descriptor)) [[unlikely]] { - kphp::log::error("unknown Simple descriptor"); + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"unknown Simple transfer"}}}; } auto session{web_state.session_get_or_init()}; @@ -225,8 +226,10 @@ inline auto simple_transfer_close(simple_transfer st) noexcept -> kphp::coro::ta // Checking that Simple transfer is held by some Composite transfer auto& composite_holder{web_state.simple_transfer2holder[st.descriptor]}; if (composite_holder.has_value()) { - if (auto close_res{co_await kphp::web::composite_transfer_remove(kphp::web::composite_transfer{*composite_holder}, kphp::web::simple_transfer{st.descriptor})}; !close_res.has_value()) { - co_return std::move(close_res); + if (auto remove_res{ + co_await kphp::web::composite_transfer_remove(kphp::web::composite_transfer{*composite_holder}, kphp::web::simple_transfer{st.descriptor})}; + !remove_res.has_value()) { + co_return std::move(remove_res); }; } diff --git a/runtime-light/stdlib/web-transfer-lib/web-state.h b/runtime-light/stdlib/web-transfer-lib/web-state.h index b2c1350293..27c7103a4e 100644 --- a/runtime-light/stdlib/web-transfer-lib/web-state.h +++ b/runtime-light/stdlib/web-transfer-lib/web-state.h @@ -31,7 +31,8 @@ struct WebInstanceState final : private vk::not_copyable { kphp::stl::unordered_map composite_transfer2config{}; - kphp::stl::map composite_transfer2simple_transfers{}; + kphp::stl::map + composite_transfer2simple_transfers{}; inline auto session_get_or_init() noexcept -> std::expected; diff --git a/runtime-light/tl/tl-functions.h b/runtime-light/tl/tl-functions.h index 281a58dc8b..0e7048ca59 100644 --- a/runtime-light/tl/tl-functions.h +++ b/runtime-light/tl/tl-functions.h @@ -520,7 +520,8 @@ class CompositeWebTransferAdd final { } constexpr size_t footprint() const noexcept { - return tl::magic{.value = COMPOSITE_WEB_TRANSFER_ADD_MAGIC}.footprint() + composite_descriptor.footprint() + simple_descriptor.footprint() + simple_config.footprint(); + return tl::magic{.value = COMPOSITE_WEB_TRANSFER_ADD_MAGIC}.footprint() + composite_descriptor.footprint() + simple_descriptor.footprint() + + simple_config.footprint(); } }; @@ -542,4 +543,20 @@ class CompositeWebTransferRemove final { } }; +class CompositeWebTransferClose final { + static constexpr uint32_t COMPOSITE_WEB_TRANSFER_CLOSE_MAGIC = 0x7162'22AB; + +public: + tl::u64 descriptor; + + void store(tl::storer& tls) const noexcept { + tl::magic{.value = COMPOSITE_WEB_TRANSFER_CLOSE_MAGIC}.store(tls); + descriptor.store(tls); + } + + constexpr size_t footprint() const noexcept { + return tl::magic{.value = COMPOSITE_WEB_TRANSFER_CLOSE_MAGIC}.footprint() + descriptor.footprint(); + } +}; + } // namespace tl diff --git a/runtime-light/tl/tl-types.h b/runtime-light/tl/tl-types.h index ae3b25fed8..081c2615dd 100644 --- a/runtime-light/tl/tl-types.h +++ b/runtime-light/tl/tl-types.h @@ -1340,7 +1340,6 @@ class CompositeWebTransferAddResultOk final { static constexpr uint32_t COMPOSITE_WEB_TRANSFER_ADD_RESULT_OK_MAGIC = 0x3161'22DD; public: - bool fetch(tl::fetcher& tlf) noexcept { tl::magic magic{}; bool ok{magic.fetch(tlf) && magic.expect(COMPOSITE_WEB_TRANSFER_ADD_RESULT_OK_MAGIC)}; @@ -1356,7 +1355,6 @@ class CompositeWebTransferRemoveResultOk final { static constexpr uint32_t COMPOSITE_WEB_TRANSFER_REMOVE_RESULT_OK_MAGIC = 0x8981'22FF; public: - bool fetch(tl::fetcher& tlf) noexcept { tl::magic magic{}; bool ok{magic.fetch(tlf) && magic.expect(COMPOSITE_WEB_TRANSFER_REMOVE_RESULT_OK_MAGIC)}; @@ -1368,4 +1366,19 @@ class CompositeWebTransferRemoveResultOk final { } }; +class CompositeWebTransferCloseResultOk final { + static constexpr uint32_t COMPOSITE_WEB_TRANSFER_CLOSE_RESULT_OK_MAGIC = 0xAB71'6222; + +public: + bool fetch(tl::fetcher& tlf) noexcept { + tl::magic magic{}; + bool ok{magic.fetch(tlf) && magic.expect(COMPOSITE_WEB_TRANSFER_CLOSE_RESULT_OK_MAGIC)}; + return ok; + } + + constexpr size_t footprint() const noexcept { + return tl::magic{.value = COMPOSITE_WEB_TRANSFER_CLOSE_RESULT_OK_MAGIC}.footprint(); + } +}; + } // namespace tl diff --git a/tests/phpt/curl/5_curl_multi_init.php b/tests/phpt/curl/5_curl_multi_init.php index f90d49b9f2..4f9ad8b8b9 100644 --- a/tests/phpt/curl/5_curl_multi_init.php +++ b/tests/phpt/curl/5_curl_multi_init.php @@ -1,4 +1,4 @@ -@ok k2_skip +@ok Date: Sun, 30 Nov 2025 17:46:05 +0300 Subject: [PATCH 06/15] Add curl_multi_setopt support Signed-off-by: Petr Shumilov --- .../kphp-light/stdlib/curl-functions.txt | 47 ++++++++------- runtime-light/stdlib/curl/curl-context.h | 8 +++ .../stdlib/curl/curl-multi-functions.h | 59 +++++++++++++++++++ runtime-light/stdlib/curl/defs.h | 28 +++++++++ .../web-transfer-lib/web-composite-transfer.h | 29 ++++----- tests/phpt/curl/7_curl_multi_setopt.php | 2 +- 6 files changed, 134 insertions(+), 39 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/curl-functions.txt b/builtin-functions/kphp-light/stdlib/curl-functions.txt index 0c50aa6afd..95f68951f1 100644 --- a/builtin-functions/kphp-light/stdlib/curl-functions.txt +++ b/builtin-functions/kphp-light/stdlib/curl-functions.txt @@ -205,18 +205,6 @@ define('CURL_NETRC_REQUIRED', 2); define('CURLOPT_RETURNTRANSFER', 1234567); -define('CURLMOPT_PIPELINING', 1000); -define('CURLMOPT_MAXCONNECTS', 1001); -define('CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE', 1002); -define('CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE', 1003); -define('CURLMOPT_MAX_HOST_CONNECTIONS', 1004); -define('CURLMOPT_MAX_PIPELINE_LENGTH', 1005); -define('CURLMOPT_MAX_TOTAL_CONNECTIONS', 1006); - -define('CURLPIPE_NOTHING', 0); -define('CURLPIPE_HTTP1', 1); -define('CURLPIPE_MULTIPLEX', 2); - define('CURL_HTTP_VERSION_NONE', 0); define('CURL_HTTP_VERSION_1_0', 1); define('CURL_HTTP_VERSION_1_1', 2); @@ -225,6 +213,15 @@ define('CURL_HTTP_VERSION_2', CURL_HTTP_VERSION_2_0); define('CURL_HTTP_VERSION_2TLS', 4); define('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE', 5); +define('UPLOAD_ERR_OK', 0); +define('UPLOAD_ERR_INI_SIZE', 1); +define('UPLOAD_ERR_FORM_SIZE', 2); +define('UPLOAD_ERR_PARTIAL', 3); +define('UPLOAD_ERR_NO_FILE', 4); +define('UPLOAD_ERR_NO_TMP_DIR', 6); +define('UPLOAD_ERR_CANT_WRITE', 7); +define('UPLOAD_ERR_EXTENSION', 8); + define("CURLM_CALL_MULTI_PERFORM", -1); define("CURLM_OK", 0); define("CURLM_BAD_HANDLE", 1); @@ -235,14 +232,17 @@ define("CURLM_BAD_SOCKET", 5); define("CURLM_UNKNOWN_OPTION", 6); define("CURLM_ADDED_ALREADY", 7); -define('UPLOAD_ERR_OK', 0); -define('UPLOAD_ERR_INI_SIZE', 1); -define('UPLOAD_ERR_FORM_SIZE', 2); -define('UPLOAD_ERR_PARTIAL', 3); -define('UPLOAD_ERR_NO_FILE', 4); -define('UPLOAD_ERR_NO_TMP_DIR', 6); -define('UPLOAD_ERR_CANT_WRITE', 7); -define('UPLOAD_ERR_EXTENSION', 8); +define('CURLMOPT_PIPELINING', 3); +define('CURLMOPT_MAXCONNECTS', 6); +define('CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE', 30010); +define('CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE', 30009); +define('CURLMOPT_MAX_HOST_CONNECTIONS', 7); +define('CURLMOPT_MAX_PIPELINE_LENGTH', 8); +define('CURLMOPT_MAX_TOTAL_CONNECTIONS', 13); + +define('CURLPIPE_NOTHING', 0); +define('CURLPIPE_HTTP1', 1); +define('CURLPIPE_MULTIPLEX', 2); // ===== SUPPORTED ===== /** @kphp-extern-func-info interruptible */ @@ -270,18 +270,21 @@ function curl_getinfo ($curl_handle ::: int, $option ::: int = 0) ::: mixed; /** @kphp-extern-func-info interruptible */ function curl_multi_init () ::: int; + /** @kphp-extern-func-info interruptible */ function curl_multi_add_handle ($multi_handle ::: int, $curl_handle ::: int) ::: int|false; /** @kphp-extern-func-info interruptible */ function curl_multi_remove_handle ($multi_handle ::: int, $curl_handle ::: int) ::: int|false; + +function curl_multi_setopt ($multi_handle ::: int, $option ::: int, $value ::: int) ::: bool; + /** @kphp-extern-func-info interruptible */ function curl_multi_close ($multi_handle ::: int) ::: void; // ===== UNSUPPORTED ===== /** @kphp-extern-func-info stub generation-required */ function curl_multi_getcontent ($curl_handle ::: int ) ::: string|false|null; -/** @kphp-extern-func-info stub generation-required */ -function curl_multi_setopt ($multi_handle ::: int, $option ::: int, $value ::: int) ::: bool; + /** @kphp-extern-func-info stub generation-required */ function curl_multi_exec ($multi_handle ::: int, &$still_running ::: int) ::: int|false; /** @kphp-extern-func-info stub generation-required */ diff --git a/runtime-light/stdlib/curl/curl-context.h b/runtime-light/stdlib/curl/curl-context.h index 77c6f5be9e..5f3afd9aaa 100644 --- a/runtime-light/stdlib/curl/curl-context.h +++ b/runtime-light/stdlib/curl/curl-context.h @@ -45,6 +45,14 @@ struct curl_context : vk::movable_only { set_errno(static_cast(code), std::move(description)); } + inline auto set_errno(kphp::web::curl::CURLMcode code, std::string_view description) noexcept { + set_errno(static_cast(code), description); + } + + inline auto set_errno(kphp::web::curl::CURLMcode code, std::optional description = std::nullopt) noexcept { + set_errno(static_cast(code), std::move(description)); + } + template inline auto bad_option_error(const char (&msg)[N]) noexcept { static_assert(N <= CURL_ERROR_SIZE, "too long error"); diff --git a/runtime-light/stdlib/curl/curl-multi-functions.h b/runtime-light/stdlib/curl/curl-multi-functions.h index 8fa33a8750..f193d7840d 100644 --- a/runtime-light/stdlib/curl/curl-multi-functions.h +++ b/runtime-light/stdlib/curl/curl-multi-functions.h @@ -17,6 +17,7 @@ #include "runtime-light/stdlib/diagnostics/logs.h" #include "runtime-light/stdlib/fork/fork-functions.h" #include "runtime-light/stdlib/web-transfer-lib/defs.h" +#include "runtime-light/stdlib/web-transfer-lib/details/web-property.h" #include "runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h" inline auto f$curl_multi_init() noexcept -> kphp::coro::task { @@ -90,6 +91,64 @@ inline auto f$curl_multi_close(kphp::web::curl::multi_type multi_id) noexcept -> co_return; } +inline auto f$curl_multi_setopt(kphp::web::curl::multi_type multi_id, int64_t option, int64_t value) noexcept -> bool { + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.multi_ctx.has(multi_id)) { + return false; + } + auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; + auto ct{kphp::web::composite_transfer{.descriptor = multi_id}}; + + switch (static_cast(option)) { + // long options + case kphp::web::curl::CURMLOPT::PIPELINING: { + auto pipeling_type{static_cast(value)}; + switch (pipeling_type) { + case kphp::web::curl::CURLPIPE::NOTHING: + case kphp::web::curl::CURLPIPE::HTTP1: + case kphp::web::curl::CURLPIPE::MULTIPLEX: { + auto res{kphp::web::set_transfer_property(ct, option, kphp::web::property_value::as_long(static_cast(pipeling_type)))}; + if (!res.has_value()) [[unlikely]] { + kphp::web::curl::print_error("could not set an mutli option", std::move(res.error())); + return false; + } + multi_ctx.set_errno(kphp::web::curl::CURLMcode::OK); + return true; + } + default: + multi_ctx.set_errno(kphp::web::curl::CURLMcode::UNKNOWN_OPTION, "a libcurl function was given an incorrect PROXYTYPE kind"); + return false; + } + } + case kphp::web::curl::CURMLOPT::MAXCONNECTS: + case kphp::web::curl::CURMLOPT::MAX_HOST_CONNECTIONS: + case kphp::web::curl::CURMLOPT::MAX_PIPELINE_LENGTH: + case kphp::web::curl::CURMLOPT::MAX_TOTAL_CONNECTIONS: { + auto res{kphp::web::set_transfer_property(ct, option, kphp::web::property_value::as_long(value))}; + if (!res.has_value()) [[unlikely]] { + kphp::web::curl::print_error("could not set an multi option", std::move(res.error())); + return false; + } + multi_ctx.set_errno(kphp::web::curl::CURLMcode::OK); + return true; + } + // off_t options + case kphp::web::curl::CURMLOPT::CHUNK_LENGTH_PENALTY_SIZE: + case kphp::web::curl::CURMLOPT::CONTENT_LENGTH_PENALTY_SIZE: { + auto res{kphp::web::set_transfer_property(ct, option, kphp::web::property_value::as_long(value))}; + if (!res.has_value()) [[unlikely]] { + kphp::web::curl::print_error("could not set an multi option", std::move(res.error())); + return false; + } + multi_ctx.set_errno(kphp::web::curl::CURLMcode::OK); + return true; + } + default: + multi_ctx.set_errno(kphp::web::curl::CURLMcode::UNKNOWN_OPTION); + return false; + } +} + inline Optional> f$curl_multi_info_read([[maybe_unused]] int64_t /*unused*/, [[maybe_unused]] Optional>> /*unused*/ = {}) { kphp::log::error("call to unsupported function : curl_multi_info_read"); diff --git a/runtime-light/stdlib/curl/defs.h b/runtime-light/stdlib/curl/defs.h index 3ee1e8e08e..ddc7bebb71 100644 --- a/runtime-light/stdlib/curl/defs.h +++ b/runtime-light/stdlib/curl/defs.h @@ -249,4 +249,32 @@ enum class CURL_HTTP_VERSION : uint8_t { _2_PRIOR_KNOWLEDGE = 5, }; +enum class CURLMcode : int16_t { + CALL_MULTI_PERFORM = -1, + OK = 0, + BAD_HANDLE = 1, + BAD_EASY_HANDLE = 2, + OUT_OF_MEMORY = 3, + INTERNAL_ERROR = 4, + BAD_SOCKET = 5, + UNKNOWN_OPTION = 6, + ADDED_ALREADY = 7, +}; + +enum class CURMLOPT : uint64_t { + PIPELINING = 3, + MAXCONNECTS = 6, + CHUNK_LENGTH_PENALTY_SIZE = 30010, + CONTENT_LENGTH_PENALTY_SIZE = 30009, + MAX_HOST_CONNECTIONS = 7, + MAX_PIPELINE_LENGTH = 8, + MAX_TOTAL_CONNECTIONS = 13, +}; + +enum class CURLPIPE : uint8_t { + NOTHING = 0, + HTTP1 = 1, + MULTIPLEX = 2, +}; + } // namespace kphp::web::curl diff --git a/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h b/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h index 7346a482d9..92305450d9 100644 --- a/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h +++ b/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h @@ -93,21 +93,6 @@ inline auto composite_transfer_add(composite_transfer ct, simple_transfer st) no kphp::log::error("session with Web components has been closed"); } - // Ensure that simple transfer hasn't been added in some composite yet - auto& simple_transfers{web_state.composite_transfer2simple_transfers[ct.descriptor]}; - if (simple_transfers.contains(st.descriptor)) { - co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"the Composite transfer already includes this Simple transfer"}}}; - } - simple_transfers.emplace(st.descriptor); - - // Set a holder for simple transfer - auto& composite_holder{web_state.simple_transfer2holder[st.descriptor]}; - if (composite_holder.has_value()) { - co_return std::unexpected{ - error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"this Simple transfer is already part of another Composite transfer"}}}; - } - composite_holder.emplace(ct.descriptor); - // Prepare simple config kphp::stl::vector tl_simple_props{}; auto& props{simple2config[st.descriptor].properties}; @@ -143,6 +128,18 @@ inline auto composite_transfer_add(composite_transfer ct, simple_transfer st) no co_return std::unexpected{details::process_error(std::get(r))}; } + // Ensure that simple transfer hasn't been added in some composite yet + auto& simple_transfers{web_state.composite_transfer2simple_transfers[ct.descriptor]}; + if (!simple_transfers.contains(st.descriptor)) { + simple_transfers.emplace(st.descriptor); + } + + // Set a holder for simple transfer + auto& composite_holder{web_state.simple_transfer2holder[st.descriptor]}; + if (!composite_holder.has_value()) { + composite_holder.emplace(ct.descriptor); + } + co_return std::expected{}; } @@ -221,7 +218,7 @@ inline auto composite_transfer_close(composite_transfer ct) noexcept -> kphp::co auto session{web_state.session_get_or_init()}; if (!session.has_value()) [[unlikely]] { - kphp::log::error("fastruct composite_transfer_config;iled to start or get session with Web component"); + kphp::log::error("failed to start or get session with Web component"); } if ((*session).get() == nullptr) [[unlikely]] { diff --git a/tests/phpt/curl/7_curl_multi_setopt.php b/tests/phpt/curl/7_curl_multi_setopt.php index 07c006322d..c86dc8ca63 100644 --- a/tests/phpt/curl/7_curl_multi_setopt.php +++ b/tests/phpt/curl/7_curl_multi_setopt.php @@ -1,4 +1,4 @@ -@ok k2_skip +@ok Date: Sun, 30 Nov 2025 19:41:04 +0300 Subject: [PATCH 07/15] Add curl_multi_exec support Signed-off-by: Petr Shumilov --- .../kphp-light/stdlib/curl-functions.txt | 10 +-- .../stdlib/curl/curl-multi-functions.h | 60 ++++++++++-------- runtime-light/stdlib/curl/defs.h | 4 +- .../web-transfer-lib/web-composite-transfer.h | 63 ++++++++++++++++++- runtime-light/tl/tl-functions.h | 18 ++++++ runtime-light/tl/tl-types.h | 52 +++++++++++---- tests/phpt/curl/8_curl_multi_exec.php | 2 +- 7 files changed, 161 insertions(+), 48 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/curl-functions.txt b/builtin-functions/kphp-light/stdlib/curl-functions.txt index 95f68951f1..b86432b1d9 100644 --- a/builtin-functions/kphp-light/stdlib/curl-functions.txt +++ b/builtin-functions/kphp-light/stdlib/curl-functions.txt @@ -234,8 +234,8 @@ define("CURLM_ADDED_ALREADY", 7); define('CURLMOPT_PIPELINING', 3); define('CURLMOPT_MAXCONNECTS', 6); -define('CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE', 30010); -define('CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE', 30009); +// define('CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE', 30010); +// define('CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE', 30009); define('CURLMOPT_MAX_HOST_CONNECTIONS', 7); define('CURLMOPT_MAX_PIPELINE_LENGTH', 8); define('CURLMOPT_MAX_TOTAL_CONNECTIONS', 13); @@ -278,15 +278,15 @@ function curl_multi_remove_handle ($multi_handle ::: int, $curl_handle ::: int) function curl_multi_setopt ($multi_handle ::: int, $option ::: int, $value ::: int) ::: bool; +/** @kphp-extern-func-info interruptible */ +function curl_multi_exec ($multi_handle ::: int, &$still_running ::: int) ::: int|false; + /** @kphp-extern-func-info interruptible */ function curl_multi_close ($multi_handle ::: int) ::: void; // ===== UNSUPPORTED ===== /** @kphp-extern-func-info stub generation-required */ function curl_multi_getcontent ($curl_handle ::: int ) ::: string|false|null; - -/** @kphp-extern-func-info stub generation-required */ -function curl_multi_exec ($multi_handle ::: int, &$still_running ::: int) ::: int|false; /** @kphp-extern-func-info stub generation-required */ function curl_multi_select ($multi_handle ::: int, $timeout ::: float = 1.0) ::: int|false; diff --git a/runtime-light/stdlib/curl/curl-multi-functions.h b/runtime-light/stdlib/curl/curl-multi-functions.h index f193d7840d..300475ea53 100644 --- a/runtime-light/stdlib/curl/curl-multi-functions.h +++ b/runtime-light/stdlib/curl/curl-multi-functions.h @@ -75,22 +75,6 @@ inline auto f$curl_multi_remove_handle(kphp::web::curl::multi_type multi_id, co_return multi_ctx.error_code; } -inline auto f$curl_multi_close(kphp::web::curl::multi_type multi_id) noexcept -> kphp::coro::task { - auto& curl_state{CurlInstanceState::get()}; - if (!curl_state.multi_ctx.has(multi_id)) [[unlikely]] { - co_return; - } - auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; - auto res{co_await kphp::forks::id_managed(kphp::web::composite_transfer_close(kphp::web::composite_transfer{multi_id}))}; - if (!res.has_value()) [[unlikely]] { - multi_ctx.set_errno(res.error().code, res.error().description); - kphp::web::curl::print_error("Could not close curl multi handle", std::move(res.error())); - co_return; - } - multi_ctx.set_errno(kphp::web::curl::CURLE::OK); - co_return; -} - inline auto f$curl_multi_setopt(kphp::web::curl::multi_type multi_id, int64_t option, int64_t value) noexcept -> bool { auto& curl_state{CurlInstanceState::get()}; if (!curl_state.multi_ctx.has(multi_id)) { @@ -132,23 +116,45 @@ inline auto f$curl_multi_setopt(kphp::web::curl::multi_type multi_id, int64_t op multi_ctx.set_errno(kphp::web::curl::CURLMcode::OK); return true; } - // off_t options - case kphp::web::curl::CURMLOPT::CHUNK_LENGTH_PENALTY_SIZE: - case kphp::web::curl::CURMLOPT::CONTENT_LENGTH_PENALTY_SIZE: { - auto res{kphp::web::set_transfer_property(ct, option, kphp::web::property_value::as_long(value))}; - if (!res.has_value()) [[unlikely]] { - kphp::web::curl::print_error("could not set an multi option", std::move(res.error())); - return false; - } - multi_ctx.set_errno(kphp::web::curl::CURLMcode::OK); - return true; - } default: multi_ctx.set_errno(kphp::web::curl::CURLMcode::UNKNOWN_OPTION); return false; } } +inline auto f$curl_multi_exec(kphp::web::curl::multi_type multi_id, int64_t& still_running) noexcept -> kphp::coro::task>{ + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.multi_ctx.has(multi_id)) [[unlikely]] { + co_return false; + } + auto res{co_await kphp::forks::id_managed(kphp::web::composite_transfer_perform(kphp::web::composite_transfer{multi_id}))}; + auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; + if (!res.has_value()) [[unlikely]] { + multi_ctx.set_errno(res.error().code, res.error().description); + kphp::web::curl::print_error("could not execute curl multi handle", std::move(res.error())); + co_return multi_ctx.error_code; + } + still_running = (*res); + multi_ctx.set_errno(kphp::web::curl::CURLE::OK); + co_return multi_ctx.error_code; +} + +inline auto f$curl_multi_close(kphp::web::curl::multi_type multi_id) noexcept -> kphp::coro::task { + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.multi_ctx.has(multi_id)) [[unlikely]] { + co_return; + } + auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; + auto res{co_await kphp::forks::id_managed(kphp::web::composite_transfer_close(kphp::web::composite_transfer{multi_id}))}; + if (!res.has_value()) [[unlikely]] { + multi_ctx.set_errno(res.error().code, res.error().description); + kphp::web::curl::print_error("Could not close curl multi handle", std::move(res.error())); + co_return; + } + multi_ctx.set_errno(kphp::web::curl::CURLE::OK); + co_return; +} + inline Optional> f$curl_multi_info_read([[maybe_unused]] int64_t /*unused*/, [[maybe_unused]] Optional>> /*unused*/ = {}) { kphp::log::error("call to unsupported function : curl_multi_info_read"); diff --git a/runtime-light/stdlib/curl/defs.h b/runtime-light/stdlib/curl/defs.h index ddc7bebb71..835dc7450f 100644 --- a/runtime-light/stdlib/curl/defs.h +++ b/runtime-light/stdlib/curl/defs.h @@ -264,8 +264,8 @@ enum class CURLMcode : int16_t { enum class CURMLOPT : uint64_t { PIPELINING = 3, MAXCONNECTS = 6, - CHUNK_LENGTH_PENALTY_SIZE = 30010, - CONTENT_LENGTH_PENALTY_SIZE = 30009, + // CHUNK_LENGTH_PENALTY_SIZE = 30010, + // CONTENT_LENGTH_PENALTY_SIZE = 30009, MAX_HOST_CONNECTIONS = 7, MAX_PIPELINE_LENGTH = 8, MAX_TOTAL_CONNECTIONS = 13, diff --git a/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h b/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h index 92305450d9..306517975b 100644 --- a/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h +++ b/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h @@ -4,7 +4,9 @@ #pragma once +#include <__expected/expected.h> #include +#include #include #include #include @@ -158,7 +160,7 @@ inline auto composite_transfer_remove(composite_transfer ct, simple_transfer st) auto session{web_state.session_get_or_init()}; if (!session.has_value()) [[unlikely]] { - kphp::log::error("fastruct composite_transfer_config;iled to start or get session with Web component"); + kphp::log::error("failed to start or get session with Web component"); } if ((*session).get() == nullptr) [[unlikely]] { @@ -208,6 +210,65 @@ inline auto composite_transfer_remove(composite_transfer ct, simple_transfer st) co_return std::expected{}; } +inline auto composite_transfer_perform(composite_transfer ct) noexcept -> kphp::coro::task> { + auto& web_state{WebInstanceState::get()}; + + auto& composite2config{web_state.composite_transfer2config}; + if (!composite2config.contains(ct.descriptor)) [[unlikely]] { + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"unknown Composite transfer"}}}; + } + + auto session{web_state.session_get_or_init()}; + if (!session.has_value()) [[unlikely]] { + kphp::log::error("failed to start or get session with Web component"); + } + + if ((*session).get() == nullptr) [[unlikely]] { + kphp::log::error("session with Web components has been closed"); + } + + // Prepare config + kphp::stl::vector tl_composite_props{}; + auto& props{composite2config[ct.descriptor].properties}; + for (const auto& [id, val] : props) { + tl::webProperty p{.id = tl::u64{id}, .value = val.serialize()}; + tl_composite_props.emplace_back(std::move(p)); + } + tl::compositeWebTransferConfig tl_composite_config{tl::vector{std::move(tl_composite_props)}}; + + // Prepare `CompositeWebTransferPerform` method + tl::CompositeWebTransferPerform tl_perform{ + .descriptor = tl::u64{ct.descriptor}, .config = std::move(tl_composite_config)}; + tl::storer tls{tl_perform.footprint()}; + tl_perform.store(tls); + + kphp::stl::vector resp_buf{}; + auto response_buffer_provider{[&resp_buf](size_t size) noexcept -> std::span { + resp_buf.resize(size); + return {resp_buf.data(), size}; + }}; + + auto resp{co_await (*session).get()->client.query(tls.view(), std::move(response_buffer_provider))}; + if (!resp.has_value()) [[unlikely]] { + kphp::log::error("failed to send request of performing Composite transfer"); + } + + tl::Either composite_perform_resp{}; + tl::fetcher tlf{*resp}; + if (!composite_perform_resp.fetch(tlf)) [[unlikely]] { + kphp::log::error("failed to parse response of performing Composite transfer"); + } + + const auto result{composite_perform_resp.value}; + if (std::holds_alternative(result)) { + co_return std::unexpected{details::process_error(std::get(result))}; + } + + const auto remaining{std::get(result).remaining.value}; + + co_return std::expected{remaining}; +} + inline auto composite_transfer_close(composite_transfer ct) noexcept -> kphp::coro::task> { auto& web_state{WebInstanceState::get()}; diff --git a/runtime-light/tl/tl-functions.h b/runtime-light/tl/tl-functions.h index 0e7048ca59..ded9e078d6 100644 --- a/runtime-light/tl/tl-functions.h +++ b/runtime-light/tl/tl-functions.h @@ -543,6 +543,24 @@ class CompositeWebTransferRemove final { } }; +class CompositeWebTransferPerform final { + static constexpr uint32_t COMPOSITE_WEB_TRANSFER_PERFORM_MAGIC = 0xAA24'42BB; + +public: + tl::u64 descriptor; + tl::compositeWebTransferConfig config; + + void store(tl::storer& tls) const noexcept { + tl::magic{.value = COMPOSITE_WEB_TRANSFER_PERFORM_MAGIC}.store(tls); + descriptor.store(tls); + config.store(tls); + } + + constexpr size_t footprint() const noexcept { + return tl::magic{.value = COMPOSITE_WEB_TRANSFER_PERFORM_MAGIC}.footprint() + descriptor.footprint() + config.footprint(); + } +}; + class CompositeWebTransferClose final { static constexpr uint32_t COMPOSITE_WEB_TRANSFER_CLOSE_MAGIC = 0x7162'22AB; diff --git a/runtime-light/tl/tl-types.h b/runtime-light/tl/tl-types.h index 081c2615dd..74c8b4dff6 100644 --- a/runtime-light/tl/tl-types.h +++ b/runtime-light/tl/tl-types.h @@ -1224,18 +1224,6 @@ struct webProperty final { } }; -struct simpleWebTransferConfig final { - tl::vector properties; - - void store(tl::storer& tls) const noexcept { - properties.store(tls); - } - - constexpr size_t footprint() const noexcept { - return properties.footprint(); - } -}; - class WebTransferGetPropertiesResultOk final { static constexpr uint32_t WEB_TRANSFER_GET_PROPERTIES_RESULT_OK_MAGIC = 0x48A7'16CC; @@ -1254,6 +1242,17 @@ class WebTransferGetPropertiesResultOk final { }; // ===== Simple ===== +struct simpleWebTransferConfig final { + tl::vector properties; + + void store(tl::storer& tls) const noexcept { + properties.store(tls); + } + + constexpr size_t footprint() const noexcept { + return properties.footprint(); + } +}; class SimpleWebTransferOpenResultOk final { static constexpr uint32_t SIMPLE_WEB_TRANSFER_OPEN_RESULT_OK_MAGIC = 0x24A8'98FF; @@ -1319,6 +1318,18 @@ class SimpleWebTransferResetResultOk final { // ===== Composite ===== +struct compositeWebTransferConfig final { + tl::vector properties; + + void store(tl::storer& tls) const noexcept { + properties.store(tls); + } + + constexpr size_t footprint() const noexcept { + return properties.footprint(); + } +}; + class CompositeWebTransferOpenResultOk final { static constexpr uint32_t COMPOSITE_WEB_TRANSFER_OPEN_RESULT_OK_MAGIC = 0x428A'89DD; @@ -1366,6 +1377,23 @@ class CompositeWebTransferRemoveResultOk final { } }; +class CompositeWebTransferPerformResultOk final { + static constexpr uint32_t COMPOSITE_WEB_TRANSFER_PERFORM_RESULT_OK_MAGIC = 0xFF42'24DD; + +public: + tl::u64 remaining; + + bool fetch(tl::fetcher& tlf) noexcept { + tl::magic magic{}; + bool ok{magic.fetch(tlf) && magic.expect(COMPOSITE_WEB_TRANSFER_PERFORM_RESULT_OK_MAGIC) && remaining.fetch(tlf)}; + return ok; + } + + constexpr size_t footprint() const noexcept { + return tl::magic{.value = COMPOSITE_WEB_TRANSFER_PERFORM_RESULT_OK_MAGIC}.footprint() + remaining.footprint(); + } +}; + class CompositeWebTransferCloseResultOk final { static constexpr uint32_t COMPOSITE_WEB_TRANSFER_CLOSE_RESULT_OK_MAGIC = 0xAB71'6222; diff --git a/tests/phpt/curl/8_curl_multi_exec.php b/tests/phpt/curl/8_curl_multi_exec.php index 2d99247881..399e245ebf 100644 --- a/tests/phpt/curl/8_curl_multi_exec.php +++ b/tests/phpt/curl/8_curl_multi_exec.php @@ -1,4 +1,4 @@ -@ok k2_skip +@ok Date: Sun, 30 Nov 2025 21:10:28 +0300 Subject: [PATCH 08/15] Add curl_multi_getcontent support Signed-off-by: Petr Shumilov --- .../kphp-light/stdlib/curl-functions.txt | 5 +- .../stdlib/curl/curl-multi-functions.h | 21 +++- .../web-transfer-lib/details/web-response.h | 95 +++++++++++++++++++ .../web-transfer-lib/web-composite-transfer.h | 3 +- .../web-transfer-lib/web-simple-transfer.h | 74 +++------------ runtime-light/tl/tl-functions.h | 16 ++++ 6 files changed, 148 insertions(+), 66 deletions(-) create mode 100644 runtime-light/stdlib/web-transfer-lib/details/web-response.h diff --git a/builtin-functions/kphp-light/stdlib/curl-functions.txt b/builtin-functions/kphp-light/stdlib/curl-functions.txt index b86432b1d9..0729f69dea 100644 --- a/builtin-functions/kphp-light/stdlib/curl-functions.txt +++ b/builtin-functions/kphp-light/stdlib/curl-functions.txt @@ -281,13 +281,14 @@ function curl_multi_setopt ($multi_handle ::: int, $option ::: int, $value ::: i /** @kphp-extern-func-info interruptible */ function curl_multi_exec ($multi_handle ::: int, &$still_running ::: int) ::: int|false; +/** @kphp-extern-func-info interruptible */ +function curl_multi_getcontent ($curl_handle ::: int ) ::: string|false|null; + /** @kphp-extern-func-info interruptible */ function curl_multi_close ($multi_handle ::: int) ::: void; // ===== UNSUPPORTED ===== /** @kphp-extern-func-info stub generation-required */ -function curl_multi_getcontent ($curl_handle ::: int ) ::: string|false|null; -/** @kphp-extern-func-info stub generation-required */ function curl_multi_select ($multi_handle ::: int, $timeout ::: float = 1.0) ::: int|false; /** @kphp-extern-func-info stub */ diff --git a/runtime-light/stdlib/curl/curl-multi-functions.h b/runtime-light/stdlib/curl/curl-multi-functions.h index 300475ea53..1cc1eae021 100644 --- a/runtime-light/stdlib/curl/curl-multi-functions.h +++ b/runtime-light/stdlib/curl/curl-multi-functions.h @@ -19,6 +19,7 @@ #include "runtime-light/stdlib/web-transfer-lib/defs.h" #include "runtime-light/stdlib/web-transfer-lib/details/web-property.h" #include "runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h" +#include "runtime-light/stdlib/web-transfer-lib/web-simple-transfer.h" inline auto f$curl_multi_init() noexcept -> kphp::coro::task { auto open_res{co_await kphp::forks::id_managed(kphp::web::composite_transfer_open(kphp::web::transfer_backend::CURL))}; @@ -122,7 +123,7 @@ inline auto f$curl_multi_setopt(kphp::web::curl::multi_type multi_id, int64_t op } } -inline auto f$curl_multi_exec(kphp::web::curl::multi_type multi_id, int64_t& still_running) noexcept -> kphp::coro::task>{ +inline auto f$curl_multi_exec(kphp::web::curl::multi_type multi_id, int64_t& still_running) noexcept -> kphp::coro::task> { auto& curl_state{CurlInstanceState::get()}; if (!curl_state.multi_ctx.has(multi_id)) [[unlikely]] { co_return false; @@ -139,6 +140,24 @@ inline auto f$curl_multi_exec(kphp::web::curl::multi_type multi_id, int64_t& sti co_return multi_ctx.error_code; } +inline auto f$curl_multi_getcontent(kphp::web::curl::easy_type easy_id) noexcept -> kphp::coro::task> { + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.easy_ctx.has(easy_id)) [[unlikely]] { + co_return false; + } + auto& easy_ctx{curl_state.easy_ctx.get_or_init(easy_id)}; + if (easy_ctx.return_transfer) { + auto res{co_await kphp::forks::id_managed(kphp::web::simple_transfer_get_response(kphp::web::simple_transfer{easy_id}))}; + if (!res.has_value()) [[unlikely]] { + easy_ctx.set_errno(res.error().code, res.error().description); + kphp::web::curl::print_error("Could not get response of curl easy handle", std::move(res.error())); + co_return false; + } + co_return (*res).body; + } + co_return Optional{}; +} + inline auto f$curl_multi_close(kphp::web::curl::multi_type multi_id) noexcept -> kphp::coro::task { auto& curl_state{CurlInstanceState::get()}; if (!curl_state.multi_ctx.has(multi_id)) [[unlikely]] { diff --git a/runtime-light/stdlib/web-transfer-lib/details/web-response.h b/runtime-light/stdlib/web-transfer-lib/details/web-response.h new file mode 100644 index 0000000000..645cdc1f4c --- /dev/null +++ b/runtime-light/stdlib/web-transfer-lib/details/web-response.h @@ -0,0 +1,95 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2025 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include <__expected/expected.h> +#include +#include +#include +#include +#include +#include + +#include "runtime-common/core/runtime-core.h" +#include "runtime-light/coroutine/task.h" +#include "runtime-light/stdlib/diagnostics/logs.h" +#include "runtime-light/stdlib/web-transfer-lib/defs.h" +#include "runtime-light/stdlib/web-transfer-lib/details/web-error.h" +#include "runtime-light/stdlib/web-transfer-lib/web-state.h" +#include "runtime-light/tl/tl-core.h" +#include "runtime-light/tl/tl-types.h" + +namespace kphp::web::details { + +inline auto process_simple_response(std::span request) noexcept -> kphp::coro::task> { + auto& web_state{WebInstanceState::get()}; + + auto session{web_state.session_get_or_init()}; + if (!session.has_value()) [[unlikely]] { + kphp::log::error("failed to start or get session with Web component"); + } + + if ((*session).get() == nullptr) [[unlikely]] { + kphp::log::error("session with Web components has been closed"); + } + + kphp::stl::vector ok_or_error_buffer{}; + response resp{}; + std::optional err{}; + auto frame_num{0}; + + auto response_buffer_provider{[&frame_num, &ok_or_error_buffer, &resp](size_t size) noexcept -> std::span { + switch (frame_num) { + case 0: + ok_or_error_buffer.resize(size); + return {ok_or_error_buffer.data(), size}; + case 1: + resp.headers = string{static_cast(size), false}; + return {reinterpret_cast(resp.headers.buffer()), size}; + case 2: + resp.body = string{static_cast(size), false}; + return {reinterpret_cast(resp.body.buffer()), size}; + default: + kphp::log::assertion(false); + return {}; + } + }}; + + const auto response_handler{[&frame_num, &err, &ok_or_error_buffer]( + [[maybe_unused]] std::span _) noexcept -> kphp::component::inter_component_session::client::response_readiness { + switch (frame_num) { + case 0: { + frame_num += 1; + tl::Either simple_web_transfer_perform_resp{}; + tl::fetcher tlf{ok_or_error_buffer}; + if (!simple_web_transfer_perform_resp.fetch(tlf)) [[unlikely]] { + kphp::log::error("failed to parse response of Simple descriptor"); + } + if (auto r{simple_web_transfer_perform_resp.value}; std::holds_alternative(r)) { + err.emplace(details::process_error(std::get(r))); + return kphp::component::inter_component_session::client::response_readiness::ready; + } + return kphp::component::inter_component_session::client::response_readiness::pending; + } + case 1: + frame_num += 1; + return kphp::component::inter_component_session::client::response_readiness::pending; + case 2: // NOLINT + return kphp::component::inter_component_session::client::response_readiness::ready; + default: + return kphp::component::inter_component_session::client::response_readiness::ready; + } + }}; + + if (auto res{co_await (*session).get()->client.query(request, std::move(response_buffer_provider), response_handler)}; !res) [[unlikely]] { + kphp::log::error("failed to send request of Simple descriptor processing"); + } + if (err.has_value()) [[unlikely]] { + co_return std::unexpected{std::move(*err)}; + } + co_return std::expected{std::move(resp)}; +} + +} // namespace kphp::web::details diff --git a/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h b/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h index 306517975b..5cd91d8917 100644 --- a/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h +++ b/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h @@ -237,8 +237,7 @@ inline auto composite_transfer_perform(composite_transfer ct) noexcept -> kphp:: tl::compositeWebTransferConfig tl_composite_config{tl::vector{std::move(tl_composite_props)}}; // Prepare `CompositeWebTransferPerform` method - tl::CompositeWebTransferPerform tl_perform{ - .descriptor = tl::u64{ct.descriptor}, .config = std::move(tl_composite_config)}; + tl::CompositeWebTransferPerform tl_perform{.descriptor = tl::u64{ct.descriptor}, .config = std::move(tl_composite_config)}; tl::storer tls{tl_perform.footprint()}; tl_perform.store(tls); diff --git a/runtime-light/stdlib/web-transfer-lib/web-simple-transfer.h b/runtime-light/stdlib/web-transfer-lib/web-simple-transfer.h index 863a750e3b..d2c330c486 100644 --- a/runtime-light/stdlib/web-transfer-lib/web-simple-transfer.h +++ b/runtime-light/stdlib/web-transfer-lib/web-simple-transfer.h @@ -15,6 +15,7 @@ #include "runtime-light/stdlib/diagnostics/logs.h" #include "runtime-light/stdlib/web-transfer-lib/defs.h" #include "runtime-light/stdlib/web-transfer-lib/details/web-error.h" +#include "runtime-light/stdlib/web-transfer-lib/details/web-response.h" #include "runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h" #include "runtime-light/stdlib/web-transfer-lib/web-state.h" #include "runtime-light/tl/tl-core.h" @@ -81,15 +82,6 @@ inline auto simple_transfer_perform(simple_transfer st) noexcept -> kphp::coro:: co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"unknown Simple transfer"}}}; } - auto session{web_state.session_get_or_init()}; - if (!session.has_value()) [[unlikely]] { - kphp::log::error("failed to start or get session with Web component"); - } - - if ((*session).get() == nullptr) [[unlikely]] { - kphp::log::error("session with Web components has been closed"); - } - kphp::stl::vector tl_props{}; auto& props{simple2config[st.descriptor].properties}; for (const auto& [id, val] : props) { @@ -101,61 +93,21 @@ inline auto simple_transfer_perform(simple_transfer st) noexcept -> kphp::coro:: tl::storer tls{tl_perform.footprint()}; tl_perform.store(tls); - kphp::stl::vector ok_or_error_buffer{}; - response resp{}; - std::optional err{}; - auto frame_num{0}; - - auto response_buffer_provider{[&frame_num, &ok_or_error_buffer, &resp](size_t size) noexcept -> std::span { - switch (frame_num) { - case 0: - ok_or_error_buffer.resize(size); - return {ok_or_error_buffer.data(), size}; - case 1: - resp.headers = string{static_cast(size), false}; - return {reinterpret_cast(resp.headers.buffer()), size}; - case 2: - resp.body = string{static_cast(size), false}; - return {reinterpret_cast(resp.body.buffer()), size}; - default: - kphp::log::assertion(false); - return {}; - } - }}; + co_return co_await details::process_simple_response(tls.view()); +} - const auto response_handler{[&frame_num, &err, &ok_or_error_buffer]( - [[maybe_unused]] std::span _) noexcept -> kphp::component::inter_component_session::client::response_readiness { - switch (frame_num) { - case 0: { - frame_num += 1; - tl::Either simple_web_transfer_perform_resp{}; - tl::fetcher tlf{ok_or_error_buffer}; - if (!simple_web_transfer_perform_resp.fetch(tlf)) [[unlikely]] { - kphp::log::error("failed to parse response of Simple descriptor performing"); - } - if (auto r{simple_web_transfer_perform_resp.value}; std::holds_alternative(r)) { - err.emplace(details::process_error(std::get(r))); - return kphp::component::inter_component_session::client::response_readiness::ready; - } - return kphp::component::inter_component_session::client::response_readiness::pending; - } - case 1: - frame_num += 1; - return kphp::component::inter_component_session::client::response_readiness::pending; - case 2: // NOLINT - return kphp::component::inter_component_session::client::response_readiness::ready; - default: - return kphp::component::inter_component_session::client::response_readiness::ready; - } - }}; +inline auto simple_transfer_get_response(simple_transfer st) noexcept -> kphp::coro::task> { + auto& web_state{WebInstanceState::get()}; - if (auto res{co_await (*session).get()->client.query(tls.view(), std::move(response_buffer_provider), response_handler)}; !res) [[unlikely]] { - kphp::log::error("failed to send request of Simple descriptor performing"); - } - if (err.has_value()) [[unlikely]] { - co_return std::unexpected{std::move(*err)}; + if (!web_state.simple_transfer2config.contains(st.descriptor)) { + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"unknown Simple transfer"}}}; } - co_return std::expected{std::move(resp)}; + + tl::SimpleWebTransferGetResponse web_transfer_get_resp{.descriptor = tl::u64{st.descriptor}}; + tl::storer tls{web_transfer_get_resp.footprint()}; + web_transfer_get_resp.store(tls); + + co_return co_await details::process_simple_response(tls.view()); } inline auto simple_transfer_reset(simple_transfer st) noexcept -> kphp::coro::task> { diff --git a/runtime-light/tl/tl-functions.h b/runtime-light/tl/tl-functions.h index ded9e078d6..5869a67333 100644 --- a/runtime-light/tl/tl-functions.h +++ b/runtime-light/tl/tl-functions.h @@ -453,6 +453,22 @@ class SimpleWebTransferPerform final { } }; +class SimpleWebTransferGetResponse final { + static constexpr uint32_t SIMPLE_WEB_TRANSFER_GET_RESPONSE_MAGIC = 0xAADD'FF24; + +public: + tl::u64 descriptor; + + void store(tl::storer& tls) const noexcept { + tl::magic{.value = SIMPLE_WEB_TRANSFER_GET_RESPONSE_MAGIC}.store(tls); + descriptor.store(tls); + } + + constexpr size_t footprint() const noexcept { + return tl::magic{.value = SIMPLE_WEB_TRANSFER_GET_RESPONSE_MAGIC}.footprint() + descriptor.footprint(); + } +}; + class SimpleWebTransferClose final { static constexpr uint32_t SIMPLE_WEB_TRANSFER_CLOSE_MAGIC = 0x36F7'16BB; From 8b94d794f3cef1f74401c1d09ce24e65ea8ff9b6 Mon Sep 17 00:00:00 2001 From: Petr Shumilov Date: Sun, 30 Nov 2025 21:40:39 +0300 Subject: [PATCH 09/15] Add curl_multi_errno and curl_multi_strerror support Signed-off-by: Petr Shumilov --- .../kphp-light/stdlib/curl-functions.txt | 8 +++---- .../stdlib/curl/curl-multi-functions.h | 24 +++++++++++++++++++ tests/phpt/curl/11_curl_multi_error.php | 2 +- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/curl-functions.txt b/builtin-functions/kphp-light/stdlib/curl-functions.txt index 0729f69dea..e30fdf0da1 100644 --- a/builtin-functions/kphp-light/stdlib/curl-functions.txt +++ b/builtin-functions/kphp-light/stdlib/curl-functions.txt @@ -287,13 +287,11 @@ function curl_multi_getcontent ($curl_handle ::: int ) ::: string|false|null; /** @kphp-extern-func-info interruptible */ function curl_multi_close ($multi_handle ::: int) ::: void; +function curl_multi_errno ($multi_handle ::: int) ::: int|false; +function curl_multi_strerror ($errornum ::: int) ::: string|null; + // ===== UNSUPPORTED ===== /** @kphp-extern-func-info stub generation-required */ function curl_multi_select ($multi_handle ::: int, $timeout ::: float = 1.0) ::: int|false; - /** @kphp-extern-func-info stub */ function curl_multi_info_read ($multi_handle ::: int, &$msgs_in_queue ::: int = TODO) ::: int[]|false; -/** @kphp-extern-func-info stub generation-required */ -function curl_multi_errno ($multi_handle ::: int) ::: int|false; -/** @kphp-extern-func-info stub generation-required */ -function curl_multi_strerror ($errornum ::: int) ::: string|null; diff --git a/runtime-light/stdlib/curl/curl-multi-functions.h b/runtime-light/stdlib/curl/curl-multi-functions.h index 1cc1eae021..5766f06e0e 100644 --- a/runtime-light/stdlib/curl/curl-multi-functions.h +++ b/runtime-light/stdlib/curl/curl-multi-functions.h @@ -174,6 +174,30 @@ inline auto f$curl_multi_close(kphp::web::curl::multi_type multi_id) noexcept -> co_return; } +inline auto f$curl_multi_strerror(kphp::web::curl::multi_type multi_id) noexcept -> Optional { + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.multi_ctx.has(multi_id)) { + return {}; + } + auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; + if (multi_ctx.error_code != static_cast(kphp::web::curl::CURLMcode::OK)) [[likely]] { + const auto* const desc_data{reinterpret_cast(multi_ctx.error_description.data())}; + const auto desc_size{static_cast(multi_ctx.error_description.size())}; + return string{desc_data, desc_size}; + } + return {}; +} + +inline auto f$curl_multi_errno(kphp::web::curl::multi_type multi_id) noexcept -> Optional { + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.multi_ctx.has(multi_id)) { + return false; + } + auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; + return multi_ctx.error_code; +} + + inline Optional> f$curl_multi_info_read([[maybe_unused]] int64_t /*unused*/, [[maybe_unused]] Optional>> /*unused*/ = {}) { kphp::log::error("call to unsupported function : curl_multi_info_read"); diff --git a/tests/phpt/curl/11_curl_multi_error.php b/tests/phpt/curl/11_curl_multi_error.php index 5854dd6ea8..b36a4e1cf1 100644 --- a/tests/phpt/curl/11_curl_multi_error.php +++ b/tests/phpt/curl/11_curl_multi_error.php @@ -1,4 +1,4 @@ -@ok k2_skip +@ok Date: Sun, 30 Nov 2025 21:56:56 +0300 Subject: [PATCH 10/15] Fix multi_setopt test Signed-off-by: Petr Shumilov --- .../stdlib/curl/curl-multi-functions.h | 3 +-- tests/phpt/curl/7_curl_multi_setopt.php | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/runtime-light/stdlib/curl/curl-multi-functions.h b/runtime-light/stdlib/curl/curl-multi-functions.h index 5766f06e0e..74f35de0cd 100644 --- a/runtime-light/stdlib/curl/curl-multi-functions.h +++ b/runtime-light/stdlib/curl/curl-multi-functions.h @@ -188,7 +188,7 @@ inline auto f$curl_multi_strerror(kphp::web::curl::multi_type multi_id) noexcept return {}; } -inline auto f$curl_multi_errno(kphp::web::curl::multi_type multi_id) noexcept -> Optional { +inline auto f$curl_multi_errno(kphp::web::curl::multi_type multi_id) noexcept -> Optional { auto& curl_state{CurlInstanceState::get()}; if (!curl_state.multi_ctx.has(multi_id)) { return false; @@ -197,7 +197,6 @@ inline auto f$curl_multi_errno(kphp::web::curl::multi_type multi_id) noexcept -> return multi_ctx.error_code; } - inline Optional> f$curl_multi_info_read([[maybe_unused]] int64_t /*unused*/, [[maybe_unused]] Optional>> /*unused*/ = {}) { kphp::log::error("call to unsupported function : curl_multi_info_read"); diff --git a/tests/phpt/curl/7_curl_multi_setopt.php b/tests/phpt/curl/7_curl_multi_setopt.php index c86dc8ca63..486e04181b 100644 --- a/tests/phpt/curl/7_curl_multi_setopt.php +++ b/tests/phpt/curl/7_curl_multi_setopt.php @@ -1,13 +1,11 @@ @ok Date: Mon, 1 Dec 2025 02:08:36 +0300 Subject: [PATCH 11/15] Add curl_multi_select support Signed-off-by: Petr Shumilov --- .../kphp-light/stdlib/curl-functions.txt | 5 +- .../stdlib/curl/curl-multi-functions.h | 31 ++++++++--- .../web-transfer-lib/web-composite-transfer.h | 52 +++++++++++++++++++ runtime-light/tl/tl-functions.h | 17 ++++++ runtime-light/tl/tl-types.h | 17 ++++++ tests/phpt/curl/9_curl_multi_select.php | 2 +- 6 files changed, 114 insertions(+), 10 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/curl-functions.txt b/builtin-functions/kphp-light/stdlib/curl-functions.txt index e30fdf0da1..9cda95f2e6 100644 --- a/builtin-functions/kphp-light/stdlib/curl-functions.txt +++ b/builtin-functions/kphp-light/stdlib/curl-functions.txt @@ -290,8 +290,9 @@ function curl_multi_close ($multi_handle ::: int) ::: void; function curl_multi_errno ($multi_handle ::: int) ::: int|false; function curl_multi_strerror ($errornum ::: int) ::: string|null; -// ===== UNSUPPORTED ===== -/** @kphp-extern-func-info stub generation-required */ +/** @kphp-extern-func-info interruptible */ function curl_multi_select ($multi_handle ::: int, $timeout ::: float = 1.0) ::: int|false; + +// ===== UNSUPPORTED ===== /** @kphp-extern-func-info stub */ function curl_multi_info_read ($multi_handle ::: int, &$msgs_in_queue ::: int = TODO) ::: int[]|false; diff --git a/runtime-light/stdlib/curl/curl-multi-functions.h b/runtime-light/stdlib/curl/curl-multi-functions.h index 74f35de0cd..32a8ed1763 100644 --- a/runtime-light/stdlib/curl/curl-multi-functions.h +++ b/runtime-light/stdlib/curl/curl-multi-functions.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -24,7 +25,7 @@ inline auto f$curl_multi_init() noexcept -> kphp::coro::task { auto open_res{co_await kphp::forks::id_managed(kphp::web::composite_transfer_open(kphp::web::transfer_backend::CURL))}; if (!open_res.has_value()) [[unlikely]] { - kphp::web::curl::print_error("Could not initialize a new curl multi handle", std::move(open_res.error())); + kphp::web::curl::print_error("could not initialize a new curl multi handle", std::move(open_res.error())); co_return 0; } const auto descriptor{(*open_res).descriptor}; @@ -48,7 +49,7 @@ inline auto f$curl_multi_add_handle(kphp::web::curl::multi_type multi_id, kphp:: auto res{co_await kphp::forks::id_managed(kphp::web::composite_transfer_add(kphp::web::composite_transfer{multi_id}, kphp::web::simple_transfer{easy_id}))}; if (!res.has_value()) [[unlikely]] { multi_ctx.set_errno(res.error().code, res.error().description); - kphp::web::curl::print_error("Could not add a curl easy handler into multi handle", std::move(res.error())); + kphp::web::curl::print_error("could not add a curl easy handler into multi handle", std::move(res.error())); co_return multi_ctx.error_code; } multi_ctx.set_errno(kphp::web::curl::CURLE::OK); @@ -69,7 +70,7 @@ inline auto f$curl_multi_remove_handle(kphp::web::curl::multi_type multi_id, co_await kphp::forks::id_managed(kphp::web::composite_transfer_remove(kphp::web::composite_transfer{multi_id}, kphp::web::simple_transfer{easy_id}))}; if (!res.has_value()) [[unlikely]] { multi_ctx.set_errno(res.error().code, res.error().description); - kphp::web::curl::print_error("Could not remove a curl easy handler from multi handle", std::move(res.error())); + kphp::web::curl::print_error("could not remove a curl easy handler from multi handle", std::move(res.error())); co_return multi_ctx.error_code; } multi_ctx.set_errno(kphp::web::curl::CURLE::OK); @@ -150,7 +151,7 @@ inline auto f$curl_multi_getcontent(kphp::web::curl::easy_type easy_id) noexcept auto res{co_await kphp::forks::id_managed(kphp::web::simple_transfer_get_response(kphp::web::simple_transfer{easy_id}))}; if (!res.has_value()) [[unlikely]] { easy_ctx.set_errno(res.error().code, res.error().description); - kphp::web::curl::print_error("Could not get response of curl easy handle", std::move(res.error())); + kphp::web::curl::print_error("could not get response of curl easy handle", std::move(res.error())); co_return false; } co_return (*res).body; @@ -167,7 +168,7 @@ inline auto f$curl_multi_close(kphp::web::curl::multi_type multi_id) noexcept -> auto res{co_await kphp::forks::id_managed(kphp::web::composite_transfer_close(kphp::web::composite_transfer{multi_id}))}; if (!res.has_value()) [[unlikely]] { multi_ctx.set_errno(res.error().code, res.error().description); - kphp::web::curl::print_error("Could not close curl multi handle", std::move(res.error())); + kphp::web::curl::print_error("could not close curl multi handle", std::move(res.error())); co_return; } multi_ctx.set_errno(kphp::web::curl::CURLE::OK); @@ -176,7 +177,7 @@ inline auto f$curl_multi_close(kphp::web::curl::multi_type multi_id) noexcept -> inline auto f$curl_multi_strerror(kphp::web::curl::multi_type multi_id) noexcept -> Optional { auto& curl_state{CurlInstanceState::get()}; - if (!curl_state.multi_ctx.has(multi_id)) { + if (!curl_state.multi_ctx.has(multi_id)) [[unlikely]] { return {}; } auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; @@ -190,13 +191,29 @@ inline auto f$curl_multi_strerror(kphp::web::curl::multi_type multi_id) noexcept inline auto f$curl_multi_errno(kphp::web::curl::multi_type multi_id) noexcept -> Optional { auto& curl_state{CurlInstanceState::get()}; - if (!curl_state.multi_ctx.has(multi_id)) { + if (!curl_state.multi_ctx.has(multi_id)) [[unlikely]] { return false; } auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; return multi_ctx.error_code; } +inline auto f$curl_multi_select(kphp::web::curl::multi_type multi_id, double timeout = 1.0) noexcept -> kphp::coro::task> { + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.multi_ctx.has(multi_id)) { + co_return false; + } + auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; + auto res{co_await kphp::forks::id_managed(kphp::web::composite_transfer_wait_updates( + kphp::web::composite_transfer{multi_id}, std::chrono::duration_cast(std::chrono::duration{timeout})))}; + if (!res.has_value()) [[unlikely]] { + multi_ctx.set_errno(res.error().code, res.error().description); + kphp::web::curl::print_error("could not select curl multi handle", std::move(res.error())); + co_return multi_ctx.error_code; + } + co_return *res; +} + inline Optional> f$curl_multi_info_read([[maybe_unused]] int64_t /*unused*/, [[maybe_unused]] Optional>> /*unused*/ = {}) { kphp::log::error("call to unsupported function : curl_multi_info_read"); diff --git a/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h b/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h index 5cd91d8917..7e9a474b75 100644 --- a/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h +++ b/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h @@ -5,6 +5,7 @@ #pragma once #include <__expected/expected.h> +#include #include #include #include @@ -322,4 +323,55 @@ inline auto composite_transfer_close(composite_transfer ct) noexcept -> kphp::co co_return std::expected{}; } +template +inline auto composite_transfer_wait_updates(composite_transfer ct, + std::chrono::duration timeout) noexcept -> kphp::coro::task> { + auto& web_state{WebInstanceState::get()}; + + auto& composite2config{web_state.composite_transfer2config}; + if (!composite2config.contains(ct.descriptor)) [[unlikely]] { + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"unknown Composite transfer"}}}; + } + + auto session{web_state.session_get_or_init()}; + if (!session.has_value()) [[unlikely]] { + kphp::log::error("failed to start or get session with Web component"); + } + + if ((*session).get() == nullptr) [[unlikely]] { + kphp::log::error("session with Web components has been closed"); + } + + tl::CompositeWebTransferWaitUpdates tl_wait_updates{ + .descriptor = tl::u64{ct.descriptor}, + .timeout = tl::u64{.value = static_cast(std::chrono::duration_cast(timeout).count())}}; + tl::storer tls{tl_wait_updates.footprint()}; + tl_wait_updates.store(tls); + + kphp::stl::vector resp_buf{}; + auto response_buffer_provider{[&resp_buf](size_t size) noexcept -> std::span { + resp_buf.resize(size); + return {resp_buf.data(), size}; + }}; + + auto resp{co_await (*session).get()->client.query(tls.view(), std::move(response_buffer_provider))}; + if (!resp.has_value()) [[unlikely]] { + kphp::log::error("failed to send request of waiting Composite transfer updates"); + } + + tl::Either composite_wait_resp{}; + tl::fetcher tlf{*resp}; + if (!composite_wait_resp.fetch(tlf)) [[unlikely]] { + kphp::log::error("failed to parse response of waiting Composite transfer updates"); + } + + auto result{composite_wait_resp.value}; + if (std::holds_alternative(result)) { + co_return std::unexpected{details::process_error(std::get(result))}; + } + + const auto awaiters_num{std::get(result).awaiters_num.value}; + co_return std::expected{awaiters_num}; +} + } // namespace kphp::web diff --git a/runtime-light/tl/tl-functions.h b/runtime-light/tl/tl-functions.h index 5869a67333..9340eea470 100644 --- a/runtime-light/tl/tl-functions.h +++ b/runtime-light/tl/tl-functions.h @@ -593,4 +593,21 @@ class CompositeWebTransferClose final { } }; +class CompositeWebTransferWaitUpdates final { + static constexpr uint32_t COMPOSITE_WEB_TRANSFER_WAIT_UPDATES_MAGIC = 0x1212'1997; + +public: + tl::u64 descriptor; + tl::u64 timeout; + + void store(tl::storer& tls) const noexcept { + tl::magic{.value = COMPOSITE_WEB_TRANSFER_WAIT_UPDATES_MAGIC}.store(tls); + descriptor.store(tls); + timeout.store(tls); + } + + constexpr size_t footprint() const noexcept { + return tl::magic{.value = COMPOSITE_WEB_TRANSFER_WAIT_UPDATES_MAGIC}.footprint() + descriptor.footprint() + timeout.footprint(); + } +}; } // namespace tl diff --git a/runtime-light/tl/tl-types.h b/runtime-light/tl/tl-types.h index 74c8b4dff6..0fbf1ba3f2 100644 --- a/runtime-light/tl/tl-types.h +++ b/runtime-light/tl/tl-types.h @@ -1409,4 +1409,21 @@ class CompositeWebTransferCloseResultOk final { } }; +class CompositeWebTransferWaitUpdatesResultOk final { + static constexpr uint32_t COMPOSITE_WEB_TRANSFER_WAIT_UPDATES_RESULT_OK_MAGIC = 0x2007'1997; + +public: + tl::u64 awaiters_num; + + bool fetch(tl::fetcher& tlf) noexcept { + tl::magic magic{}; + bool ok{magic.fetch(tlf) && magic.expect(COMPOSITE_WEB_TRANSFER_WAIT_UPDATES_RESULT_OK_MAGIC) && awaiters_num.fetch(tlf)}; + return ok; + } + + constexpr size_t footprint() const noexcept { + return tl::magic{.value = COMPOSITE_WEB_TRANSFER_WAIT_UPDATES_RESULT_OK_MAGIC}.footprint() && awaiters_num.footprint(); + } +}; + } // namespace tl diff --git a/tests/phpt/curl/9_curl_multi_select.php b/tests/phpt/curl/9_curl_multi_select.php index 3c120dc357..dcf9193a90 100644 --- a/tests/phpt/curl/9_curl_multi_select.php +++ b/tests/phpt/curl/9_curl_multi_select.php @@ -1,4 +1,4 @@ -@ok k2_skip +@ok Date: Tue, 2 Dec 2025 00:03:19 +0300 Subject: [PATCH 12/15] Add curl_multi_info_read support Signed-off-by: Petr Shumilov --- .../kphp-light/stdlib/curl-functions.txt | 3 +- .../stdlib/curl/curl-easy-functions.h | 14 +++- .../stdlib/curl/curl-multi-functions.h | 79 ++++++++++++++++++- runtime-light/stdlib/web-transfer-lib/defs.h | 35 ++++++-- .../web-transfer-lib/details/web-error.h | 1 + .../web-transfer-lib/details/web-property.h | 9 ++- .../web-transfer-lib/web-composite-transfer.h | 9 ++- tests/phpt/curl/10_curl_multi_info_read.php | 2 +- 8 files changed, 127 insertions(+), 25 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/curl-functions.txt b/builtin-functions/kphp-light/stdlib/curl-functions.txt index 9cda95f2e6..6b12a644d0 100644 --- a/builtin-functions/kphp-light/stdlib/curl-functions.txt +++ b/builtin-functions/kphp-light/stdlib/curl-functions.txt @@ -293,6 +293,5 @@ function curl_multi_strerror ($errornum ::: int) ::: string|null; /** @kphp-extern-func-info interruptible */ function curl_multi_select ($multi_handle ::: int, $timeout ::: float = 1.0) ::: int|false; -// ===== UNSUPPORTED ===== -/** @kphp-extern-func-info stub */ +/** @kphp-extern-func-info interruptible */ function curl_multi_info_read ($multi_handle ::: int, &$msgs_in_queue ::: int = TODO) ::: int[]|false; diff --git a/runtime-light/stdlib/curl/curl-easy-functions.h b/runtime-light/stdlib/curl/curl-easy-functions.h index bd6cfe9694..edd02554ee 100644 --- a/runtime-light/stdlib/curl/curl-easy-functions.h +++ b/runtime-light/stdlib/curl/curl-easy-functions.h @@ -506,8 +506,10 @@ inline auto f$curl_getinfo(kphp::web::curl::easy_type easy_id, int64_t option = auto& easy_ctx{curl_state.easy_ctx.get_or_init(easy_id)}; switch (static_cast(option)) { case kphp::web::curl::CURLINFO::NONE: { - auto res{co_await kphp::forks::id_managed(kphp::web::get_transfer_properties(kphp::web::simple_transfer{easy_id}, std::nullopt))}; + auto res{co_await kphp::forks::id_managed( + kphp::web::get_transfer_properties(kphp::web::simple_transfer{easy_id}, std::nullopt, kphp::web::get_properties_policy::load))}; if (!res.has_value()) [[unlikely]] { + easy_ctx.set_errno(res.error().code, res.error().description); kphp::web::curl::print_error("could not get all info options of easy handle", std::move(res.error())); co_return false; } @@ -525,7 +527,8 @@ inline auto f$curl_getinfo(kphp::web::curl::easy_type easy_id, int64_t option = if (!easy_ctx.has_been_executed) { const auto url_opt_id{static_cast(kphp::web::curl::CURLOPT::URL)}; - const auto url{co_await kphp::forks::id_managed(kphp::web::get_transfer_properties(kphp::web::simple_transfer{easy_id}, url_opt_id))}; + const auto url{co_await kphp::forks::id_managed( + kphp::web::get_transfer_properties(kphp::web::simple_transfer{easy_id}, url_opt_id, kphp::web::get_properties_policy::cached))}; if (url.has_value()) { const auto& v{(*url).find(url_opt_id)}; kphp::log::assertion(v != (*url).end()); @@ -569,7 +572,8 @@ inline auto f$curl_getinfo(kphp::web::curl::easy_type easy_id, int64_t option = case kphp::web::curl::CURLINFO::EFFECTIVE_URL: if (!easy_ctx.has_been_executed) { const auto url_opt_id{static_cast(kphp::web::curl::CURLOPT::URL)}; - const auto url{co_await kphp::forks::id_managed(kphp::web::get_transfer_properties(kphp::web::simple_transfer{easy_id}, url_opt_id))}; + const auto url{co_await kphp::forks::id_managed( + kphp::web::get_transfer_properties(kphp::web::simple_transfer{easy_id}, url_opt_id, kphp::web::get_properties_policy::cached))}; if (url.has_value()) { co_return (*url).find(url_opt_id)->second.to_mixed(); } @@ -601,8 +605,10 @@ inline auto f$curl_getinfo(kphp::web::curl::easy_type easy_id, int64_t option = case kphp::web::curl::CURLINFO::CONDITION_UNMET: case kphp::web::curl::CURLINFO::NUM_CONNECTS: case kphp::web::curl::CURLINFO::HEADER_OUT: { - auto res{co_await kphp::forks::id_managed(kphp::web::get_transfer_properties(kphp::web::simple_transfer{easy_id}, option))}; + auto res{co_await kphp::forks::id_managed( + kphp::web::get_transfer_properties(kphp::web::simple_transfer{easy_id}, option, kphp::web::get_properties_policy::load))}; if (!res.has_value()) [[unlikely]] { + easy_ctx.set_errno(res.error().code, res.error().description); kphp::web::curl::print_error("could not get a specific info of easy handle", std::move(res.error())); co_return 0; } diff --git a/runtime-light/stdlib/curl/curl-multi-functions.h b/runtime-light/stdlib/curl/curl-multi-functions.h index 32a8ed1763..b3926d3e0a 100644 --- a/runtime-light/stdlib/curl/curl-multi-functions.h +++ b/runtime-light/stdlib/curl/curl-multi-functions.h @@ -214,7 +214,80 @@ inline auto f$curl_multi_select(kphp::web::curl::multi_type multi_id, double tim co_return *res; } -inline Optional> f$curl_multi_info_read([[maybe_unused]] int64_t /*unused*/, - [[maybe_unused]] Optional>> /*unused*/ = {}) { - kphp::log::error("call to unsupported function : curl_multi_info_read"); +inline auto f$curl_multi_info_read(kphp::web::curl::multi_type multi_id, std::optional> msgs_in_queue = {}) noexcept -> kphp::coro::task>>{ + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.multi_ctx.has(multi_id)) { + co_return false; + } + auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; + + constexpr auto CURL_MULTI_INFO_READ_OPTION = 0; + + auto props{co_await kphp::forks::id_managed( + kphp::web::get_transfer_properties(kphp::web::composite_transfer{multi_id}, CURL_MULTI_INFO_READ_OPTION, kphp::web::get_properties_policy::load))}; + if (!props.has_value()) [[unlikely]] { + multi_ctx.set_errno(props.error().code, props.error().description); + kphp::web::curl::print_error("could not get info message of multi handle", std::move(props.error())); + if (msgs_in_queue.has_value()) { + (*msgs_in_queue).get() = 0; + } + co_return false; + } + + auto it_optional_info{(*props).find(CURL_MULTI_INFO_READ_OPTION)}; + if (it_optional_info == (*props).end()) [[unlikely]] { + kphp::web::curl::print_error("incorrect format of multi info message", std::move(props.error())); + if (msgs_in_queue.has_value()) { + (*msgs_in_queue).get() = 0; + } + co_return false; + } + + const auto& optional_info{it_optional_info->second.to>()}; + if (!optional_info.has_value()) [[unlikely]] { + // Special case: array of bool should be interpreted as empty array, otherwise raise a warning + if (it_optional_info->second.to>().has_value() == false) { // NOLINT + kphp::log::warning("incorrect format of multi info message: array of options have to be obtained"); + } + if (msgs_in_queue.has_value()) { + (*msgs_in_queue).get() = 0; + } + co_return false; + } + + // Message is empty + const auto& info{*optional_info}; + if (info.size().size == 0) { + if (msgs_in_queue.has_value()) { + (*msgs_in_queue).get() = 0; + } + co_return false; + } + + constexpr auto MSGS_IN_QUEUE = 0; + constexpr auto MSG_IDX = 1; + constexpr auto RESULT_IDX = 2; + constexpr auto HANDLE_IDX = 3; + + array result{array_size{3, false}}; + if (auto ok{info.has_key(MSGS_IN_QUEUE)}; ok && msgs_in_queue.has_value()) { + (*msgs_in_queue).get() = info.get_value(MSGS_IN_QUEUE); + } + + if (auto ok{info.has_key(MSG_IDX)}; ok) { + result.set_value(string{"msg"},info.get_value(MSG_IDX)); + } + + if (auto ok{info.has_key(RESULT_IDX)}; ok) { + result.set_value(string{"result"},info.get_value(RESULT_IDX)); + } + + if (auto ok{info.has_key(HANDLE_IDX)}; ok) { + auto easy_id{info.get_value(HANDLE_IDX)}; + if (curl_state.easy_ctx.has(easy_id)) { + result.set_value(string{"handle"},info.get_value(HANDLE_IDX)); + } + } + + co_return result; } diff --git a/runtime-light/stdlib/web-transfer-lib/defs.h b/runtime-light/stdlib/web-transfer-lib/defs.h index 8b6b758b82..fe6425f3fe 100644 --- a/runtime-light/stdlib/web-transfer-lib/defs.h +++ b/runtime-light/stdlib/web-transfer-lib/defs.h @@ -4,9 +4,12 @@ #pragma once +#include #include #include #include +#include +#include #include "runtime-light/stdlib/diagnostics/logs.h" #include "runtime-light/tl/tl-types.h" @@ -36,21 +39,21 @@ class property_value { private: explicit property_value(bool v) noexcept - : value(v){}; + : value(v) {}; explicit property_value(int64_t v) noexcept - : value(v){}; + : value(v) {}; explicit property_value(double v) noexcept - : value(v){}; + : value(v) {}; explicit property_value(string v) noexcept - : value(v){}; + : value(v) {}; explicit property_value(array v) noexcept - : value(v){}; + : value(v) {}; explicit property_value(array v) noexcept - : value(v){}; + : value(v) {}; explicit property_value(array v) noexcept - : value(v){}; + : value(v) {}; explicit property_value(array v) noexcept - : value(v){}; + : value(v) {}; std::variant, array, array, array> value; @@ -91,6 +94,11 @@ class property_value { static inline auto deserialize(const tl::webPropertyValue& tl_prop_value) noexcept -> property_value; inline auto to_mixed() const noexcept -> mixed; + + template + requires std::same_as || std::same_as || std::same_as || std::same_as || std::same_as> || + std::same_as> || std::same_as> || std::same_as> + inline auto to() const noexcept -> std::optional; }; using simple_transfers = kphp::stl::unordered_set; @@ -132,6 +140,7 @@ enum backend_internal_error : int64_t { post_field_value_not_string = -513, header_line_not_string = -514, unsupported_property = -515, + cannot_set_transfer_token = -516, }; // ---------------------------------- @@ -300,4 +309,14 @@ inline auto property_value::to_mixed() const noexcept -> mixed { return std::visit([](const auto& v) noexcept -> mixed { return mixed{v}; }, this->value); } +template +requires std::same_as || std::same_as || std::same_as || std::same_as || std::same_as> || + std::same_as> || std::same_as> || std::same_as> +inline auto property_value::to() const noexcept -> std::optional { + if (!std::holds_alternative(this->value)) { + return {}; + } + return std::get(this->value); +} + } // namespace kphp::web diff --git a/runtime-light/stdlib/web-transfer-lib/details/web-error.h b/runtime-light/stdlib/web-transfer-lib/details/web-error.h index 6fb404446a..77093dfe03 100644 --- a/runtime-light/stdlib/web-transfer-lib/details/web-error.h +++ b/runtime-light/stdlib/web-transfer-lib/details/web-error.h @@ -20,6 +20,7 @@ inline auto process_error(tl::WebError e) noexcept -> error { case backend_internal_error::post_field_value_not_string: case backend_internal_error::header_line_not_string: case backend_internal_error::unsupported_property: + case backend_internal_error::cannot_set_transfer_token: return error{.code = WEB_INTERNAL_ERROR_CODE, .description = string(e.description.value.data(), e.description.value.size())}; default: // BackendError diff --git a/runtime-light/stdlib/web-transfer-lib/details/web-property.h b/runtime-light/stdlib/web-transfer-lib/details/web-property.h index 087dff3b89..c9cedb0d0e 100644 --- a/runtime-light/stdlib/web-transfer-lib/details/web-property.h +++ b/runtime-light/stdlib/web-transfer-lib/details/web-property.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include @@ -23,6 +24,8 @@ namespace kphp::web { +enum class get_properties_policy : uint8_t { cached, load }; + inline auto set_transfer_property(std::variant transfer, property_id prop_id, property_value prop_value) -> std::expected { // Simple @@ -55,10 +58,10 @@ inline auto set_transfer_property(std::variant{}; } -inline auto get_transfer_properties(std::variant transfer, - std::optional prop_id) -> kphp::coro::task> { +inline auto get_transfer_properties(std::variant transfer, std::optional prop_id, + get_properties_policy policy) -> kphp::coro::task> { // Try to get a cached prop - if (prop_id.has_value()) { + if (prop_id.has_value() && policy == get_properties_policy::cached) { const auto p{prop_id.value()}; const auto& web_state{WebInstanceState::get()}; if (std::holds_alternative(transfer)) { diff --git a/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h b/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h index 7e9a474b75..0838671659 100644 --- a/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h +++ b/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h @@ -286,14 +286,15 @@ inline auto composite_transfer_close(composite_transfer ct) noexcept -> kphp::co kphp::log::error("session with Web components has been closed"); } - // Enumerate over all included simple transfers and close them + // Enumerate over all included simple transfers, close and remove them auto& simple_transfers{web_state.composite_transfer2simple_transfers[ct.descriptor]}; - for (const auto st : simple_transfers) { - if (auto remove_res{co_await kphp::web::composite_transfer_remove(ct, kphp::web::simple_transfer{st})}; !remove_res.has_value()) { + auto it_simple_transfer {simple_transfers.begin()}; + while (simple_transfers.size()) { + if (auto remove_res{co_await kphp::web::composite_transfer_remove(ct, kphp::web::simple_transfer{*it_simple_transfer})}; !remove_res.has_value()) { co_return std::move(remove_res); }; + it_simple_transfer = simple_transfers.begin(); } - simple_transfers.clear(); tl::CompositeWebTransferClose tl_close{tl::u64{ct.descriptor}}; tl::storer tls{tl_close.footprint()}; diff --git a/tests/phpt/curl/10_curl_multi_info_read.php b/tests/phpt/curl/10_curl_multi_info_read.php index 2c5177768d..d4830369e0 100644 --- a/tests/phpt/curl/10_curl_multi_info_read.php +++ b/tests/phpt/curl/10_curl_multi_info_read.php @@ -1,4 +1,4 @@ -@ok k2_skip +@ok Date: Tue, 2 Dec 2025 16:39:07 +0300 Subject: [PATCH 13/15] Fix code style Signed-off-by: Petr Shumilov --- .../kphp-light/stdlib/curl-functions.txt | 17 ++--------------- .../stdlib/curl/curl-multi-functions.h | 15 ++++++--------- runtime-light/stdlib/web-transfer-lib/defs.h | 16 ++++++++-------- .../web-transfer-lib/details/web-response.h | 6 +++--- .../web-transfer-lib/web-composite-transfer.h | 7 +++---- runtime-light/tl/tl-types.h | 14 +++++++------- 6 files changed, 29 insertions(+), 46 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/curl-functions.txt b/builtin-functions/kphp-light/stdlib/curl-functions.txt index 6b12a644d0..ccd31fd488 100644 --- a/builtin-functions/kphp-light/stdlib/curl-functions.txt +++ b/builtin-functions/kphp-light/stdlib/curl-functions.txt @@ -244,54 +244,41 @@ define('CURLPIPE_NOTHING', 0); define('CURLPIPE_HTTP1', 1); define('CURLPIPE_MULTIPLEX', 2); -// ===== SUPPORTED ===== +// ===== EASY API ===== /** @kphp-extern-func-info interruptible */ function curl_init ($url ::: string = "") ::: int; /** @kphp-extern-func-info interruptible */ function curl_reset ($curl_handle ::: int) ::: void; - function curl_setopt ($curl_handle ::: int, $option ::: int, $value ::: mixed) ::: bool; function curl_setopt_array ($curl_handle ::: int, $options ::: array) ::: bool; - /** @kphp-extern-func-info interruptible */ function curl_exec ($curl_handle ::: int) ::: mixed; - /** @kphp-extern-func-info interruptible */ function curl_close ($curl_handle ::: int) ::: void; - /** @kphp-extern-func-info interruptible */ function curl_exec_concurrently($curl_handle ::: int, $timeout ::: float = 1.0): string|false; - function curl_error ($curl_handle ::: int) ::: string; function curl_errno ($curl_handle ::: int) ::: int; - /** @kphp-extern-func-info interruptible */ function curl_getinfo ($curl_handle ::: int, $option ::: int = 0) ::: mixed; +// ===== MULTI API ===== /** @kphp-extern-func-info interruptible */ function curl_multi_init () ::: int; - /** @kphp-extern-func-info interruptible */ function curl_multi_add_handle ($multi_handle ::: int, $curl_handle ::: int) ::: int|false; /** @kphp-extern-func-info interruptible */ function curl_multi_remove_handle ($multi_handle ::: int, $curl_handle ::: int) ::: int|false; - function curl_multi_setopt ($multi_handle ::: int, $option ::: int, $value ::: int) ::: bool; - /** @kphp-extern-func-info interruptible */ function curl_multi_exec ($multi_handle ::: int, &$still_running ::: int) ::: int|false; - /** @kphp-extern-func-info interruptible */ function curl_multi_getcontent ($curl_handle ::: int ) ::: string|false|null; - /** @kphp-extern-func-info interruptible */ function curl_multi_close ($multi_handle ::: int) ::: void; - function curl_multi_errno ($multi_handle ::: int) ::: int|false; function curl_multi_strerror ($errornum ::: int) ::: string|null; - /** @kphp-extern-func-info interruptible */ function curl_multi_select ($multi_handle ::: int, $timeout ::: float = 1.0) ::: int|false; - /** @kphp-extern-func-info interruptible */ function curl_multi_info_read ($multi_handle ::: int, &$msgs_in_queue ::: int = TODO) ::: int[]|false; diff --git a/runtime-light/stdlib/curl/curl-multi-functions.h b/runtime-light/stdlib/curl/curl-multi-functions.h index b3926d3e0a..8b27991cf1 100644 --- a/runtime-light/stdlib/curl/curl-multi-functions.h +++ b/runtime-light/stdlib/curl/curl-multi-functions.h @@ -214,7 +214,8 @@ inline auto f$curl_multi_select(kphp::web::curl::multi_type multi_id, double tim co_return *res; } -inline auto f$curl_multi_info_read(kphp::web::curl::multi_type multi_id, std::optional> msgs_in_queue = {}) noexcept -> kphp::coro::task>>{ +inline auto f$curl_multi_info_read(kphp::web::curl::multi_type multi_id, + std::optional> msgs_in_queue = {}) noexcept -> kphp::coro::task>> { auto& curl_state{CurlInstanceState::get()}; if (!curl_state.multi_ctx.has(multi_id)) { co_return false; @@ -224,7 +225,7 @@ inline auto f$curl_multi_info_read(kphp::web::curl::multi_type multi_id, std::op constexpr auto CURL_MULTI_INFO_READ_OPTION = 0; auto props{co_await kphp::forks::id_managed( - kphp::web::get_transfer_properties(kphp::web::composite_transfer{multi_id}, CURL_MULTI_INFO_READ_OPTION, kphp::web::get_properties_policy::load))}; + kphp::web::get_transfer_properties(kphp::web::composite_transfer{multi_id}, CURL_MULTI_INFO_READ_OPTION, kphp::web::get_properties_policy::load))}; if (!props.has_value()) [[unlikely]] { multi_ctx.set_errno(props.error().code, props.error().description); kphp::web::curl::print_error("could not get info message of multi handle", std::move(props.error())); @@ -245,10 +246,6 @@ inline auto f$curl_multi_info_read(kphp::web::curl::multi_type multi_id, std::op const auto& optional_info{it_optional_info->second.to>()}; if (!optional_info.has_value()) [[unlikely]] { - // Special case: array of bool should be interpreted as empty array, otherwise raise a warning - if (it_optional_info->second.to>().has_value() == false) { // NOLINT - kphp::log::warning("incorrect format of multi info message: array of options have to be obtained"); - } if (msgs_in_queue.has_value()) { (*msgs_in_queue).get() = 0; } @@ -275,17 +272,17 @@ inline auto f$curl_multi_info_read(kphp::web::curl::multi_type multi_id, std::op } if (auto ok{info.has_key(MSG_IDX)}; ok) { - result.set_value(string{"msg"},info.get_value(MSG_IDX)); + result.set_value(string{"msg"}, info.get_value(MSG_IDX)); } if (auto ok{info.has_key(RESULT_IDX)}; ok) { - result.set_value(string{"result"},info.get_value(RESULT_IDX)); + result.set_value(string{"result"}, info.get_value(RESULT_IDX)); } if (auto ok{info.has_key(HANDLE_IDX)}; ok) { auto easy_id{info.get_value(HANDLE_IDX)}; if (curl_state.easy_ctx.has(easy_id)) { - result.set_value(string{"handle"},info.get_value(HANDLE_IDX)); + result.set_value(string{"handle"}, info.get_value(HANDLE_IDX)); } } diff --git a/runtime-light/stdlib/web-transfer-lib/defs.h b/runtime-light/stdlib/web-transfer-lib/defs.h index fe6425f3fe..11af1b5481 100644 --- a/runtime-light/stdlib/web-transfer-lib/defs.h +++ b/runtime-light/stdlib/web-transfer-lib/defs.h @@ -39,21 +39,21 @@ class property_value { private: explicit property_value(bool v) noexcept - : value(v) {}; + : value(v){}; explicit property_value(int64_t v) noexcept - : value(v) {}; + : value(v){}; explicit property_value(double v) noexcept - : value(v) {}; + : value(v){}; explicit property_value(string v) noexcept - : value(v) {}; + : value(v){}; explicit property_value(array v) noexcept - : value(v) {}; + : value(v){}; explicit property_value(array v) noexcept - : value(v) {}; + : value(v){}; explicit property_value(array v) noexcept - : value(v) {}; + : value(v){}; explicit property_value(array v) noexcept - : value(v) {}; + : value(v){}; std::variant, array, array, array> value; diff --git a/runtime-light/stdlib/web-transfer-lib/details/web-response.h b/runtime-light/stdlib/web-transfer-lib/details/web-response.h index 645cdc1f4c..f38dccc69e 100644 --- a/runtime-light/stdlib/web-transfer-lib/details/web-response.h +++ b/runtime-light/stdlib/web-transfer-lib/details/web-response.h @@ -62,12 +62,12 @@ inline auto process_simple_response(std::span request) noexcept switch (frame_num) { case 0: { frame_num += 1; - tl::Either simple_web_transfer_perform_resp{}; + tl::Either simple_web_transfer_resp{}; tl::fetcher tlf{ok_or_error_buffer}; - if (!simple_web_transfer_perform_resp.fetch(tlf)) [[unlikely]] { + if (!simple_web_transfer_resp.fetch(tlf)) [[unlikely]] { kphp::log::error("failed to parse response of Simple descriptor"); } - if (auto r{simple_web_transfer_perform_resp.value}; std::holds_alternative(r)) { + if (auto r{simple_web_transfer_resp.value}; std::holds_alternative(r)) { err.emplace(details::process_error(std::get(r))); return kphp::component::inter_component_session::client::response_readiness::ready; } diff --git a/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h b/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h index 0838671659..3258883914 100644 --- a/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h +++ b/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h @@ -288,12 +288,11 @@ inline auto composite_transfer_close(composite_transfer ct) noexcept -> kphp::co // Enumerate over all included simple transfers, close and remove them auto& simple_transfers{web_state.composite_transfer2simple_transfers[ct.descriptor]}; - auto it_simple_transfer {simple_transfers.begin()}; + auto it_simple_transfer{simple_transfers.begin()}; while (simple_transfers.size()) { if (auto remove_res{co_await kphp::web::composite_transfer_remove(ct, kphp::web::simple_transfer{*it_simple_transfer})}; !remove_res.has_value()) { co_return std::move(remove_res); }; - it_simple_transfer = simple_transfers.begin(); } tl::CompositeWebTransferClose tl_close{tl::u64{ct.descriptor}}; @@ -371,8 +370,8 @@ inline auto composite_transfer_wait_updates(composite_transfer ct, co_return std::unexpected{details::process_error(std::get(result))}; } - const auto awaiters_num{std::get(result).awaiters_num.value}; - co_return std::expected{awaiters_num}; + const auto updated_descriptors_num{std::get(result).updated_descriptors_num.value}; + co_return std::expected{updated_descriptors_num}; } } // namespace kphp::web diff --git a/runtime-light/tl/tl-types.h b/runtime-light/tl/tl-types.h index 0fbf1ba3f2..cfe4dee1ee 100644 --- a/runtime-light/tl/tl-types.h +++ b/runtime-light/tl/tl-types.h @@ -1271,18 +1271,18 @@ class SimpleWebTransferOpenResultOk final { } }; -class SimpleWebTransferPerformResultOk final { - static constexpr uint32_t SIMPLE_WEB_TRANSFER_PERFORM_RESULT_OK_MAGIC = 0x77A8'98FF; +class SimpleWebTransferResponseResultOk final { + static constexpr uint32_t SIMPLE_WEB_TRANSFER_RESPONSE_RESULT_OK_MAGIC = 0x77A8'98FF; public: bool fetch(tl::fetcher& tlf) noexcept { tl::magic magic{}; - bool ok{magic.fetch(tlf) && magic.expect(SIMPLE_WEB_TRANSFER_PERFORM_RESULT_OK_MAGIC)}; + bool ok{magic.fetch(tlf) && magic.expect(SIMPLE_WEB_TRANSFER_RESPONSE_RESULT_OK_MAGIC)}; return ok; } constexpr size_t footprint() const noexcept { - return tl::magic{.value = SIMPLE_WEB_TRANSFER_PERFORM_RESULT_OK_MAGIC}.footprint(); + return tl::magic{.value = SIMPLE_WEB_TRANSFER_RESPONSE_RESULT_OK_MAGIC}.footprint(); } }; @@ -1413,16 +1413,16 @@ class CompositeWebTransferWaitUpdatesResultOk final { static constexpr uint32_t COMPOSITE_WEB_TRANSFER_WAIT_UPDATES_RESULT_OK_MAGIC = 0x2007'1997; public: - tl::u64 awaiters_num; + tl::u64 updated_descriptors_num; bool fetch(tl::fetcher& tlf) noexcept { tl::magic magic{}; - bool ok{magic.fetch(tlf) && magic.expect(COMPOSITE_WEB_TRANSFER_WAIT_UPDATES_RESULT_OK_MAGIC) && awaiters_num.fetch(tlf)}; + bool ok{magic.fetch(tlf) && magic.expect(COMPOSITE_WEB_TRANSFER_WAIT_UPDATES_RESULT_OK_MAGIC) && updated_descriptors_num.fetch(tlf)}; return ok; } constexpr size_t footprint() const noexcept { - return tl::magic{.value = COMPOSITE_WEB_TRANSFER_WAIT_UPDATES_RESULT_OK_MAGIC}.footprint() && awaiters_num.footprint(); + return tl::magic{.value = COMPOSITE_WEB_TRANSFER_WAIT_UPDATES_RESULT_OK_MAGIC}.footprint() && updated_descriptors_num.footprint(); } }; From a9c3011d15ade375a7f4648b7329ac1333efc6fc Mon Sep 17 00:00:00 2001 From: Petr Shumilov Date: Wed, 3 Dec 2025 16:28:59 +0300 Subject: [PATCH 14/15] Add curl multi functional test Signed-off-by: Petr Shumilov --- tests/python/tests/curl/curl_test_case.py | 22 ++++++++ tests/python/tests/curl/php/index.php | 62 ++++++++++++++++++++++ tests/python/tests/curl/test_curl_multi.py | 13 +++++ 3 files changed, 97 insertions(+) create mode 100644 tests/python/tests/curl/test_curl_multi.py diff --git a/tests/python/tests/curl/curl_test_case.py b/tests/python/tests/curl/curl_test_case.py index 09954a9521..432ebd76ae 100644 --- a/tests/python/tests/curl/curl_test_case.py +++ b/tests/python/tests/curl/curl_test_case.py @@ -38,3 +38,25 @@ def _prepare_result(self, uri, method, extra=None): if extra: res.update(extra) return res + +class CurlMultiTestCase(WebServerAutoTestCase): + @classmethod + def extra_class_setup(cls): + if cls.should_use_k2(): + cls.web_server.ignore_log_errors() + else: + cls.web_server.update_options({ + "--workers-num": 3, + }) + + def _curl_multi_request(self, uri1, uri2): + url1 = "localhost:{}{}".format(self.web_server.http_port, uri1) if uri1.startswith('/') else uri1 + url2 = "localhost:{}{}".format(self.web_server.http_port, uri2) if uri2.startswith('/') else uri2 + resp = self.web_server.http_post( + uri=self.test_case_uri, + json={ + "url1": url1, + "url2": url2, + }) + self.assertEqual(resp.status_code, 200) + return resp.json() diff --git a/tests/python/tests/curl/php/index.php b/tests/python/tests/curl/php/index.php index 4217dfd281..e6df37cd30 100644 --- a/tests/python/tests/curl/php/index.php +++ b/tests/python/tests/curl/php/index.php @@ -9,6 +9,12 @@ function simple_function() { } function main() { + if (strpos($_SERVER["PHP_SELF"], "/echo/multi/") === 0) { + usleep(500000); + echo substr($_SERVER["REQUEST_URI"], strlen("/echo/multi/")); + return; + } + if (strpos($_SERVER["PHP_SELF"], "/echo") === 0) { usleep(500000); $resp = array_filter_by_key($_SERVER, function ($key): bool { @@ -45,6 +51,10 @@ function main() { test_curl_reset_handle(); return; } + case "/test_curl_multi": { + test_curl_multi(); + return; + } } critical_error("unknown test"); @@ -149,4 +159,56 @@ function test_curl_reset_handle() { echo json_encode($resp); } +function test_curl_multi() { + $params = json_decode(file_get_contents('php://input')); + + $url1 = (string)$params["url1"]; + $url2 = (string)$params["url2"]; + + + $ch1 = curl_init($url1); + $ch2 = curl_init($url2); + + curl_setopt($ch1, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true); + + $mh = curl_multi_init(); + + curl_multi_add_handle($mh, $ch1); + curl_multi_add_handle($mh, $ch2); + + $running = null; + $status = 0; + // Execute the multi handle + do { + $msgs_in_queue = 0; + curl_multi_info_read($mh, $msgs_in_queue); + + $status = curl_multi_exec($mh, $running); + // Wait for activity on any curl-connection + if ($running) { + curl_multi_select($mh); + } + } while ($running && $status == CURLM_OK); + + if ($status != CURLM_OK) { + $resp = ["mutli_ret_code" => $status, "easy_result1" => "", "easy_result2" => ""]; + echo json_encode($resp); + return; + } + + $response1 = (string)curl_multi_getcontent($ch1); + $response2 = (string)curl_multi_getcontent($ch2); + + curl_multi_remove_handle($mh, $ch1); + curl_multi_remove_handle($mh, $ch2); + + curl_multi_close($mh); + curl_close($ch1); + curl_close($ch2); + + $resp = ["mutli_ret_code" => $status, "easy_result1" => $response1, "easy_result2" => $response2]; + echo json_encode($resp); +} + main(); diff --git a/tests/python/tests/curl/test_curl_multi.py b/tests/python/tests/curl/test_curl_multi.py new file mode 100644 index 0000000000..0c6c3f2742 --- /dev/null +++ b/tests/python/tests/curl/test_curl_multi.py @@ -0,0 +1,13 @@ +import pytest +from python.tests.curl.curl_test_case import CurlMultiTestCase + + +class TestCurl(CurlMultiTestCase): + test_case_uri="/test_curl_multi" + + def test_curl_multi_transfers(self): + self.assertEqual(self._curl_multi_request("/echo/multi/payload_1", "/echo/multi/payload_2"), { + "mutli_ret_code": 0, + "easy_result1": "payload_1", + "easy_result2": "payload_2" + }) From 6766479556d9bac87dd25625cc8a0a44da86b113 Mon Sep 17 00:00:00 2001 From: Petr Shumilov Date: Fri, 5 Dec 2025 19:17:36 +0300 Subject: [PATCH 15/15] Fix code style Signed-off-by: Petr Shumilov --- runtime-light/stdlib/curl/curl-context.h | 4 +-- .../stdlib/curl/curl-multi-functions.h | 28 +++++++++---------- runtime-light/stdlib/curl/defs.h | 2 +- .../web-transfer-lib/details/web-response.h | 1 - .../web-transfer-lib/web-composite-transfer.h | 1 - 5 files changed, 16 insertions(+), 20 deletions(-) diff --git a/runtime-light/stdlib/curl/curl-context.h b/runtime-light/stdlib/curl/curl-context.h index 5f3afd9aaa..513df8ce57 100644 --- a/runtime-light/stdlib/curl/curl-context.h +++ b/runtime-light/stdlib/curl/curl-context.h @@ -45,11 +45,11 @@ struct curl_context : vk::movable_only { set_errno(static_cast(code), std::move(description)); } - inline auto set_errno(kphp::web::curl::CURLMcode code, std::string_view description) noexcept { + inline auto set_errno(kphp::web::curl::CURLME code, std::string_view description) noexcept { set_errno(static_cast(code), description); } - inline auto set_errno(kphp::web::curl::CURLMcode code, std::optional description = std::nullopt) noexcept { + inline auto set_errno(kphp::web::curl::CURLME code, std::optional description = std::nullopt) noexcept { set_errno(static_cast(code), std::move(description)); } diff --git a/runtime-light/stdlib/curl/curl-multi-functions.h b/runtime-light/stdlib/curl/curl-multi-functions.h index 8b27991cf1..d43d1dbb90 100644 --- a/runtime-light/stdlib/curl/curl-multi-functions.h +++ b/runtime-light/stdlib/curl/curl-multi-functions.h @@ -15,7 +15,6 @@ #include "runtime-light/stdlib/curl/curl-state.h" #include "runtime-light/stdlib/curl/defs.h" #include "runtime-light/stdlib/curl/details/diagnostics.h" -#include "runtime-light/stdlib/diagnostics/logs.h" #include "runtime-light/stdlib/fork/fork-functions.h" #include "runtime-light/stdlib/web-transfer-lib/defs.h" #include "runtime-light/stdlib/web-transfer-lib/details/web-property.h" @@ -38,11 +37,10 @@ inline auto f$curl_multi_add_handle(kphp::web::curl::multi_type multi_id, kphp:: if (!curl_state.multi_ctx.has(multi_id)) [[unlikely]] { co_return false; } - auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; - if (!curl_state.easy_ctx.has(easy_id)) [[unlikely]] { co_return false; } + auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; auto& easy_ctx{curl_state.easy_ctx.get_or_init(easy_id)}; easy_ctx.errors_reset(); @@ -98,11 +96,11 @@ inline auto f$curl_multi_setopt(kphp::web::curl::multi_type multi_id, int64_t op kphp::web::curl::print_error("could not set an mutli option", std::move(res.error())); return false; } - multi_ctx.set_errno(kphp::web::curl::CURLMcode::OK); + multi_ctx.set_errno(kphp::web::curl::CURLME::OK); return true; } default: - multi_ctx.set_errno(kphp::web::curl::CURLMcode::UNKNOWN_OPTION, "a libcurl function was given an incorrect PROXYTYPE kind"); + multi_ctx.set_errno(kphp::web::curl::CURLME::UNKNOWN_OPTION, "a libcurl function was given an incorrect PROXYTYPE kind"); return false; } } @@ -115,11 +113,11 @@ inline auto f$curl_multi_setopt(kphp::web::curl::multi_type multi_id, int64_t op kphp::web::curl::print_error("could not set an multi option", std::move(res.error())); return false; } - multi_ctx.set_errno(kphp::web::curl::CURLMcode::OK); + multi_ctx.set_errno(kphp::web::curl::CURLME::OK); return true; } default: - multi_ctx.set_errno(kphp::web::curl::CURLMcode::UNKNOWN_OPTION); + multi_ctx.set_errno(kphp::web::curl::CURLME::UNKNOWN_OPTION); return false; } } @@ -154,7 +152,7 @@ inline auto f$curl_multi_getcontent(kphp::web::curl::easy_type easy_id) noexcept kphp::web::curl::print_error("could not get response of curl easy handle", std::move(res.error())); co_return false; } - co_return (*res).body; + co_return std::move((*res).body); } co_return Optional{}; } @@ -181,7 +179,7 @@ inline auto f$curl_multi_strerror(kphp::web::curl::multi_type multi_id) noexcept return {}; } auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; - if (multi_ctx.error_code != static_cast(kphp::web::curl::CURLMcode::OK)) [[likely]] { + if (multi_ctx.error_code != static_cast(kphp::web::curl::CURLME::OK)) [[likely]] { const auto* const desc_data{reinterpret_cast(multi_ctx.error_description.data())}; const auto desc_size{static_cast(multi_ctx.error_description.size())}; return string{desc_data, desc_size}; @@ -211,7 +209,7 @@ inline auto f$curl_multi_select(kphp::web::curl::multi_type multi_id, double tim kphp::web::curl::print_error("could not select curl multi handle", std::move(res.error())); co_return multi_ctx.error_code; } - co_return *res; + co_return std::move(*res_; } inline auto f$curl_multi_info_read(kphp::web::curl::multi_type multi_id, @@ -267,24 +265,24 @@ inline auto f$curl_multi_info_read(kphp::web::curl::multi_type multi_id, constexpr auto HANDLE_IDX = 3; array result{array_size{3, false}}; - if (auto ok{info.has_key(MSGS_IN_QUEUE)}; ok && msgs_in_queue.has_value()) { + if (info.has_key(MSGS_IN_QUEUE) && msgs_in_queue.has_value()) { (*msgs_in_queue).get() = info.get_value(MSGS_IN_QUEUE); } - if (auto ok{info.has_key(MSG_IDX)}; ok) { + if (info.has_key(MSG_IDX)) { result.set_value(string{"msg"}, info.get_value(MSG_IDX)); } - if (auto ok{info.has_key(RESULT_IDX)}; ok) { + if (info.has_key(RESULT_IDX)) { result.set_value(string{"result"}, info.get_value(RESULT_IDX)); } - if (auto ok{info.has_key(HANDLE_IDX)}; ok) { + if (info.has_key(HANDLE_IDX)) { auto easy_id{info.get_value(HANDLE_IDX)}; if (curl_state.easy_ctx.has(easy_id)) { result.set_value(string{"handle"}, info.get_value(HANDLE_IDX)); } } - co_return result; + co_return std::move(result); } diff --git a/runtime-light/stdlib/curl/defs.h b/runtime-light/stdlib/curl/defs.h index 835dc7450f..93688c9d61 100644 --- a/runtime-light/stdlib/curl/defs.h +++ b/runtime-light/stdlib/curl/defs.h @@ -249,7 +249,7 @@ enum class CURL_HTTP_VERSION : uint8_t { _2_PRIOR_KNOWLEDGE = 5, }; -enum class CURLMcode : int16_t { +enum class CURLME : int16_t { CALL_MULTI_PERFORM = -1, OK = 0, BAD_HANDLE = 1, diff --git a/runtime-light/stdlib/web-transfer-lib/details/web-response.h b/runtime-light/stdlib/web-transfer-lib/details/web-response.h index f38dccc69e..548be04cda 100644 --- a/runtime-light/stdlib/web-transfer-lib/details/web-response.h +++ b/runtime-light/stdlib/web-transfer-lib/details/web-response.h @@ -4,7 +4,6 @@ #pragma once -#include <__expected/expected.h> #include #include #include diff --git a/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h b/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h index 3258883914..0ce5539604 100644 --- a/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h +++ b/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h @@ -4,7 +4,6 @@ #pragma once -#include <__expected/expected.h> #include #include #include