-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Feature request: Type-erased handler wrapper #1100
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
Comments
Upon further thought, the API would still need to expose template functions taking any completion token so that it may perform an |
Could you please take a look at https://github.com/mabrarov/asio_samples/blob/master/libs/ma_handler_storage/include/ma/handler_storage.hpp (https://github.com/mabrarov/asio_samples/blob/master/tests/ma_handler_storage_test/src/handler_storage_test.cpp) - if it does the same as you mentioned? I understand, that it’s pre-Network / Executors TS, i.e. doesn’t deal with Asio executors, allocators and completion tokens (but supports Asio legacy custom handler allocation and execution). I believe, there was discussion about the same somewhere here or in mailing list (cannot find it right now) and that discussion had an outcome (maybe new feature in Asio). I feel like my intention around |
Here is that discussion (idea is close to this topic, but described from another point of view / use cases): #891 |
I hacked together a prototype on this branch: https://github.com/chriskohlhoff/asio/tree/any-completion-handler Example found here: (The implementation should work on C++11 or later btw, even though the example is for C++20.) |
I've written that one over here: https://github.com/madmongo1/asio_experiments/blob/master/include/asioex/async_op.hpp Also containing a type-erased op |
That example covers most of my intended usages nicely. Thanks! Another use case is handlers that are stored for a long duration and executed whenever an event occurs. Such handlers need to be executed multiple times, and must therefore be copyable when passed to I've cobbled my own type-erased handler (still in a development branch) in the interim, and was faced with these challenges:
|
@chriskohlhoff Would it be possible to add an template <typename H, typename E, typename Handler = typename decay<H>::type>
any_completion_handler(H&& h, E&& e)
: fn_table_(&detail::any_completion_handler_fn_table_instance<Handler, Signatures...>::value),
impl_(detail::any_completion_handler_impl<Handler>::create(
(get_associated_executor)(h, std::forward<E>(e)),
(get_associated_cancellation_slot)(h), std::forward<H>(h)))
{
} |
@chriskohlhoff I think I can (and probably should) refactor my code so that the fallback executor is passed at the time of |
@chriskohlhoff I'm getting compile errors when doing: boost::asio::post( exec, std::bind(std::move(f), std::forward<Ts>(args)...) ); where I'll write a minimal example that duplicates the problem, but in the meantime, is what I'm attempting even possible? |
Narrowed down the problem to |
On the requirement for multi-shot handlers, a while back I extended
|
This was easy enough for me to implement via a wrapper class containing both |
@chriskohlhoff I'm finally out of compile error hell due to the necessity to move
The problem is due to this:
where static constexpr member variables are not implicity inline in C++11. I think it should be replaced with a static constexpr function. |
No, that didn't work as static variables are not permitted in template <typename H, typename... Ss>
constexpr any_completion_handler_fn_table<Ss...>
any_completion_handler_fn_table_instance<H, Ss...>::value; I'm not sure whether this is safe, because different translation units will have different tables. |
@chriskohlhoff , my CppWAMP code (not yet pushed) is now working with a "boostified" version of your
Thanks again for writing the Is there a way I can non-intrusively "fix" For that matter, why must it be May I suggest that a new |
@chriskohlhoff Other finishing touches to your
|
You're correct, and this is something i've been planning too, so I've added it to the branch. (However, before merging this I want to investigate relaxing the requirements on a completion executor some more, and that requires some further thought.) I have also added the other things you mentioned (except cancellation which was already there). |
Awesome! There is no |
So I did. Fixed. |
In the absence of
and similar for |
Thanks! With the I must say I'm impressed with how quickly you put all this together and that it works with just a small example to test it against! I still need to run Valgrind to check for memory leaks, but everything seems to be working well so far. If you make any changes or additions to Your I used to have separate APIs in my library for traditional callbacks and stackful coroutines, but now I'm able to collapse them into a single API that accepts any completion token, while still being able to compile my library. |
I pushed an updated branch with some changes to ensure that the |
EDIT: Nevermind, brainfart. Those latest no-throw changes result in compile errors that I can't easily decipher. The first of them is:
It occurs when I attempt to I'm still on Boost 1.79; perhaps updating to Boost 1.80 would solve the problem. If you care to compile my library yourself to see the complete error traces, here's a snapshot of my library where the errors occur: https://github.com/ecorm/cppwamp/tree/e0455d11601fdb7bbf56dcb705ccc0407e410cf8 To compile:
The above will download Boost and will link against it, instead of your system's Boost libraries. Your I apologize in advance for the convoluted design; I'm currently working on simplifying it. |
Oh, I see the problem now. You made changes to Sorry about the brainfart. Edit: I see now I can just manually remove the |
@chriskohlhoff It's currently not possible to use
I tried with this using the head of your #include <iostream>
#include <asio/any_completion_executor.hpp>
#include <asio/detached.hpp>
#include <asio/io_context.hpp>
#include <asio/spawn.hpp>
//------------------------------------------------------------------------------
int main()
{
asio::io_context ioctx;
asio::any_completion_executor exec(ioctx.get_executor());
asio::spawn(
exec,
[](asio::basic_yield_context<asio::any_completion_executor> yield)
{
std::cout << "Hello" << std::endl;
}
#ifdef ASIO_DISABLE_BOOST_COROUTINE
, asio::detached
#endif
);
ioctx.run();
return 0;
} With |
As a workaround, I'm using this hack: template <typename E, typename F>
void spawn_workaround(E& executor, F&& function)
{
using namespace asio;
auto ex1 = executor.template target<typename io_context::executor_type>();
if (ex1 != nullptr)
{
spawn(*ex1, std::forward<F>(function));
return;
}
auto ex2 = executor.template target<any_io_executor>();
if (ex2 != nullptr)
{
spawn(*ex2, std::forward<F>(function));
return;
}
throw std::logic_error("Executor must originate from io_service::executor_type "
"or any_io_executor");
} |
That legacy non-completion-token overload of |
I mistakenly thought those overloads where not available when |
Hi @chriskohlhoff and @ecorm, First of all I want to thank you for idea and implementation of this feature. I'd like to notice (and get your feedback) one issue which users of
This case is the reason I created (ugly) |
@mabrarov I'm having difficulty understanding your second point. Can you clarify with some pseudocode? |
@chriskohlhoff I've updated to Boost 1.81.0 which now includes #include <iostream>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>
int main()
{
namespace asio = boost::asio;
asio::io_context ioctx;
asio::any_completion_executor exec(ioctx.get_executor());
asio::any_completion_handler<void ()> handler([](){std::cout << "Hello";});
asio::post(exec, std::move(handler)); // error: no matching function for call to `post`
// asio::post(ioctx, std::move(handler)); // This compiles
// exec.execute(std::move(handler)); // And this compiles
ioctx.run();
return 0;
} |
I think I stumbled upon an explanation in this New in Boost 1.81 blog post, under Asio -> Semantic Changes. I'm gonna need to re-read it a few times to make sense of it. I think this is what I should be doing: asio::post(ioctx, asio::bind_executor(exec, std::move(handler))); |
Boost 1.81 is now working with my library, which uses |
Added #1330, a feature request for a multi-shot equivalent of |
Uh oh!
There was an error while loading. Please reload this page.
We need a wrapper that type-erases an asynchronous handler and bundles its associated executor and allocator. This would prevent the forcing of higher-level networking libraries to be header-only.
It would also allow such higher-level networking libraries to expose known types in its API, instead of templatized function arguments.This proposed type-erased handler could be passed to
boost::asio::post
and the like. It would also have to support move-only underlying handlers.Some of us still care about compile times and being able to apply the Pimpl idiom. This is not possible when our libraries are forced to be header-only because of Asio.
I have found such a wrapper in the wild at https://github.com/inetic/asio-utp/blob/master/include/asio_utp/detail/handler.hpp . However it lacks small buffer optimization and has additional stuff that seems specific to that library.
The text was updated successfully, but these errors were encountered: