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