diff --git a/builtin-functions/kphp-light/stdlib/curl-functions.txt b/builtin-functions/kphp-light/stdlib/curl-functions.txt index ee7297d544..ccd31fd488 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,61 +232,53 @@ 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 ===== +// ===== EASY API ===== /** @kphp-extern-func-info interruptible */ function curl_init ($url ::: string = "") ::: int; /** @kphp-extern-func-info interruptible */ function curl_reset ($curl_handle ::: int) ::: void; - function curl_setopt ($curl_handle ::: int, $option ::: int, $value ::: mixed) ::: bool; function curl_setopt_array ($curl_handle ::: int, $options ::: array) ::: bool; - /** @kphp-extern-func-info interruptible */ function curl_exec ($curl_handle ::: int) ::: mixed; - /** @kphp-extern-func-info interruptible */ function curl_close ($curl_handle ::: int) ::: void; - /** @kphp-extern-func-info interruptible */ function curl_exec_concurrently($curl_handle ::: int, $timeout ::: float = 1.0): string|false; - function curl_error ($curl_handle ::: int) ::: string; function curl_errno ($curl_handle ::: int) ::: int; - /** @kphp-extern-func-info interruptible */ function curl_getinfo ($curl_handle ::: int, $option ::: int = 0) ::: mixed; -// ===== UNSUPPORTED ===== - -/** @kphp-extern-func-info stub generation-required */ +// ===== MULTI API ===== +/** @kphp-extern-func-info interruptible */ function curl_multi_init () ::: int; -/** @kphp-extern-func-info stub generation-required */ +/** @kphp-extern-func-info interruptible */ 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 */ +/** @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 stub generation-required */ +/** @kphp-extern-func-info interruptible */ 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; - -/** @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 */ +/** @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; -/** @kphp-extern-func-info stub generation-required */ +function curl_multi_errno ($multi_handle ::: int) ::: int|false; function curl_multi_strerror ($errornum ::: int) ::: string|null; +/** @kphp-extern-func-info interruptible */ +function curl_multi_select ($multi_handle ::: int, $timeout ::: float = 1.0) ::: int|false; +/** @kphp-extern-func-info interruptible */ +function curl_multi_info_read ($multi_handle ::: int, &$msgs_in_queue ::: int = TODO) ::: int[]|false; diff --git a/runtime-light/stdlib/curl/curl-context.h b/runtime-light/stdlib/curl/curl-context.h index 9892a55b3c..513df8ce57 100644 --- a/runtime-light/stdlib/curl/curl-context.h +++ b/runtime-light/stdlib/curl/curl-context.h @@ -4,9 +4,11 @@ #pragma once +#include #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" @@ -14,12 +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 set_errno(int64_t code, std::string_view description) noexcept { // If Web Transfer Lib specific error @@ -46,12 +45,44 @@ struct easy_context { set_errno(static_cast(code), std::move(description)); } + inline auto set_errno(kphp::web::curl::CURLME code, std::string_view description) noexcept { + set_errno(static_cast(code), description); + } + + inline auto set_errno(kphp::web::curl::CURLME 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"); kphp::log::warning("{}", msg); set_errno(CURLE::BAD_FUNCTION_ARGUMENT, {{msg, N}}); } + + inline auto errors_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::errors_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 698f5bf580..edd02554ee 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,19 +436,26 @@ 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); 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> { - 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})))}; @@ -457,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())}; @@ -467,16 +490,26 @@ 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))}; + auto res{co_await kphp::forks::id_managed( + kphp::web::get_transfer_properties(kphp::web::simple_transfer{easy_id}, std::nullopt, kphp::web::get_properties_policy::load))}; if (!res.has_value()) [[unlikely]] { + easy_ctx.set_errno(res.error().code, res.error().description); kphp::web::curl::print_error("could not get all info options of easy handle", std::move(res.error())); co_return false; } @@ -494,7 +527,8 @@ inline auto f$curl_getinfo(kphp::web::curl::easy_type easy_id, int64_t option = if (!easy_ctx.has_been_executed) { const auto url_opt_id{static_cast(kphp::web::curl::CURLOPT::URL)}; - const auto url{co_await kphp::forks::id_managed(kphp::web::get_transfer_properties(kphp::web::simple_transfer{easy_id}, url_opt_id))}; + const auto url{co_await kphp::forks::id_managed( + kphp::web::get_transfer_properties(kphp::web::simple_transfer{easy_id}, url_opt_id, kphp::web::get_properties_policy::cached))}; if (url.has_value()) { const auto& v{(*url).find(url_opt_id)}; kphp::log::assertion(v != (*url).end()); @@ -538,7 +572,8 @@ inline auto f$curl_getinfo(kphp::web::curl::easy_type easy_id, int64_t option = case kphp::web::curl::CURLINFO::EFFECTIVE_URL: if (!easy_ctx.has_been_executed) { const auto url_opt_id{static_cast(kphp::web::curl::CURLOPT::URL)}; - const auto url{co_await kphp::forks::id_managed(kphp::web::get_transfer_properties(kphp::web::simple_transfer{easy_id}, url_opt_id))}; + const auto url{co_await kphp::forks::id_managed( + kphp::web::get_transfer_properties(kphp::web::simple_transfer{easy_id}, url_opt_id, kphp::web::get_properties_policy::cached))}; if (url.has_value()) { co_return (*url).find(url_opt_id)->second.to_mixed(); } @@ -570,8 +605,10 @@ inline auto f$curl_getinfo(kphp::web::curl::easy_type easy_id, int64_t option = case kphp::web::curl::CURLINFO::CONDITION_UNMET: case kphp::web::curl::CURLINFO::NUM_CONNECTS: case kphp::web::curl::CURLINFO::HEADER_OUT: { - auto res{co_await kphp::forks::id_managed(kphp::web::get_transfer_properties(kphp::web::simple_transfer{easy_id}, option))}; + auto res{co_await kphp::forks::id_managed( + kphp::web::get_transfer_properties(kphp::web::simple_transfer{easy_id}, option, kphp::web::get_properties_policy::load))}; if (!res.has_value()) [[unlikely]] { + easy_ctx.set_errno(res.error().code, res.error().description); kphp::web::curl::print_error("could not get a specific info of easy handle", std::move(res.error())); co_return 0; } diff --git a/runtime-light/stdlib/curl/curl-multi-functions.h b/runtime-light/stdlib/curl/curl-multi-functions.h index 3fa7714c77..d43d1dbb90 100644 --- a/runtime-light/stdlib/curl/curl-multi-functions.h +++ b/runtime-light/stdlib/curl/curl-multi-functions.h @@ -4,14 +4,285 @@ #pragma once +#include #include #include #include #include "runtime-common/core/runtime-core.h" -#include "runtime-light/stdlib/diagnostics/logs.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/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" +#include "runtime-light/stdlib/web-transfer-lib/web-simple-transfer.h" -inline Optional> f$curl_multi_info_read([[maybe_unused]] int64_t /*unused*/, - [[maybe_unused]] Optional>> /*unused*/ = {}) { - kphp::log::error("call to unsupported function : curl_multi_info_read"); +inline auto f$curl_multi_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; + } + const auto descriptor{(*open_res).descriptor}; + CurlInstanceState::get().multi_ctx.get_or_init(descriptor); + co_return descriptor; +} + +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)) [[unlikely]] { + co_return false; + } + if (!curl_state.easy_ctx.has(easy_id)) [[unlikely]] { + co_return false; + } + auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; + auto& easy_ctx{curl_state.easy_ctx.get_or_init(easy_id)}; + easy_ctx.errors_reset(); + + 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)) [[unlikely]] { + co_return false; + } + auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; + if (!curl_state.easy_ctx.has(easy_id)) [[unlikely]] { + co_return false; + } + auto 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 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::CURLME::OK); + return true; + } + default: + multi_ctx.set_errno(kphp::web::curl::CURLME::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::CURLME::OK); + return true; + } + default: + multi_ctx.set_errno(kphp::web::curl::CURLME::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_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 std::move((*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]] { + 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_strerror(kphp::web::curl::multi_type multi_id) noexcept -> Optional { + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.multi_ctx.has(multi_id)) [[unlikely]] { + return {}; + } + auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; + if (multi_ctx.error_code != static_cast(kphp::web::curl::CURLME::OK)) [[likely]] { + const auto* const desc_data{reinterpret_cast(multi_ctx.error_description.data())}; + const auto desc_size{static_cast(multi_ctx.error_description.size())}; + return string{desc_data, desc_size}; + } + 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)) [[unlikely]] { + return false; + } + auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; + return multi_ctx.error_code; +} + +inline auto f$curl_multi_select(kphp::web::curl::multi_type multi_id, double timeout = 1.0) noexcept -> kphp::coro::task> { + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.multi_ctx.has(multi_id)) { + co_return false; + } + auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; + auto res{co_await kphp::forks::id_managed(kphp::web::composite_transfer_wait_updates( + kphp::web::composite_transfer{multi_id}, std::chrono::duration_cast(std::chrono::duration{timeout})))}; + if (!res.has_value()) [[unlikely]] { + multi_ctx.set_errno(res.error().code, res.error().description); + kphp::web::curl::print_error("could not select curl multi handle", std::move(res.error())); + co_return multi_ctx.error_code; + } + co_return std::move(*res_; +} + +inline auto f$curl_multi_info_read(kphp::web::curl::multi_type multi_id, + std::optional> msgs_in_queue = {}) noexcept -> kphp::coro::task>> { + auto& curl_state{CurlInstanceState::get()}; + if (!curl_state.multi_ctx.has(multi_id)) { + co_return false; + } + auto& multi_ctx{curl_state.multi_ctx.get_or_init(multi_id)}; + + constexpr auto CURL_MULTI_INFO_READ_OPTION = 0; + + auto props{co_await kphp::forks::id_managed( + kphp::web::get_transfer_properties(kphp::web::composite_transfer{multi_id}, CURL_MULTI_INFO_READ_OPTION, kphp::web::get_properties_policy::load))}; + if (!props.has_value()) [[unlikely]] { + multi_ctx.set_errno(props.error().code, props.error().description); + kphp::web::curl::print_error("could not get info message of multi handle", std::move(props.error())); + if (msgs_in_queue.has_value()) { + (*msgs_in_queue).get() = 0; + } + co_return false; + } + + auto it_optional_info{(*props).find(CURL_MULTI_INFO_READ_OPTION)}; + if (it_optional_info == (*props).end()) [[unlikely]] { + kphp::web::curl::print_error("incorrect format of multi info message", std::move(props.error())); + if (msgs_in_queue.has_value()) { + (*msgs_in_queue).get() = 0; + } + co_return false; + } + + const auto& optional_info{it_optional_info->second.to>()}; + if (!optional_info.has_value()) [[unlikely]] { + if (msgs_in_queue.has_value()) { + (*msgs_in_queue).get() = 0; + } + co_return false; + } + + // Message is empty + const auto& info{*optional_info}; + if (info.size().size == 0) { + if (msgs_in_queue.has_value()) { + (*msgs_in_queue).get() = 0; + } + co_return false; + } + + constexpr auto MSGS_IN_QUEUE = 0; + constexpr auto MSG_IDX = 1; + constexpr auto RESULT_IDX = 2; + constexpr auto HANDLE_IDX = 3; + + array result{array_size{3, false}}; + if (info.has_key(MSGS_IN_QUEUE) && msgs_in_queue.has_value()) { + (*msgs_in_queue).get() = info.get_value(MSGS_IN_QUEUE); + } + + if (info.has_key(MSG_IDX)) { + result.set_value(string{"msg"}, info.get_value(MSG_IDX)); + } + + if (info.has_key(RESULT_IDX)) { + result.set_value(string{"result"}, info.get_value(RESULT_IDX)); + } + + if (info.has_key(HANDLE_IDX)) { + auto easy_id{info.get_value(HANDLE_IDX)}; + if (curl_state.easy_ctx.has(easy_id)) { + result.set_value(string{"handle"}, info.get_value(HANDLE_IDX)); + } + } + + co_return std::move(result); } 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(); +} diff --git a/runtime-light/stdlib/curl/defs.h b/runtime-light/stdlib/curl/defs.h index 4efa725d9b..93688c9d61 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; @@ -248,4 +249,32 @@ enum class CURL_HTTP_VERSION : uint8_t { _2_PRIOR_KNOWLEDGE = 5, }; +enum class CURLME : 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/defs.h b/runtime-light/stdlib/web-transfer-lib/defs.h index e0b5279672..11af1b5481 100644 --- a/runtime-light/stdlib/web-transfer-lib/defs.h +++ b/runtime-light/stdlib/web-transfer-lib/defs.h @@ -4,9 +4,12 @@ #pragma once +#include #include #include #include +#include +#include #include "runtime-light/stdlib/diagnostics/logs.h" #include "runtime-light/tl/tl-types.h" @@ -91,14 +94,25 @@ class property_value { static inline auto deserialize(const tl::webPropertyValue& tl_prop_value) noexcept -> property_value; inline auto to_mixed() const noexcept -> mixed; + + template + requires std::same_as || std::same_as || std::same_as || std::same_as || std::same_as> || + std::same_as> || std::same_as> || std::same_as> + inline auto to() const noexcept -> std::optional; }; +using simple_transfers = kphp::stl::unordered_set; + using properties_type = kphp::stl::unordered_map; struct simple_transfer_config { properties_type properties{}; }; +struct composite_transfer_config { + properties_type properties{}; +}; + struct response { string headers; string body; @@ -126,6 +140,7 @@ enum backend_internal_error : int64_t { post_field_value_not_string = -513, header_line_not_string = -514, unsupported_property = -515, + cannot_set_transfer_token = -516, }; // ---------------------------------- @@ -294,4 +309,14 @@ inline auto property_value::to_mixed() const noexcept -> mixed { return std::visit([](const auto& v) noexcept -> mixed { return mixed{v}; }, this->value); } +template +requires std::same_as || std::same_as || std::same_as || std::same_as || std::same_as> || + std::same_as> || std::same_as> || std::same_as> +inline auto property_value::to() const noexcept -> std::optional { + if (!std::holds_alternative(this->value)) { + return {}; + } + return std::get(this->value); +} + } // namespace kphp::web diff --git a/runtime-light/stdlib/web-transfer-lib/details/web-error.h b/runtime-light/stdlib/web-transfer-lib/details/web-error.h index 6fb404446a..77093dfe03 100644 --- a/runtime-light/stdlib/web-transfer-lib/details/web-error.h +++ b/runtime-light/stdlib/web-transfer-lib/details/web-error.h @@ -20,6 +20,7 @@ inline auto process_error(tl::WebError e) noexcept -> error { case backend_internal_error::post_field_value_not_string: case backend_internal_error::header_line_not_string: case backend_internal_error::unsupported_property: + case backend_internal_error::cannot_set_transfer_token: return error{.code = WEB_INTERNAL_ERROR_CODE, .description = string(e.description.value.data(), e.description.value.size())}; default: // BackendError diff --git a/runtime-light/stdlib/web-transfer-lib/details/web-property.h b/runtime-light/stdlib/web-transfer-lib/details/web-property.h index 6faea6593f..c9cedb0d0e 100644 --- a/runtime-light/stdlib/web-transfer-lib/details/web-property.h +++ b/runtime-light/stdlib/web-transfer-lib/details/web-property.h @@ -7,11 +7,8 @@ #include #include #include -#include -#include #include #include -#include #include #include @@ -27,6 +24,8 @@ namespace kphp::web { +enum class get_properties_policy : uint8_t { cached, load }; + inline auto set_transfer_property(std::variant transfer, property_id prop_id, property_value prop_value) -> std::expected { // Simple @@ -59,10 +58,10 @@ inline auto set_transfer_property(std::variant{}; } -inline auto get_transfer_properties(std::variant transfer, - std::optional prop_id) -> kphp::coro::task> { +inline auto get_transfer_properties(std::variant transfer, std::optional prop_id, + get_properties_policy policy) -> kphp::coro::task> { // Try to get a cached prop - if (prop_id.has_value()) { + if (prop_id.has_value() && policy == get_properties_policy::cached) { const auto p{prop_id.value()}; const auto& web_state{WebInstanceState::get()}; if (std::holds_alternative(transfer)) { @@ -127,8 +126,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/details/web-response.h b/runtime-light/stdlib/web-transfer-lib/details/web-response.h new file mode 100644 index 0000000000..548be04cda --- /dev/null +++ b/runtime-light/stdlib/web-transfer-lib/details/web-response.h @@ -0,0 +1,94 @@ +// 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 +#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_resp{}; + tl::fetcher tlf{ok_or_error_buffer}; + if (!simple_web_transfer_resp.fetch(tlf)) [[unlikely]] { + kphp::log::error("failed to parse response of Simple descriptor"); + } + if (auto r{simple_web_transfer_resp.value}; std::holds_alternative(r)) { + err.emplace(details::process_error(std::get(r))); + return kphp::component::inter_component_session::client::response_readiness::ready; + } + 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 new file mode 100644 index 0000000000..0ce5539604 --- /dev/null +++ b/runtime-light/stdlib/web-transfer-lib/web-composite-transfer.h @@ -0,0 +1,376 @@ +// 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 +#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"); + } + + // 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))}; + } + + // 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{}; +} + +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("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 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{"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{"this Simple transfer is not yet part of any Composite transfer"}}}; + } + 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{}; +} + +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()}; + + 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"); + } + + // Enumerate over all included simple transfers, close and remove them + auto& simple_transfers{web_state.composite_transfer2simple_transfers[ct.descriptor]}; + auto it_simple_transfer{simple_transfers.begin()}; + while (simple_transfers.size()) { + if (auto remove_res{co_await kphp::web::composite_transfer_remove(ct, kphp::web::simple_transfer{*it_simple_transfer})}; !remove_res.has_value()) { + co_return std::move(remove_res); + }; + } + + 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{}; +} + +template +inline auto composite_transfer_wait_updates(composite_transfer ct, + std::chrono::duration timeout) noexcept -> kphp::coro::task> { + auto& web_state{WebInstanceState::get()}; + + auto& composite2config{web_state.composite_transfer2config}; + if (!composite2config.contains(ct.descriptor)) [[unlikely]] { + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"unknown Composite transfer"}}}; + } + + auto session{web_state.session_get_or_init()}; + if (!session.has_value()) [[unlikely]] { + kphp::log::error("failed to start or get session with Web component"); + } + + if ((*session).get() == nullptr) [[unlikely]] { + kphp::log::error("session with Web components has been closed"); + } + + tl::CompositeWebTransferWaitUpdates tl_wait_updates{ + .descriptor = tl::u64{ct.descriptor}, + .timeout = tl::u64{.value = static_cast(std::chrono::duration_cast(timeout).count())}}; + tl::storer tls{tl_wait_updates.footprint()}; + tl_wait_updates.store(tls); + + kphp::stl::vector resp_buf{}; + auto response_buffer_provider{[&resp_buf](size_t size) noexcept -> std::span { + resp_buf.resize(size); + return {resp_buf.data(), size}; + }}; + + auto resp{co_await (*session).get()->client.query(tls.view(), std::move(response_buffer_provider))}; + if (!resp.has_value()) [[unlikely]] { + kphp::log::error("failed to send request of waiting Composite transfer updates"); + } + + tl::Either composite_wait_resp{}; + tl::fetcher tlf{*resp}; + if (!composite_wait_resp.fetch(tlf)) [[unlikely]] { + kphp::log::error("failed to parse response of waiting Composite transfer updates"); + } + + auto result{composite_wait_resp.value}; + if (std::holds_alternative(result)) { + co_return std::unexpected{details::process_error(std::get(result))}; + } + + const auto updated_descriptors_num{std::get(result).updated_descriptors_num.value}; + co_return std::expected{updated_descriptors_num}; +} + +} // namespace kphp::web diff --git a/runtime-light/stdlib/web-transfer-lib/web-simple-transfer.h b/runtime-light/stdlib/web-transfer-lib/web-simple-transfer.h index 06618406d4..d2c330c486 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,8 @@ #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/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" #include "runtime-light/tl/tl-functions.h" @@ -23,7 +25,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,27 +63,23 @@ 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}; } 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"); - } - - 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"); + co_return std::unexpected{error{.code = WEB_INTERNAL_ERROR_CODE, .description = string{"unknown Simple transfer"}}}; } kphp::stl::vector tl_props{}; @@ -94,65 +93,31 @@ 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: - 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> { - 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 +151,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 +160,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 +175,16 @@ 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 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); + }; + } + 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 3de8b7669b..27c7103a4e 100644 --- a/runtime-light/stdlib/web-transfer-lib/web-state.h +++ b/runtime-light/stdlib/web-transfer-lib/web-state.h @@ -26,8 +26,13 @@ struct WebInstanceState final : private vk::not_copyable { bool session_is_finished{false}; kphp::stl::unordered_map simple_transfer2config{}; - kphp::stl::unordered_map + 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 74919208ef..9340eea470 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; @@ -431,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; @@ -463,24 +501,113 @@ 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: + using web_backend_type = tl::u8; + web_backend_type web_backend; + + void store(tl::storer& tls) const noexcept { + tl::magic{.value = COMPOSITE_WEB_TRANSFER_OPEN_MAGIC}.store(tls); + web_backend.store(tls); + } + + constexpr size_t footprint() const noexcept { + return tl::magic{.value = COMPOSITE_WEB_TRANSFER_OPEN_MAGIC}.footprint() + web_backend.footprint(); + } +}; + +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(); + } +}; + +class CompositeWebTransferPerform final { + static constexpr uint32_t COMPOSITE_WEB_TRANSFER_PERFORM_MAGIC = 0xAA24'42BB; public: - tl::u8 is_simple; tl::u64 descriptor; - tl::Maybe property_id; + tl::compositeWebTransferConfig config; void store(tl::storer& tls) const noexcept { - tl::magic{.value = WEB_TRANSFER_GET_PROPERTIES_MAGIC}.store(tls); - is_simple.store(tls); + tl::magic{.value = COMPOSITE_WEB_TRANSFER_PERFORM_MAGIC}.store(tls); descriptor.store(tls); - property_id.store(tls); + config.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_PERFORM_MAGIC}.footprint() + descriptor.footprint() + config.footprint(); + } +}; + +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(); } }; +class CompositeWebTransferWaitUpdates final { + static constexpr uint32_t COMPOSITE_WEB_TRANSFER_WAIT_UPDATES_MAGIC = 0x1212'1997; + +public: + tl::u64 descriptor; + tl::u64 timeout; + + void store(tl::storer& tls) const noexcept { + tl::magic{.value = COMPOSITE_WEB_TRANSFER_WAIT_UPDATES_MAGIC}.store(tls); + descriptor.store(tls); + timeout.store(tls); + } + + constexpr size_t footprint() const noexcept { + return tl::magic{.value = COMPOSITE_WEB_TRANSFER_WAIT_UPDATES_MAGIC}.footprint() + descriptor.footprint() + timeout.footprint(); + } +}; } // namespace tl diff --git a/runtime-light/tl/tl-types.h b/runtime-light/tl/tl-types.h index e18078108f..cfe4dee1ee 100644 --- a/runtime-light/tl/tl-types.h +++ b/runtime-light/tl/tl-types.h @@ -1224,6 +1224,24 @@ struct webProperty 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 ===== struct simpleWebTransferConfig final { tl::vector properties; @@ -1253,18 +1271,18 @@ class SimpleWebTransferOpenResultOk final { } }; -class SimpleWebTransferPerformResultOk final { - static constexpr uint32_t SIMPLE_WEB_TRANSFER_PERFORM_RESULT_OK_MAGIC = 0x77A8'98FF; +class SimpleWebTransferResponseResultOk final { + static constexpr uint32_t SIMPLE_WEB_TRANSFER_RESPONSE_RESULT_OK_MAGIC = 0x77A8'98FF; public: bool fetch(tl::fetcher& tlf) noexcept { tl::magic magic{}; - bool ok{magic.fetch(tlf) && magic.expect(SIMPLE_WEB_TRANSFER_PERFORM_RESULT_OK_MAGIC)}; + bool ok{magic.fetch(tlf) && magic.expect(SIMPLE_WEB_TRANSFER_RESPONSE_RESULT_OK_MAGIC)}; return ok; } constexpr size_t footprint() const noexcept { - return tl::magic{.value = SIMPLE_WEB_TRANSFER_PERFORM_RESULT_OK_MAGIC}.footprint(); + return tl::magic{.value = SIMPLE_WEB_TRANSFER_RESPONSE_RESULT_OK_MAGIC}.footprint(); } }; @@ -1298,20 +1316,113 @@ class SimpleWebTransferResetResultOk final { } }; -class WebTransferGetPropertiesResultOk final { - static constexpr uint32_t WEB_TRANSFER_GET_PROPERTIES_RESULT_OK_MAGIC = 0x48A7'16CC; +// ===== Composite ===== -public: +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; + +public: + 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(); + } +}; + +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(); + } +}; + +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; + +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(); + } +}; + +class CompositeWebTransferWaitUpdatesResultOk final { + static constexpr uint32_t COMPOSITE_WEB_TRANSFER_WAIT_UPDATES_RESULT_OK_MAGIC = 0x2007'1997; + +public: + tl::u64 updated_descriptors_num; + + bool fetch(tl::fetcher& tlf) noexcept { + tl::magic magic{}; + bool ok{magic.fetch(tlf) && magic.expect(COMPOSITE_WEB_TRANSFER_WAIT_UPDATES_RESULT_OK_MAGIC) && updated_descriptors_num.fetch(tlf)}; + return ok; + } + + constexpr size_t footprint() const noexcept { + return tl::magic{.value = COMPOSITE_WEB_TRANSFER_WAIT_UPDATES_RESULT_OK_MAGIC}.footprint() && updated_descriptors_num.footprint(); } }; diff --git a/tests/phpt/curl/10_curl_multi_info_read.php b/tests/phpt/curl/10_curl_multi_info_read.php index 2c5177768d..d4830369e0 100644 --- a/tests/phpt/curl/10_curl_multi_info_read.php +++ b/tests/phpt/curl/10_curl_multi_info_read.php @@ -1,4 +1,4 @@ -@ok k2_skip +@ok $status, "easy_result1" => "", "easy_result2" => ""]; + echo json_encode($resp); + return; + } + + $response1 = (string)curl_multi_getcontent($ch1); + $response2 = (string)curl_multi_getcontent($ch2); + + curl_multi_remove_handle($mh, $ch1); + curl_multi_remove_handle($mh, $ch2); + + curl_multi_close($mh); + curl_close($ch1); + curl_close($ch2); + + $resp = ["mutli_ret_code" => $status, "easy_result1" => $response1, "easy_result2" => $response2]; + echo json_encode($resp); +} + main(); diff --git a/tests/python/tests/curl/test_curl_multi.py b/tests/python/tests/curl/test_curl_multi.py new file mode 100644 index 0000000000..0c6c3f2742 --- /dev/null +++ b/tests/python/tests/curl/test_curl_multi.py @@ -0,0 +1,13 @@ +import pytest +from python.tests.curl.curl_test_case import CurlMultiTestCase + + +class TestCurl(CurlMultiTestCase): + test_case_uri="/test_curl_multi" + + def test_curl_multi_transfers(self): + self.assertEqual(self._curl_multi_request("/echo/multi/payload_1", "/echo/multi/payload_2"), { + "mutli_ret_code": 0, + "easy_result1": "payload_1", + "easy_result2": "payload_2" + })