Boost logo

Boost :

From: Ruben Perez (rubenperez038_at_[hidden])
Date: 2023-09-07 11:31:34


> A more advanced app, and what I would like to see personally, is an example and architectural discussion on design patterns involving how best to handle server requests that require more time/resources that may not be appropriate for a single-threaded server (e.g. a database server.) From a high-level perspective, my current thinking on this is:

I guess you mean any protocol that does not have an async library, or
a resource-intensive task such
as image processing? If there is a specific task or protocol you'd
like to see, please do mention it.
Even if it does not fit in the chat application architecture, we can
always use it as an idea for another
Servertech app.

>
> Handle fast requests in the main single-threaded boost.asio event loop (assuming they don't access resources locked by the next bullet point.)
> Handle longer requests by delegating to a separate thread pool that triggers a callback when done, without blocking the single-threaded event loop. The threads in the separate thread pool do "traditional locking" via mutexes, read/write locks, etc.
>
> Are there more modern approaches/techniques?

I think this goes the right way, but I'd try to encapsulate it in a
class that exposes an async
interface. So let's say your troublesome call is `get_db_customer`,
which is a third party, sync
function that may block for a long time. I'd go for something like:

class db_client
{
    // configure this with the number of threads you want
    boost::asio::thread_pool pool_;
public:
    customer get_customer(boost::asio::yield_context yield)
    {
        // A channel is a structure to communicate between coroutines.
        // concurrent_channel is thread-safe
        boost::asio::experimental::concurrent_channel<void(error_code,
customer)> chan{yield.get_executor()};

        // Whatever function we pass to post() will be run in the thread pool
        boost::asio::post(pool_.get_executor(), [&chan] {
            // This is now running in the thread_pool. Call the sync function
            customer res = sync_get_db_customer();

            // Communicate the result back to the coroutine
            chan.async_send(error_code(), std::move(res),
boost::asio::detached);
        });

        // The coroutine will suspend until the result is ready, allowing
        // other coroutines to make progress
        return chan.async_receive(yield);
    }
};

This way, you don't need locking or callbacks - you can still use the approach I
propose, while retaining efficiency.

It's taken me some time to come up with the code and double-check it was
doing the right thing. I acknowledge that teaching this to newcomers is a real
need. I've raised
https://github.com/anarthal/servertech-chat/issues/44
to incorporate this to the docs, and
https://github.com/anarthal/servertech-chat/issues/43 to actually use this in
the code, if we come up with a feature that needs it and makes sense
in ServerTech code.

Regards,
Ruben.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk