Giter VIP home page Giter VIP logo

Comments (8)

Tradias avatar Tradias commented on July 24, 2024 1

Hi,

the comment in the code is in fact accurate. Asio describes the thread-switching behavior in their documentation. Although I find it rather difficult to understand, let me try to explain it in my own words.

Take the following example:

    asio::io_context io_context1;
    asio::steady_timer timer{io_context1};
    asio::io_context io_context2;
    asio::co_spawn(
        io_context2,
        [&]() -> asio::awaitable<void>
        {
            // on io_context2 thread

            // Timer is handled on io_context1 thread
            // But after completion it will:
            // `asio::dispatch(asio::get_associated_executor(use_awaitable_completion_handler,
            // timer.get_executor()), use_awaitable_completion_handler)`
            // which causes the switch back to io_context2 thread
            co_await timer.async_wait(asio::use_awaitable);

            // on io_context2 thread
        },
        asio::detached);
    auto g = asio::make_work_guard(io_context1);
    std::thread t{[&]
                  {
                      io_context1.run();
                  }};
    io_context2.run();
    g.reset();
    t.join();

Asio performs the following steps on this line co_await timer.async_wait(asio::use_awaitable);:

  • Convert the completion token (asio::use_awaitable) into a completion handler using asio::async_result machinery. The details do not matter much, but the result is a callable object (called completion handler) that is to be invoked when the timer completes.
  • Get the I/O executor by calling timer.get_executor() and use that to do whatever is necessary to initiate and wait for the timer (system calls, epoll, etc.)
  • Meanwhile create the completion handler executor by calling asio::get_associated_executor(completion_handler, timer.get_executor()) and establish work-tracking (call asio::make_work_guard()) until the timer completes. use_awaitable's completion handler actually has an associated executor, it is the one used in the call to co_spawn (io_context2).
  • When the timer completes, invoke the completion handler as if by calling asio::dispatch(completion_handler_executor, completion_handler). This is where the thread switching is happening, going from timer.get_executor() (io_context1) to the completion handler executor (io_context2).
  • The completion handler will then resume the coroutine.

Since asio-grpc tries to mimic Asio's behavior as much as possible, replacing asio::steady_timer with agrpc::ServerRPC and io_context with GrpcContext yields the same thread-switching result.

I hope that helps, let me know if something is still unclear.

from asio-grpc.

Tradias avatar Tradias commented on July 24, 2024 1

Correct, the co_spawn call is actually hidden inside agrpc::register_awaitable_rpc_handler. It actually invokes co_spawn like so:

asio::co_spawn(asio::get_associated_executor(completion_handler, first_argument_passed_to_register_awaitable_rpc_handler())

Asio internally always works with executors. There is a co_spawn overload that takes an execution context, like GrpcContext, and essentially forwards the call to co_spawn(grpc_context.get_executor(), ...).

from asio-grpc.

bronfo1 avatar bronfo1 commented on July 24, 2024

Hi,

Thank you for the clear explanation. I tested it and everything worked just as you described. Your guidance made the thread-switching behavior in Asio much clearer.

Also, I want to say that asio-grpc is an amazing project. Really appreciate all the work you've put into it.

Best

from asio-grpc.

Tradias avatar Tradias commented on July 24, 2024

Thanks, glad I could help.

Btw, you can disable the thread-switching this way:

inline void use_inline_awaitable() { return asio::bind_executor(asio::system_executor(), asio::use_awaitable); }


co_await timer.async_wait(use_inline_awaitable());

This will cause async_wait to resume the coroutine on whatever thread the timer's I/O executor is running on.

from asio-grpc.

bronfo1 avatar bronfo1 commented on July 24, 2024

Hi again,

I revisited your example and noticed that after co_await timer.async_wait(), the execution returns to the io_context2 thread, the same thread as before the co_await. So, in the writer code, before co_await rpc.write(), it's in the thread_pool, right? By the same logic, does this mean that after co_await rpc.write(), the execution also returns to the thread_pool?

from asio-grpc.

Tradias avatar Tradias commented on July 24, 2024

It doesn't matter what thread it was on before the co_await, what matters is the first argument passed to asio::co_spawn. By default any co_await ... asio::use_awaitable asynchronous operation started within the co_spawned coroutine will switch back to that first argument (provided the asynchronous operation follows Asio's completion handler executor rules explained earlier). I know this can be a bit unfortunate because the co_spawn call can be far away in some other source file.

from asio-grpc.

bronfo1 avatar bronfo1 commented on July 24, 2024

Great, it's finally clear now. Thank you so much.
Have a good day!

from asio-grpc.

bronfo1 avatar bronfo1 commented on July 24, 2024

writer is (just a little far away) from the co_spawn that first argument is grpc_context or grpc_context's executor, right?

from asio-grpc.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.