Как получить boost:: asio:: io_service текущий номер действия

Boost:: asio:: io_service обеспечивает "отслеживание обработчика" для целей отладки, он активируется путем определения BOOST_ASIO_ENABLE_HANDLER_TRACKING, но записывает свои данные в stderr. Я хотел бы использовать эту информацию для отслеживания в своем приложении. Мой вопрос - это лучший способ получить доступ к <action> внутри моего приложения?

Более подробно о том, почему я хочу это сделать; Я хотел бы добавить <action> в качестве параметра для других операций async, чтобы я мог отслеживать, откуда пришел исходный запрос.

Ответ 1

Пример, который имитирует отслеживание обработчика отладки asio. Предостережения:

  • Предполагается, что ioService запускается только из одного потока. Я никогда не использую других способов, поэтому я не уверен, что нужно изменить, чтобы исправить это ограничение.
  • Непоточный безопасный доступ к std::cerr - исправление этого влево как упражнение.

Код

#include <boost/asio.hpp>
#include <boost/atomic.hpp>

#include <iostream>

class HandlerTracking
{
public:

    HandlerTracking()
        :
        mCount(1)
    { }


    template <class Handler>
    class WrappedHandler
    {
    public:

        WrappedHandler(HandlerTracking& t, Handler h, std::uint64_t id) 
            : 
            mHandlerTracking(t), 
            mHandler(h),
            mId(id)
        { }

        WrappedHandler(const WrappedHandler& other)
            :
            mHandlerTracking(other.mHandlerTracking),
            mHandler(other.mHandler),
            mId(other.mId),
            mInvoked(other.mInvoked)
        {
            other.mInvoked = true;
        }

        ~WrappedHandler()
        {
            if (!mInvoked)
                std::cerr << '~' << mId << std::endl;
        }

        template <class... Args>
        void operator()(Args... args)
        {
            mHandlerTracking.mCurrHandler = mId;
            std::cerr << '>' << mId << std::endl;

            try
            {
                mInvoked = true;
                mHandler(args...); 
            }
            catch(...)
            {
                std::cerr << '!' << mId << std::endl;
                throw;
            }
            std::cerr << '<' << mId << std::endl;
        }

        const std::uint64_t id() { return mId; }

    private:

        HandlerTracking& mHandlerTracking;
        Handler mHandler;
        const std::uint64_t mId;
        mutable bool mInvoked = false;
    };

    template <class Handler>
    WrappedHandler<Handler> wrap(Handler handler) 
    {
        auto next = mCount.fetch_add(1);
        std::cerr << mCurrHandler << '*' << next << std::endl;
        return WrappedHandler<Handler>(*this, handler, next);
    }

    boost::atomic<std::uint64_t> mCount;
    std::uint64_t mCurrHandler = 0;           // Note: If ioService run on multiple threads we need a curr handler per thread
};


// Custom invokation hook for wrapped handlers
//template <typename Function, typename Handler>
//void asio_handler_invoke(Function f, HandlerTracking::WrappedHandler<Handler>* h)
//{
//    std::cerr << "Context: " << h << ", " << h->id() << ", " << f.id() << std::endl;
//    f();
//}


// Class to demonstrate callback with arguments
class MockSocket
{
public:

    MockSocket(boost::asio::io_service& ioService) : mIoService(ioService) {}

    template <class Handler>
    void async_read(Handler h)
    {
        mIoService.post([h]() mutable { h(42); }); // we always read 42 bytes
    }

private:
    boost::asio::io_service& mIoService;
};

int main(int argc, char* argv[])
{
    boost::asio::io_service ioService;
    HandlerTracking tracking;

    MockSocket socket(ioService);

    std::function<void()> f1 = [&]() { std::cout << "Handler1" << std::endl; };
    std::function<void()> f2 = [&]() { std::cout << "Handler2" << std::endl; ioService.post(tracking.wrap(f1)); };
    std::function<void()> f3 = [&]() { std::cout << "Handler3" << std::endl; ioService.post(tracking.wrap(f2)); };
    std::function<void()> f4 = [&]() { std::cout << "Handler4" << std::endl; ioService.post(tracking.wrap(f3)); };

    std::function<void(int)> s1 = [](int s) { std::cout << "Socket read " << s << " bytes" << std::endl; };

    socket.async_read(tracking.wrap(s1)); 

    ioService.post(tracking.wrap(f1));
    ioService.post(tracking.wrap(f2));
    ioService.post(tracking.wrap(f3));
    auto tmp = tracking.wrap(f4);  // example handler destroyed without invocation

    ioService.run();



    return 0;
}

Выход

0*1
0*2
0*3
0*4
0*5
>1
Socket read 42 bytes
<1
>2
Handler1
<2
>3
Handler2
3*6
<3
>4
Handler3
4*7
<4
>6
Handler1
<6
>7
Handler2
7*8
<7
>8
Handler1
<8
~5

Ответ 2

Asio не предоставляет данные отслеживания обработчика. Попытка извлечь информацию отслеживания, содержащуюся в Asio, была бы гораздо более грязной, чем катящийся собственный пользовательский обработчик.

Вот фрагмент от Asio отслеживание обработчика:

namespace boost {
namespace asio {
namespace detail {

class handler_tracking
{
public:
  class completion;

  // Base class for objects containing tracked handlers.
  class tracked_handler
  {
  private:
    // Only the handler_tracking class will have access to the id.
    friend class handler_tracking;
    friend class completion;
    uint64_t id_;

  // ...

  private:
    friend class handler_tracking;
    uint64_t id_;
    bool invoked_;
    completion* next_;
  };

// ...

private:
  struct tracking_state;
  static tracking_state* get_state();
};

} // namespace detail
} // namespace asio
} // namespace boost

Как уже упоминалось, передача GUID во всех обработчиках позволит связать несколько асинхронных операций. Один неинтрузивный способ сделать это - создать собственный тип обработчика отслеживания, который обертывает существующие обработчики и управляет данными отслеживания. Пример для пользовательских обработчиков см. В примере Boost.Asio Invocation.

Кроме того, имейте в виду, что если используется тип пользовательского обработчика, при составлении обработчиков нужно быть очень осторожным. В частности, привязка вызова типа пользовательского обработчика (asio_handler_invoke()) может потребовать учета контекста других обработчиков. Например, если явным образом не учитываю завершенный обработчик, возвращенный из strand::wrap(), он предотвратит запуск промежуточных операций в правильном контексте для сложенные операции. Чтобы избежать необходимости явно обрабатывать это, можно поместить пользовательский обработчик strand::wrap():

boost::asio::async_read(..., strand.wrap(tracker.wrap(&handle_read))); // Good.
boost::asio::async_read(..., tracker.wrap(strand.wrap(&handle_read))); // Bad.