[k2] rpc client API - #1635
Conversation
156e9ec to
222adae
Compare
a342aa9 to
e77636c
Compare
0c1f4cc to
44d348e
Compare
…GAIN` errno if response is not ready
…e std::string_view instead of string, removed m_id from query_handle
…sponse_awaiter_tasks
…:STATIC_BUFFER_LENGTH`; added `if collect_responses_extra_info` check
df743db to
6435238
Compare
| return {descriptor}; | ||
| } | ||
|
|
||
| inline std::expected<size_t, int32_t> rpc_get_response_size(uint64_t rpc_d) noexcept { |
There was a problem hiding this comment.
Let's use k2::descriptor instead of uint64_t
| int32_t k2_component_access(size_t name_len, const char* name); | ||
|
|
||
| /** | ||
| * Try to send rpc request. In case of success write descriptor of rpc request to `rpc_d`, otherwise return `errno` != 0, |
There was a problem hiding this comment.
Could you please make these docs more clear?
For example, for k2_rpc_send_request it might look like:
* Try to send an RPC request. On success, write the request descriptor to `rpc_d`;
* this descriptor is later used when calling `k2_rpc_fetch_response`.
* Otherwise, return a non-zero `errno`.
...
Please, take a look at other API functions you've added and check their docs too
| int64_t current_query_id{kphp::rpc::VALID_QUERY_ID_RANGE_START}; | ||
|
|
||
| kphp::stl::unordered_map<int64_t, kphp::coro::shared_task<std::optional<string>>, kphp::memory::script_allocator> response_awaiter_tasks; | ||
| kphp::stl::unordered_map<int64_t, kphp::coro::shared_task<std::expected<kphp::stl::vector<std::byte, kphp::memory::script_allocator>, int32_t>>, |
There was a problem hiding this comment.
This is not a good idea to have shared_task returning something like vector. shared_task returns a copy of its result on each co_await, so you'll end up with multiple copies of the vector
| m_deadline{deadline} {} | ||
|
|
||
| template<std::invocable<size_t> B> | ||
| requires std::is_convertible_v<std::invoke_result_t<B, size_t>, std::span<std::byte>> |
There was a problem hiding this comment.
Let's make it std::same_as<std::span<byte>>
|
|
||
| template<std::invocable<size_t> B> | ||
| requires std::is_convertible_v<std::invoke_result_t<B, size_t>, std::span<std::byte>> | ||
| auto get_ready_response(B response_buffer_provider) noexcept -> std::expected<std::span<std::byte>, int32_t>; |
There was a problem hiding this comment.
Why do we need to accept this parameter by value?
|
|
||
| template<std::invocable<size_t> B> | ||
| requires std::is_convertible_v<std::invoke_result_t<B, size_t>, std::span<std::byte>> | ||
| auto get_response(B response_buffer_provider) noexcept -> kphp::coro::task<std::expected<std::span<std::byte>, int32_t>>; |
There was a problem hiding this comment.
Just a note to think about: we can get rid of this method. Instead, we might have an interface like this:
kphp::rpc::query q{create_some_query()};
co_await q(my_response_buffer_provider);
What do you think?
| } | ||
| size_t response_size{*response_size_exp}; | ||
|
|
||
| std::span<std::byte> response_buffer{std::invoke(response_buffer_provider, response_size)}; |
There was a problem hiding this comment.
I think we need to check response_buffer's size here
| requires std::is_convertible_v<std::invoke_result_t<B, size_t>, std::span<std::byte>> | ||
| auto query::get_response(B response_buffer_provider) noexcept -> kphp::coro::task<std::expected<std::span<std::byte>, int32_t>> { | ||
| if (m_descriptor == k2::INVALID_PLATFORM_DESCRIPTOR) { | ||
| co_return std::unexpected{TL_ERROR_INTERNAL}; |
There was a problem hiding this comment.
We can use TL_ERROR_INVALID_CONNECTION_ID to improve observability
| kphp::coro::io_scheduler& m_scheduler{kphp::coro::io_scheduler::get()}; | ||
| std::chrono::nanoseconds timeout{kphp::time::remaining(m_deadline)}; | ||
|
|
||
| // TODO DISCUSS: may be completely remove timeout monitoring in kphp and leave it in k2-node's rpc client ??? |
There was a problem hiding this comment.
Seems reasonable now. There are two ways the platform can notify us about RPC timeout:
- close the stream -> aren't there other errors that can lead to stream being closed?
- write some "there was a timeout" answer to the stream -> requires a bit more complex processing since such an answer will vary depending on RPC kind we use
| /** | ||
| * Calculate time remaining to the deadline. | ||
| */ | ||
| inline std::chrono::nanoseconds remaining(std::chrono::steady_clock::time_point deadline) noexcept { |
There was a problem hiding this comment.
What do you think about following implementation:
/**
* Returns the current steady-clock time point from the K2 platform.
*/
inline std::chrono::steady_clock::time_point now() noexcept /* iff k2::instant is noexcept */ {
k2::TimePoint now_instant{};
k2::instant(std::addressof(now_instant));
return std::chrono::steady_clock::time_point{std::chrono::nanoseconds{now_instant.time_point_ns}};
}
/**
* Calculate time remaining until the deadline.
*/
template <typename Duration>
inline auto remaining(std::chrono::time_point<std::chrono::steady_clock, Duration> deadline) noexcept {
return deadline - now();
}
/**
* Converts a timeout to the time point at which it elapses, i.e. the deadline.
*/
template <typename Rep, typename Period>
inline auto expires_at(std::chrono::duration<Rep, Period> timeout) noexcept {
return now() + timeout;
}
It makes client code easier by removing mandatory conversions from any duration to nanoseconds
| inline kphp::coro::task<array<int64_t>> f$rpc_send_requests(string actor, array<mixed> tl_objects, Optional<double> timeout, bool ignore_answer, | ||
| class_instance<C$KphpRpcRequestsExtraInfo> requests_extra_info, | ||
| bool need_responses_extra_info) noexcept { | ||
| inline array<int64_t> f$rpc_send_requests(string actor, array<mixed> tl_objects, Optional<double> timeout, bool ignore_answer, |
There was a problem hiding this comment.
As it's not longer a coroutine, we don't need to pass parameters by value
| m_deadline{deadline} {} | ||
|
|
||
| template<std::invocable<size_t> B> | ||
| requires std::is_same_v<std::invoke_result_t<B, size_t>, std::span<std::byte>> |
There was a problem hiding this comment.
missing includes for concepts and type_traits
|
|
||
| inline auto query::send(std::string_view actor, std::chrono::milliseconds timeout, | ||
| std::span<const std::byte> request_buffer) noexcept -> std::expected<query, int32_t> { | ||
| auto descriptor_exp{k2::rpc_send_request(actor, request_buffer, RpcKind::TL_RPC)}; |
There was a problem hiding this comment.
I think send needs to accept RPC kind as a parameter
| co_return std::unexpected{TL_ERROR_INVALID_CONNECTION_ID}; | ||
| } | ||
|
|
||
| kphp::coro::io_scheduler& m_scheduler{kphp::coro::io_scheduler::get()}; |
There was a problem hiding this comment.
nit: no need to get the scheduler unless it's really needed
| auto timeout{kphp::time::remaining(m_deadline)}; | ||
|
|
||
| if (timeout <= std::chrono::nanoseconds::zero()) { | ||
| co_return std::move(*this).get_ready_response<B>(std::move(response_buffer_provider)); |
There was a problem hiding this comment.
nit: I think it's better to declare get_ready_response and static constexpr lambda inside response
| std::swap(tl_storer, rpc_server_instance_st.tl_storer); | ||
| } | ||
| }}; | ||
| // if we have to allocate memory for request buffer, it will be in this vector, and it will be freed at the end of the function |
There was a problem hiding this comment.
I think it's better to use the tl_storer buffer for this
| auto fetch_task{kphp::component::fetch_response(stream, kphp::component::read_ext::append(*opt_response))}; | ||
| if (auto expected{co_await kphp::coro::io_scheduler::get().schedule(std::move(fetch_task), timeout)}; !expected) [[unlikely]] { | ||
| opt_response = std::nullopt; | ||
| static constexpr auto awaiter_coroutine{[](query q, int64_t query_id, bool collect_responses_extra_info) mutable noexcept |
| -> kphp::coro::shared_task<std::expected<string, int32_t>> { | ||
| string resp_buf{}; | ||
| const auto response_buffer_provider{[&resp_buf](size_t size) noexcept -> std::span<std::byte> { | ||
| resp_buf.assign(size, true); |
There was a problem hiding this comment.
reserve_at_least better expresses what you do here
| return {reinterpret_cast<std::byte*>(resp_buf.buffer()), size}; | ||
| }}; | ||
|
|
||
| auto fetch_task{std::move(q).response<decltype(response_buffer_provider)>(response_buffer_provider)}; |
There was a problem hiding this comment.
No need for explicit decltype here
| auto fetch_task{kphp::component::fetch_response(stream, [](std::span<const std::byte>) noexcept {})}; | ||
| std::ignore = co_await kphp::coro::io_scheduler::get().schedule(std::move(fetch_task), timeout); | ||
| }}; | ||
| static constexpr auto ignore_answer_awaiter_coroutine{[](query q) noexcept -> kphp::coro::shared_task<> { |
No description provided.