Search code examples
c++c++17boost-asioc++pmr

Create an asynchronous post using Asio and custom allocator


I am reading implementation of asio handlers using custom allocators. manual

I am trying to implement my own custom allocator using pmr. But fails in compilation with the following error

prog.cc: In instantiation of
'AsioHandler<Handler>::AsioHandler(Handler&&) [with Handler = main()::<lambda()>]':
prog.cc:77:35:   required from 'AsioHandler<typename std::remove_reference<_Tp>::type> make_custom_alloc_handler(Handler&&) [with Handler = main()::<lambda()>; typename std::remove_reference<_Tp>::type =
main()::<lambda()>]'
prog.cc:84:55:   required from here
prog.cc:69:24: error: cannot convert '<brace-enclosed initializer list>' to 'memory_resource*' in initialization    69 |    
memory_resource  * alloc_{&pool_};
      |                        ^~~~~~
In file included from /opt/wandbox/boost-1.81.0-gcc-12.2.0/include/boost/asio.hpp:52,
                 from prog.cc:13: /opt/wandbox/boost-1.81.0-gcc-12.2.0/include/boost/asio/bind_executor.hpp:
In instantiation of 'boost::asio::detail::executor_binder_base<T, Executor, false>::executor_binder_base(E&&, U&&) [with E = const boost::asio::io_context::basic_executor_type<std::allocator<void>, 0>&; U = AsioHandler<main()::<lambda()> >; T = AsioHandler<main()::<lambda()> >; Executor = boost::asio::io_context::basic_executor_type<std::allocator<void>, 0>]':
/opt/wandbox/boost-1.81.0-gcc-12.2.0/include/boost/asio/bind_executor.hpp:295:46: required from 'boost::asio::executor_binder<T, Executor>::executor_binder(boost::asio::executor_arg_t, const executor_type&, U&&) [with U = AsioHandler<main()::<lambda()> >; T = AsioHandler<main()::<lambda()> >; Executor = boost::asio::io_context::basic_executor_type<std::allocator<void>, 0>; executor_type = boost::asio::io_context::basic_executor_type<std::allocator<void>, 0>]'
/opt/wandbox/boost-1.81.0-gcc-12.2.0/include/boost/asio/bind_executor.hpp:508:10: required from 'boost::asio::executor_binder<typename std::decay<_Tp2>::type, typename ExecutionContext::executor_type> boost::asio::bind_executor(ExecutionContext&, T&&, typename constraint<std::is_convertible<ExecutionContext&, execution_context&>::value>::type) [with ExecutionContext = io_context; T = AsioHandler<main()::<lambda()> >; typename ExecutionContext::executor_type = io_context::basic_executor_type<std::allocator<void>, 0>; typename std::decay<_Tp2>::type = std::decay<AsioHandler<main()::<lambda()> > ::type; typename constraint<std::is_convertible<ExecutionContext&, execution_context&>::value>::type = int]'
prog.cc:86:49:   required from here
/opt/wandbox/boost-1.81.0-gcc-12.2.0/include/boost/asio/bind_executor.hpp:181:7: error: call of overloaded 'AsioHandler(AsioHandler<main()::<lambda()>)' is ambiguous
#include <boost/container/pmr/monotonic_buffer_resource.hpp>
#include  <boost/container/pmr/global_resource.hpp>
#include <cstdlib>
#include <iostream>
#include <thread>
#include <boost/aligned_storage.hpp>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/asio.hpp>
#include <boost/asio/post.hpp>
namespace bc = boost::container;
class memory_resource: public bc::pmr::memory_resource
{
public:
    explicit memory_resource(bc::pmr::memory_resource* up = bc::pmr::get_default_resource())
    :upstream_(up){}

    void* do_allocate(size_t bytes, size_t alignment) override {
        void* ret = upstream_->allocate(bytes, alignment);
        return ret;
    }
    void do_deallocate(void* ptr, size_t bytes, size_t alignment) override {
        upstream_->deallocate(ptr, bytes, alignment);
    }
    bool do_is_equal(const bc::pmr::memory_resource& other) const noexcept override {
        return this == &other;
    }
    
private:
    bc::pmr::memory_resource* upstream_;
};

//template<typename Handler,size_t S = 2048>
constexpr std::size_t S = 2048 ;
template<typename Handler>
class AsioHandler: public Handler
{
public:
    using Handler::operator();
    AsioHandler(Handler && h)
    :h_(std::forward<Handler>(h))
    {

    }
    memory_resource  * get_allocator() const noexcept
    {
        return alloc_;
    }

    friend void* asio_handler_allocate(std::size_t size
                                      ,AsioHandler<Handler>* this_handler)
    {
        return this_handler->alloc_->do_allocate(size,S);
    }

    friend void asio_handler_deallocate(void* pointer, std::size_t size
                                        ,AsioHandler<Handler>* this_handler)
    {
        this_handler->alloc_->do_deallocate(pointer,size,S);
    }
private:
    Handler h_;
    typename std::aligned_storage<S>::type storage_;
    boost::container::pmr::monotonic_buffer_resource pool_{&storage_,S};
    memory_resource  * alloc_{&pool_};
};

// Helper function to wrap a handler object to add custom allocation.
template <typename Handler>
AsioHandler<typename std::remove_reference<Handler>::type> 
make_custom_alloc_handler(Handler && h)
{
  return {std::forward<Handler>(h)};
}

int main()
{
    boost::asio::io_context ctx;
    auto func = [](){};
   auto  h = make_custom_alloc_handler<decltype(func)>(std::move(func));
    
    boost::asio::post(boost::asio::bind_executor(ctx, std::move(h)));
    
    std::thread t {[&ctx]()
    {
        ctx.run();
    }
    };
    t.join();
    
}

How to fix this?


Solution

  • That's... a lot of code. It has a ton of unused headers, unnecessary threads, a duplicated Handler h_ member which is already a base class (!), the invalid forward<> on a non-deduced rvalue reference, missing allocator_type typedef, questionable do_is_equal implementation, using remove_reference_t instead of decay_t, etc.

    It seems like you hardcode the alignment parameter on every do_allocate call to be equal to the S of the the pool... Is this intended?

    Next up, your memory_resource subclass shadows bc::pmr::memory_resource making your code hard to grok. E.g., which did you mean to use for AsioHandler::alloc_? I'd assume your own (let's call it my_memory_resource for now), but the initializer isn't compatible at all. Did you mean my_memory_resource instead of my_memory_resource*?

    Regardless of everything, pool_ being of type bc::pmc::monotonic_buffer_resource makes AsioHandler non-copyable by definition.

    All this combined really makes me scratch my head how you even got the compiler message you posted in the first place.

    I can make your code compile by fixing the non-copyability (making AsioHandler move-only), but I will not vouch for this code to be useful:

    Live On Coliru

    #include <boost/aligned_storage.hpp>
    #include <boost/asio.hpp>
    #include <boost/container/pmr/global_resource.hpp>
    #include <boost/container/pmr/monotonic_buffer_resource.hpp>
    #include <cstdlib>
    #include <iostream>
    // #include <boost/array.hpp>
    // #include <boost/asio/post.hpp>
    // #include <boost/bind/bind.hpp>
    // #include <boost/enable_shared_from_this.hpp>
    // #include <boost/noncopyable.hpp>
    // #include <boost/shared_ptr.hpp>
    namespace bc = boost::container;
    
    class my_memory_resource : public bc::pmr::memory_resource
    {
    public:
        explicit my_memory_resource(
            bc::pmr::memory_resource* up = bc::pmr::get_default_resource())
            : upstream_(up)
        {
        }
    
        void* do_allocate(size_t bytes, size_t alignment) override
        {
            void* ret = upstream_->allocate(bytes, alignment);
            return ret;
        }
        void do_deallocate(
            void* ptr, size_t bytes, size_t alignment) override
        {
            upstream_->deallocate(ptr, bytes, alignment);
        }
        bool do_is_equal(
            bc::pmr::memory_resource const& other) const noexcept override
        {
            return this ==
                &other; // SEHE FIXME https://en.cppreference.com/w/cpp/memory/memory_resource/do_is_equal
        }
    
    private:
        bc::pmr::memory_resource* upstream_;
    };
    
    // template<typename Handler,size_t S = 2048>
    constexpr std::size_t S = 2048;
    template <typename Handler> struct AsioHandler : Handler {
        using Handler::operator();
        explicit AsioHandler(Handler h) : Handler(std::move(h)) {}
    
        my_memory_resource* get_allocator() noexcept { return alloc_.get(); }
    
        friend void* asio_handler_allocate(std::size_t size, AsioHandler<Handler>* h) {
            return h->alloc_->do_allocate(size, S);
        }
    
        friend void asio_handler_deallocate(void* pointer, std::size_t size, AsioHandler<Handler>* h) {
            h->alloc_->do_deallocate(pointer, size, S);
        }
    
    private:
        // Handler h_;
        typename std::aligned_storage<S>::type storage_;
        std::unique_ptr<bc::pmr::monotonic_buffer_resource> pool_ =
            std::make_unique<bc::pmr::monotonic_buffer_resource>(&storage_, S);
        std::unique_ptr<my_memory_resource> alloc_ = std::make_unique<my_memory_resource>(pool_.get());
    };
    
    // Helper function to wrap a handler object to add custom allocation.
    template <typename Handler> AsioHandler<std::decay_t<Handler>> make_custom_alloc_handler(Handler&& h) {
        return AsioHandler<std::decay_t<Handler>>{std::forward<Handler>(h)};
    }
    
    int main()
    {
        boost::asio::io_context ctx;
    
        auto func = []() { std::cout << "SEHE WAS HERE" << std::endl; };
        auto h    = make_custom_alloc_handler<decltype(func)>(std::move(func));
    
        // boost::asio::post(ctx, std::move(h));
        boost::asio::post(boost::asio::bind_executor(ctx, std::move(h)));
    
        ctx.run();
    }
    

    Prints

    SEHE WAS HERE