-
Notifications
You must be signed in to change notification settings - Fork 36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Move the adapter module to the detail namespace #128
Comments
Hello. Not sure what exactly planned to be moved, so let me ask a question. I found 'boost::redis::adapter::boost_redis_adapt' function in redis/adapter/adapter.hpp, and used it to type erasue boost::redis::response, while still taking advantage of it's statis typing. I actually thought that was kinda planned, because 'response' argument for async_exec is fully templated and require only boost_redis_adapt function to be defined. My naive approach: class ResponseHider
{
using Call = std::function<void(std::any &, std::size_t i, boost::redis::resp3::basic_node<std::string_view> const &nd, boost::system::error_code &ec)>;
using GetSupportedResponseSize = std::function<size_t(const std::any &)>;
std::any adapter_;
Call call_;
GetSupportedResponseSize get_supported_response_size_{};
public:
template<class ...Results>
explicit ResponseHider(boost::redis::response<Results...> &response)
{
using Adapter = std::decay_t<decltype(boost_redis_adapt(response))>;
adapter_ = boost_redis_adapt(response);
// No capture, any as argument with recasting in runtime to not bother with copy/move constructors
call_ = [](std::any &any_adapter, auto &&...args)
{
auto &adapter = std::any_cast<Adapter &>(any_adapter);
adapter(std::forward<decltype(args)>(args)...);
};
get_supported_response_size_ = [](const std::any &any_adapter)
{
const auto &adapter = std::any_cast<const Adapter &>(any_adapter);
return adapter.get_supported_response_size();
};
}
void operator()(std::size_t i, boost::redis::resp3::basic_node<std::string_view> const &nd, boost::system::error_code &ec)
{
return call_(adapter_, i, nd, ec);
}
[[nodiscard]]
auto get_supported_response_size() const noexcept
{
return get_supported_response_size_(adapter_);
}
};
ResponseHider boost_redis_adapt(ResponseHider &t) noexcept; ResponseHider objects then can be passed into async_exec in implementation files. I thought that templated response (requiring just adapter for it) is exactly for it, to customize responses, giving also way to not reimplement deserialization of resp protocol by using original adapters. That kind of usage wasn't planned? |
Hi, sorry for being so late. Before the inclusion in Boost # Present
conn.async_exec(request, response, token);
# Before Boost
conn.async_exec(request, adapt(response), token); In the Boost review however somebody raised the question about why I have to call adapt()? and I decided to hide its call here. This resulted in a simpler API that is however harder to type-erase as you correctly noted (type-erasure occurrs here btw). There is however a simple fix for that: Move the adapter out of the inner layer to expose it to the user as originaly thought. That would result in another # Current overload
template <class Response, class CompletionToken>
auto async_exec(request const& req, Response& resp, CompletionToken&& token);
# Overload that takes the currently internal type-erased adapter.
template <class CompletionToken>
auto async_exec(request const& req, adapter_type adapter, CompletionToken&& token); By using For family reasons however I don't have much time to dedicate to this feature in the upcomming weeks (second baby comes in a week) but if you are lucky some Asio expert such as @anarthal might get interest in implementing it. If you want to folow this path however, please open a new ticket with reference to this one, thanks. |
If we're sure removing
This is true. @GoldRobot is still going to have to pimpl away the connection, as including @GoldRobot I found myself in a similar need with Redis, and came up with this scheme. Essentially, you build a service class (using pimpl or virtuals) where you expose a repository-like API. The style is a little old-fashioned, but gets the job done, achieves a pretty good level of separation of concerns, and is very well suited to run unit tests using mocks. Another option (not ideal, but also gets the job done) is to use explicit template instantiations:
Even if a generic type-eraser is the ideal, I'd avoid it for now until we understand whether Moving forward with the Redis issue. What you're calling I need to understand a couple of things before doing anything: If you're happy with this change, I can implement it and PR (might be big depending on what you answer). Also, if you're going to be unavailable to maintain Boost.Redis, I can probably dedicate some time to doing it moving forward. |
congratulations and all the best man :)
Callbacks, unfortenatly.
Kind of that what I done, difference is that my class expose templated response argument. Basically I wrap exactly connection, to then reference it from logical blocks. So additional layer between boost::redis::connection and chat::redis_client.
Understood. Other questions is for mzimbres, but in case he is busy, what I found.
As I see, resp3::parse return true only if whole response was consumed (or if error occurs). resp3::parse result used by on_read. If resp3::parse result returned false (parser::consume said need more basically), on_read convert it to parse_result::needs_more. Which means append_some_op will happen in cycle inside of reader_op. resp3::parser track number of 'consumed' (readed actually) bytes, untill reset get called. reset called inside of on_finish_parsing, along with buffer clearing. So stream-parsing is happens. It's minimal element is resp3::node, and buffer never get cleared untill whole response message readed. But based of presence of std::size_t here, I suspect there was plan to consume parsed bytes from buffer on go, but right now it's always zero. |
The internal connection type ( std::function<void(node_type const&, system::error_code&)>; to type-erase it.
The adapter is lightweight handle to the response and knows how to fill it as chunks of RESP3 arrive on the socket.
Correct, Boost.Redis implements a sans-IO stream parser. It could be even moved into a separate library to complement Boost.Serialization and Boost.Json.
The connection class will pass incomming data to the parser until it is done with the message and after that it is reset.
Yes, we need it. There is no way of knowing the size of e.g. agreegate message types in advance.
Yes, that is fine. I expect something along the lines of my previous message, where another async_exec is provided, which takes an adapter as parameter.
That would be nice, thanks. I am not going to say I will be completely unavailable, but only that I won't engage in larger features. Help is much appreciated. |
Looks like I completely misread the code involving stream parsing, thanks @GoldRobot for your detailed analysis. Looks like we're all agreeing on exposing a type-erased response adapter as the best solution. I'll try to work that out and PR you. |
No elements of the adapter module appears in the public API so it should be moved in the detail namespace and directories. Some parts of it belong into
redis/detail
while other inredis/resp3/detail
.The text was updated successfully, but these errors were encountered: