Mixing async/sync code thanks to boost::context
Hello,
I am new in this mailing list, I hope this is the right list to post my
issue.
Context:
I often have to deal with some code which is originally synchronous. When
we introduce an asynchronous call, all the callers must be made
asynchronous with async/await everywhere.
Let's take a concrete example:
Suppose we have an existing synchronous library which makes computation.
Suppose we can give a custom function to this library to compute a part of
the calculation. We decide that this function will call a server...
We may wish the main compute function to become asynchronous...
Now the big question:
Why isn't it possible so far to do this without changing all the functions
?? I find this really ugly, this is really difficult to make a good design.
With asymetric async calls, I doubt this is possible, so I tried
boost::context.
In my example, the "library" is represented by the function calculate which
is synchronous. It calls a function call_server which simulates the call to
a server, in fact it will just wait for 5 seconds but in a asynchronous
way.
The main function will do a computation in parallel while the server will
be called. You will find the code at the end of the mail.
This sounds too good to be true but in this case, where did I miss
something ? I haven't implemented a socket with a select but this would
work, right ?
The library can still be used in an asynchronous or synchronous context
(with no extra cost).
To help the understanding of the code, we have 3 tasks, the continuation
are stored in the tasks vector. task are identified thanks to their indice
in the vector:
- 0: main task (id_main)
- 1: reactor(id_reactor)
- 2; computation_task (id_computation)
I implement a kind of future, whose value is retrieved at the end of the
main function and which launches a task to compute the value.
Remarks:
1/ This is not optimised, we could make fewer context switches. There is no
need for a specific task for the reactor, it could be called directly.
2/ The call_server function could detect automatically the sync or async
context, if the reactor is not active (no waiting tasks to be run), then it
would run i a synchronous way.
3/ This solution is based on a singleton for the reactor, but this is what
we want,
4/ This could be certainly better coded, I am not an expert, I use to code
more in Python and wanted to use C++ to see if I could solve these designs
problems.
I am curious to read what you think about this, I certainly missed
something so please clarify and tell me if a clean solution is possible.
Thanks a lot for reading.
Regards,
Chris
#include
On 12/12/2018 10:30, Christophe Bailly wrote:
I am new in this mailing list, I hope this is the right list to post my issue.
If you have a question about using Boost libraries, then it belongs on the Boost-Users list. This list is for development of Boost libraries.
Why isn't it possible so far to do this without changing all the functions ?? I find this really ugly, this is really difficult to make a good design.
It's inherent in the async/await design -- async functions must await to "block", they can't call traditional thread-blocking functions otherwise they prevent forward progress of other async tasks. (Sometimes this may appear to be harmless as they can sometimes use other threads to progress, but it's still incorrect code.) Boost.Context and Boost.Coroutine are the same way -- you need to explicitly yield rather than blocking the thread some other way. Async functions can only call other async functions or sync functions that are expected to complete immediately (or with only CPU-bound work). I remember recently reading someone making an analogy between this and const functions only being able to call other const functions, or (in some other language that I don't recall) I/O functions being able to call non-I/O functions but not the reverse. It's just the way it works.
In my example, the "library" is represented by the function calculate which is synchronous. It calls a function call_server which simulates the call to a server, in fact it will just wait for 5 seconds but in a asynchronous way. The main function will do a computation in parallel while the server will be called. You will find the code at the end of the mail.
This sounds too good to be true but in this case, where did I miss something ? I haven't implemented a socket with a select but this would work, right ?
If you're looking for an asynchronous reactor library, then look at Boost.Asio. Or Boost.Fiber for a slightly different solution space. These already support futures and coroutines.
On Wed, 12 Dec 2018 at 00:35, Gavin Lambert via Boost
On 12/12/2018 10:30, Christophe Bailly wrote:
I am new in this mailing list, I hope this is the right list to post my issue.
If you have a question about using Boost libraries, then it belongs on the Boost-Users list. This list is for development of Boost libraries.
It is not really a question about using library but a question/suggestion about another possible implementation of async code. If there is a flaw in my logic, where is the flaw precisely ? I am aware there is certainly one but so far I don't see honestly. I have posted a concrete code, it is just a demo for feasibility but it works.
Async functions can only call other async functions or sync functions that are expected to complete immediately (or with only CPU-bound work).
You describe the way it works, I am not convinced that this is the way it should work. The more I use async/await, the more I wonder if this could not be done differently. The code posted works, is asynchronous though there is a synchronous call in the middle (by synchronous I mean the function is written just like a synchronous function, nothing to change). I just want to understand, nothing else.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
On 12/12/2018 13:31, Christophe Bailly wrote:
It is not really a question about using library but a question/suggestion about another possible implementation of async code.
Which is still a usage of a library.
The code posted works, is asynchronous though there is a synchronous call in the middle (by synchronous I mean the function is written just like a synchronous function, nothing to change).
Coroutines look like synchronous code, but they are still asynchronous because they can be suspended and resumed at yield points. It is still problematic to block without yielding. Have a look at the examples provided with Boost.Asio.
Hello,
I made many efforts to make this clear, this was sent also to the
boost-users list.
I won't insist in this mailing list if nobody is interested in the idea
Here is my mail:
I have worked on a prototype to avoid the current limitations with async
code, the use of sync + async code is really difficult, it does not
co-exist easily. If you introduce async code, you have to migrate all your
code ... There are alternative solutions (threads..) but here is a
suggestion to overcome these limitations.
It is currently a very simple prototype but I would be interested to have
your feedback. I think this kind of solution can be really powerfull, but I
will wait to know what you think about it.
Here are the links:
https://pythonc.home.blog/
https://github.com/chbailly/5a5
Regards,
Chris
On Wed, 12 Dec 2018 at 02:11, Gavin Lambert via Boost
On 12/12/2018 13:31, Christophe Bailly wrote:
It is not really a question about using library but a question/suggestion about another possible implementation of async code.
Which is still a usage of a library.
The code posted works, is asynchronous though there is a synchronous call in the middle (by synchronous I mean the function is written just like a synchronous function, nothing to change).
Coroutines look like synchronous code, but they are still asynchronous because they can be suspended and resumed at yield points. It is still problematic to block without yielding.
Have a look at the examples provided with Boost.Asio.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
On 12/15/18 1:18 AM, Christophe Bailly via Boost wrote:
I have worked on a prototype to avoid the current limitations with async code, the use of sync + async code is really difficult, it does not co-exist easily. If you introduce async code, you have to migrate all your code ... There are alternative solutions (threads..) but here is a suggestion to overcome these limitations.
This the following an accurate description? The user has to inject wait_socket_recv() before any recv() call, which will wait until the socket is readable. The wait conceptually yields the current coroutine, allowing other readable I/O requests to continue. Internally the wait blocks on select(), which waits for any pending I/O requests. Once a socket becomes readable, its associated coroutine is allowed to continue. Unlike Boost.Asio, which requires the user to pass a future or a yield context around in the application code, your approach is to store similiar information in global containers. If that is an accurate description, then the use of global containers clearly presents a scalability issue. Your prototype assumes a single threaded application.
On Tue, Dec 11, 2018, 6:35 PM Gavin Lambert via Boost Christophe, have you looked at Boost.Fiber? That's a library built on
Boost.Context that lets you write synchronous-looking code that makes
asynchronous calls - without having to change calling code. Without reading
through what you posted, it sounds as though that's what you're trying to
accomplish.
The nice thing about Boost.Context is that once you have that, you can
build different APIs on it that can, in various ways, support the control
inversion characteristic of "stackful coroutines."
But if you haven't yet researched Fiber, that library might save you some
work.
participants (4)
-
Bjorn Reese
-
Christophe Bailly
-
Gavin Lambert
-
Nat Goodspeed