Search code examples
c++memory-managementboost-asioallocation

Can this allocation be avoided with some custom allocator?


I am trying to learn how to use boost::asio together with custom allocators. In the below example I scheduele a timer to be fired on a strand, and the handler is allocated using a custom allocator. For this I have used this example:. But even if I specify a custom allocator there is a call to new with stack trace:

operator new(unsigned long) 0x00007fff2040882a
std::__1::__libcpp_allocate(unsigned long, unsigned long) new:253
std::__1::allocator<std::__1::__shared_ptr_emplace<boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> >, std::__1::allocator<boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> > > > >::allocate(unsigned long) memory:1664
std::__1::enable_if<!(is_array<boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> > >::value), std::__1::shared_ptr<boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> > > >::type std::__1::make_shared<boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> >, boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> > >(boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> >&&) memory:4037
void boost::asio::execution::detail::any_executor_base::construct_object<boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> > >(boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> >&, std::__1::integral_constant<bool, false>) any_executor.hpp:1155
boost::asio::execution::detail::any_executor_base::any_executor_base<boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> > >(boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> >, std::__1::integral_constant<bool, false>) any_executor.hpp:502
boost::asio::execution::any_executor<boost::asio::execution::context_as_t<boost::asio::execution_context&>, boost::asio::execution::detail::blocking::never_t<0>, boost::asio::execution::prefer_only<boost::asio::execution::detail::blocking::possibly_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::tracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::untracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::fork_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::continuation_t<0> > >::any_executor<boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> > >(boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> >, std::__1::enable_if<conditional<(!(is_same<boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> >, boost::asio::execution::any_executor<boost::asio::execution::context_as_t<boost::asio::execution_context&>, boost::asio::execution::detail::blocking::never_t<0>, boost::asio::execution::prefer_only<boost::asio::execution::detail::blocking::possibly_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::tracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::untracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::fork_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::continuation_t<0> > > >::value)) && (!(is_base_of<boost::asio::execution::detail::any_executor_base, boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> > >::value)), boost::asio::execution::detail::is_valid_target_executor<boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> >, void (boost::asio::execution::context_as_t<boost::asio::execution_context&>, boost::asio::execution::detail::blocking::never_t<0>, boost::asio::execution::prefer_only<boost::asio::execution::detail::blocking::possibly_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::tracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::untracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::fork_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::continuation_t<0> >)>, std::__1::integral_constant<bool, false> >::type::value, void>::type*) any_executor.hpp:1406
boost::asio::execution::any_executor<boost::asio::execution::context_as_t<boost::asio::execution_context&>, boost::asio::execution::detail::blocking::never_t<0>, boost::asio::execution::prefer_only<boost::asio::execution::detail::blocking::possibly_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::tracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::untracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::fork_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::continuation_t<0> > >::any_executor<boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> > >(boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> >, std::__1::enable_if<conditional<(!(is_same<boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> >, boost::asio::execution::any_executor<boost::asio::execution::context_as_t<boost::asio::execution_context&>, boost::asio::execution::detail::blocking::never_t<0>, boost::asio::execution::prefer_only<boost::asio::execution::detail::blocking::possibly_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::tracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::untracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::fork_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::continuation_t<0> > > >::value)) && (!(is_base_of<boost::asio::execution::detail::any_executor_base, boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> > >::value)), boost::asio::execution::detail::is_valid_target_executor<boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> >, void (boost::asio::execution::context_as_t<boost::asio::execution_context&>, boost::asio::execution::detail::blocking::never_t<0>, boost::asio::execution::prefer_only<boost::asio::execution::detail::blocking::possibly_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::tracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::untracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::fork_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::continuation_t<0> >)>, std::__1::integral_constant<bool, false> >::type::value, void>::type*) any_executor.hpp:1409
boost::asio::execution::any_executor<boost::asio::execution::context_as_t<boost::asio::execution_context&>, boost::asio::execution::detail::blocking::never_t<0>, boost::asio::execution::prefer_only<boost::asio::execution::detail::blocking::possibly_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::tracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::untracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::fork_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::continuation_t<0> > > boost::asio::execution::detail::any_executor_base::prefer_fn_impl<boost::asio::execution::any_executor<boost::asio::execution::context_as_t<boost::asio::execution_context&>, boost::asio::execution::detail::blocking::never_t<0>, boost::asio::execution::prefer_only<boost::asio::execution::detail::blocking::possibly_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::tracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::untracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::fork_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::continuation_t<0> > >, boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::blocking::possibly_t<0> > >(void const*, void const*, std::__1::enable_if<(!(is_same<boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> >, void>::value)) && (boost::asio::execution::prefer_only<boost::asio::execution::detail::blocking::possibly_t<0> >::is_preferable), void>::type*) any_executor.hpp:1102
boost::asio::execution::any_executor<boost::asio::execution::context_as_t<boost::asio::execution_context&>, boost::asio::execution::detail::blocking::never_t<0>, boost::asio::execution::prefer_only<boost::asio::execution::detail::blocking::possibly_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::tracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::untracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::fork_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::continuation_t<0> > > boost::asio::execution::detail::any_executor_base::prefer_fn<boost::asio::execution::any_executor<boost::asio::execution::context_as_t<boost::asio::execution_context&>, boost::asio::execution::detail::blocking::never_t<0>, boost::asio::execution::prefer_only<boost::asio::execution::detail::blocking::possibly_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::tracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::untracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::fork_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::continuation_t<0> > >, boost::asio::strand<boost::asio::io_context::basic_executor_type<std::__1::allocator<void>, 4u> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::blocking::possibly_t<0> > >(void const*, void const*) any_executor.hpp:1115
boost::asio::execution::any_executor<boost::asio::execution::context_as_t<boost::asio::execution_context&>, boost::asio::execution::detail::blocking::never_t<0>, boost::asio::execution::prefer_only<boost::asio::execution::detail::blocking::possibly_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::tracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::untracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::fork_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::continuation_t<0> > > boost::asio::execution::any_executor<boost::asio::execution::context_as_t<boost::asio::execution_context&>, boost::asio::execution::detail::blocking::never_t<0>, boost::asio::execution::prefer_only<boost::asio::execution::detail::blocking::possibly_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::tracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::untracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::fork_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::continuation_t<0> > >::prefer<boost::asio::execution::detail::blocking::possibly_t<0> >(boost::asio::execution::detail::blocking::possibly_t<0> const&, std::__1::enable_if<find_convertible_preferable_property<boost::asio::execution::detail::blocking::possibly_t<0> >::value, void>::type*) const any_executor.hpp:1682
boost::asio::any_io_executor boost::asio::any_io_executor::prefer<boost::asio::execution::detail::blocking::possibly_t<0> >(boost::asio::execution::detail::blocking::possibly_t<0> const&, boost::asio::constraint<traits::prefer_member<boost::asio::execution::any_executor<boost::asio::execution::context_as_t<boost::asio::execution_context&>, boost::asio::execution::detail::blocking::never_t<0>, boost::asio::execution::prefer_only<boost::asio::execution::detail::blocking::possibly_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::tracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::untracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::fork_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::continuation_t<0> > > const&, boost::asio::execution::detail::blocking::possibly_t<0> const&>::is_valid, int>::type) const any_io_executor.hpp:228
std::__1::enable_if<(call_traits<asio_prefer_fn::impl, boost::asio::any_io_executor&, void (boost::asio::execution::detail::blocking::possibly_t<0> const&)>::overload) == ((asio_prefer_fn::overload_type)3), asio_prefer_fn::call_traits<asio_prefer_fn::impl, boost::asio::any_io_executor&, void (boost::asio::execution::detail::blocking::possibly_t<0> const&), void, void, void, void, void, void, void>::result_type>::type asio_prefer_fn::impl::operator()<boost::asio::any_io_executor&, boost::asio::execution::detail::blocking::possibly_t<0> const&>(boost::asio::any_io_executor&, boost::asio::execution::detail::blocking::possibly_t<0> const&) const prefer.hpp:496
void boost::asio::detail::handler_work_base<boost::asio::any_io_executor, void, boost::asio::io_context, boost::asio::executor, void>::dispatch<boost::asio::detail::binder1<CustomAllocationHandler<boost::_bi::bind_t<void, boost::_mfi::mf1<void, AsioTimer<main::$_0>::HandlerWrapper, boost::system::error_code const&>, boost::_bi::list2<boost::_bi::value<boost::shared_ptr<AsioTimer<main::$_0>::HandlerWrapper> >, boost::arg<1> (*)()> >, BucketPool<128ul> >, boost::system::error_code>, CustomAllocationHandler<boost::_bi::bind_t<void, boost::_mfi::mf1<void, AsioTimer<main::$_0>::HandlerWrapper, boost::system::error_code const&>, boost::_bi::list2<boost::_bi::value<boost::shared_ptr<AsioTimer<main::$_0>::HandlerWrapper> >, boost::arg<1> (*)()> >, BucketPool<128ul> > >(boost::asio::detail::binder1<CustomAllocationHandler<boost::_bi::bind_t<void, boost::_mfi::mf1<void, AsioTimer<main::$_0>::HandlerWrapper, boost::system::error_code const&>, boost::_bi::list2<boost::_bi::value<boost::shared_ptr<AsioTimer<main::$_0>::HandlerWrapper> >, boost::arg<1> (*)()> >, BucketPool<128ul> >, boost::system::error_code>&, CustomAllocationHandler<boost::_bi::bind_t<void, boost::_mfi::mf1<void, AsioTimer<main::$_0>::HandlerWrapper, boost::system::error_code const&>, boost::_bi::list2<boost::_bi::value<boost::shared_ptr<AsioTimer<main::$_0>::HandlerWrapper> >, boost::arg<1> (*)()> >, BucketPool<128ul> >&) handler_work.hpp:430
void boost::asio::detail::handler_work<CustomAllocationHandler<boost::_bi::bind_t<void, boost::_mfi::mf1<void, AsioTimer<main::$_0>::HandlerWrapper, boost::system::error_code const&>, boost::_bi::list2<boost::_bi::value<boost::shared_ptr<AsioTimer<main::$_0>::HandlerWrapper> >, boost::arg<1> (*)()> >, BucketPool<128ul> >, boost::asio::any_io_executor, void>::complete<boost::asio::detail::binder1<CustomAllocationHandler<boost::_bi::bind_t<void, boost::_mfi::mf1<void, AsioTimer<main::$_0>::HandlerWrapper, boost::system::error_code const&>, boost::_bi::list2<boost::_bi::value<boost::shared_ptr<AsioTimer<main::$_0>::HandlerWrapper> >, boost::arg<1> (*)()> >, BucketPool<128ul> >, boost::system::error_code> >(boost::asio::detail::binder1<CustomAllocationHandler<boost::_bi::bind_t<void, boost::_mfi::mf1<void, AsioTimer<main::$_0>::HandlerWrapper, boost::system::error_code const&>, boost::_bi::list2<boost::_bi::value<boost::shared_ptr<AsioTimer<main::$_0>::HandlerWrapper> >, boost::arg<1> (*)()> >, BucketPool<128ul> >, boost::system::error_code>&, CustomAllocationHandler<boost::_bi::bind_t<void, boost::_mfi::mf1<void, AsioTimer<main::$_0>::HandlerWrapper, boost::system::error_code const&>, boost::_bi::list2<boost::_bi::value<boost::shared_ptr<AsioTimer<main::$_0>::HandlerWrapper> >, boost::arg<1> (*)()> >, BucketPool<128ul> >&) handler_work.hpp:505
boost::asio::detail::wait_handler<CustomAllocationHandler<boost::_bi::bind_t<void, boost::_mfi::mf1<void, AsioTimer<main::$_0>::HandlerWrapper, boost::system::error_code const&>, boost::_bi::list2<boost::_bi::value<boost::shared_ptr<AsioTimer<main::$_0>::HandlerWrapper> >, boost::arg<1> (*)()> >, BucketPool<128ul> >, boost::asio::any_io_executor>::do_complete(void*, boost::asio::detail::scheduler_operation*, boost::system::error_code const&, unsigned long) wait_handler.hpp:76
boost::asio::detail::scheduler_operation::complete(void*, boost::system::error_code const&, unsigned long) scheduler_operation.hpp:40
boost::asio::detail::scheduler::do_run_one(boost::asio::detail::conditionally_enabled_mutex::scoped_lock&, boost::asio::detail::scheduler_thread_info&, boost::system::error_code const&) scheduler.ipp:486
boost::asio::detail::scheduler::run(boost::system::error_code&) scheduler.ipp:204
boost::asio::io_context::run() io_context.ipp:63
main::$_1::operator()() const strand_demo.cpp:190
decltype(std::__1::forward<main::$_1>(fp)()) std::__1::__invoke<main::$_1>(main::$_1&&) type_traits:3747
void std::__1::__async_func<main::$_1>::__execute<>(std::__1::__tuple_indices<>) future:2186
std::__1::__async_func<main::$_1>::operator()() future:2179
std::__1::__async_assoc_state<void, std::__1::__async_func<main::$_1> >::__execute() future:997
decltype(*(std::__1::forward<std::__1::__async_assoc_state<void, std::__1::__async_func<main::$_1> >*>(fp0)).*fp()) std::__1::__invoke<void (std::__1::__async_assoc_state<void, std::__1::__async_func<main::$_1> >::*)(), std::__1::__async_assoc_state<void, std::__1::__async_func<main::$_1> >*, void>(void (std::__1::__async_assoc_state<void, std::__1::__async_func<main::$_1> >::*&&)(), std::__1::__async_assoc_state<void, std::__1::__async_func<main::$_1> >*&&) type_traits:3688
void std::__1::__thread_execute<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, void (std::__1::__async_assoc_state<void, std::__1::__async_func<main::$_1> >::*)(), std::__1::__async_assoc_state<void, std::__1::__async_func<main::$_1> >*, 2ul>(std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, void (std::__1::__async_assoc_state<void, std::__1::__async_func<main::$_1> >::*)(), std::__1::__async_assoc_state<void, std::__1::__async_func<main::$_1> >*>&, std::__1::__tuple_indices<2ul>) thread:280
void* std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, void (std::__1::__async_assoc_state<void, std::__1::__async_func<main::$_1> >::*)(), std::__1::__async_assoc_state<void, std::__1::__async_func<main::$_1> >*> >(void*) thread:291
_pthread_start 0x00007fff204438fc
thread_start 0x00007fff2043f443

Code:

#include <chrono>
#include <iostream>
#include <utility>

#include "boost/asio.hpp"
#include <boost/pool/pool.hpp>
#include <boost/make_shared.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/bind/bind.hpp>

namespace details {

template <size_t SIZE, size_t... REST>
class Bucket;

template <size_t SIZE>
class Bucket<SIZE> {

public:
  Bucket() = default;

protected:
  void* do_alloc(const std::size_t numElements) {
    assert(numElements == 1);
    return allocator_.malloc();
  }

  void do_dealloc(void* const ptr, const std::size_t numElements) {
    assert(numElements == 1);
    allocator_.free(ptr, numElements);
  }

private:
  boost::pool<boost::default_user_allocator_new_delete> allocator_{SIZE};
};

template <size_t SIZE, size_t... REST>
class Bucket
  : public Bucket<SIZE>
  , public Bucket<REST>... {};

}  // namespace details

template <size_t SIZE, size_t... REST>
class BucketPool : public details::Bucket<SIZE, REST...> {
public:
  template <size_t S>
  void* alloc(const std::size_t numElements) {
    return details::Bucket<S>::do_alloc(numElements);
  }

  template <size_t S>
  void dealloc(void* const ptr, const std::size_t numElements) {
    assert(numElements == 1);
    details::Bucket<S>::do_dealloc(ptr, numElements);
  }
};


using strand_t = boost::asio::strand<boost::asio::io_context::executor_type>;

template <typename T, typename PoolType>
class ObjectAllocator {
public:
  using value_type = T;

  explicit ObjectAllocator(PoolType& bucketPool) : bucketPool_(bucketPool) {}

  template <typename U, typename K>
  explicit ObjectAllocator(const ObjectAllocator<U, K>& other)
  : bucketPool_(other.bucketPool_) {}

  bool operator==(const ObjectAllocator& lhs) const noexcept {
    return bucketPool_ == lhs.bucketPool_;
  }

  bool operator!=(const ObjectAllocator& lhs) const noexcept {
    return bucketPool_ != lhs.bucketPool_;
  }

  T* allocate(const std::size_t numElements) const {
    return static_cast<T*>(bucketPool_.template alloc<sizeof(T)>(numElements));
  }

  void deallocate(T* const ptr, const std::size_t numElements) const {
    bucketPool_.template dealloc<sizeof(T)>(ptr, numElements);
  }

private:
  template <typename, typename>
  friend class ObjectAllocator;

  PoolType& bucketPool_;
};

template <typename HandlerT>
class AsioTimer {

  class HandlerWrapper : public boost::enable_shared_from_this<HandlerWrapper> {
  public:
    HandlerWrapper(strand_t strand, HandlerT handler)
    : timer_(strand), handler_(handler), milliseconds_(0) {}

    void startTimer(const std::chrono::milliseconds& everyMilliseconds) {
      milliseconds_ = everyMilliseconds;
      timer_.expires_from_now(everyMilliseconds);
      startAsyncWait();
    }

  private:
    void startAsyncWait() {
      timer_.async_wait(MakeCustomAllocationHandler(
        memory_,
        boost::bind(&HandlerWrapper::handleTimerCallback, this->shared_from_this(),
                    boost::asio::placeholders::error)));
    }

    void handleTimerCallback(const boost::system::error_code& e) {
      if (e != boost::asio::error::operation_aborted) {
        handler_();
      }
      timer_.expires_at(timer_.expires_at() + milliseconds_);
      startAsyncWait();
    }

    BucketPool<128> memory_;
    boost::asio::steady_timer timer_;
    HandlerT handler_;
    std::chrono::milliseconds milliseconds_;
  };

public:
  AsioTimer(strand_t strand, HandlerT handler)
  : handlerWrapper_(boost::make_shared<HandlerWrapper>(strand, handler)) {}

  void startTimer(const std::chrono::milliseconds& everyMilliseconds) {
    handlerWrapper_->startTimer(everyMilliseconds);
  }

private:
  boost::shared_ptr<HandlerWrapper> handlerWrapper_;
};

template <typename HandlerT, typename PoolT>
class CustomAllocationHandler {
public:
  using allocator_type = ObjectAllocator<HandlerT, PoolT>;

  CustomAllocationHandler(PoolT& memory, HandlerT handler)
  : memory_(memory), handler_(std::move(handler)) {}

  allocator_type get_allocator() const noexcept {
    return allocator_type(memory_);
  }

  template <typename... Args>
  void operator()(Args&&... args) {
    handler_(std::forward<Args>(args)...);
  }

private:
  PoolT& memory_;
  HandlerT handler_;
};

template <typename HandlerT, typename PoolT>
CustomAllocationHandler<HandlerT, PoolT> MakeCustomAllocationHandler(PoolT& memory,
                                                                     HandlerT handler) {
  return CustomAllocationHandler<HandlerT, PoolT>(memory, std::move(handler));
}


int main() {
  boost::asio::io_context ioContext;

  strand_t myStrand(make_strand(ioContext));

  AsioTimer timer(myStrand, [] { std::cout << "timer called" << std::endl; });
  timer.startTimer(std::chrono::milliseconds(20));

  auto fut = std::async([&ioContext] {
    ioContext.run();
  });

  std::this_thread::sleep_for(std::chrono::seconds(1));
  ioContext.stop();
  fut.get();
}

Is there some way to avoid this call to new while calling ioContext::run()?


Solution

  • Your code is using type-erased executors. It's effectively operating on the asio::basic_stream_socket<asio::ip::tcp, asio::any_io_executor>, this is causing overhead (allocation and refcounting).

    To remove the allocations involved, statically type your executor. Keep in mind it makes your code less flexible, so maybe you need to template on the actual executor type if you want to be using it with different executors (e.g. thread_pool vs io_context vs a strand<> of either).

    So e.g.

    using executor_t = boost::asio::io_context::executor_type;
    using socket_t   = boost::asio::basic_stream_socket<tcp, executor_t>;
    using acceptor_t = boost::asio::basic_socket_acceptor<tcp, executor_t>;
    

    Note that this does in practice often increase performance of Asio networking by quite a bit!

    Live Demo

    For fun I made it reverse the text before echo. Output "dlrow olleH"

    #include <array>
    #include <boost/asio.hpp>
    #include <cstdlib>
    #include <iostream>
    #include <memory>
    #include <type_traits>
    #include <utility>
    
    using boost::asio::ip::tcp;
    
    // Class to manage the memory to be used for handler-based custom allocation.
    // It contains a single block of memory which may be returned for allocation
    // requests. If the memory is in use when an allocation request is made, the
    // allocator delegates allocation to the global heap.
    class handler_memory {
      public:
        handler_memory() : in_use_(false)
        {
        }
    
        handler_memory(const handler_memory&) = delete;
        handler_memory& operator=(const handler_memory&) = delete;
    
        void* allocate(std::size_t size)
        {
            if (!in_use_ && size < sizeof(storage_)) {
                in_use_ = true;
                return &storage_;
            } else {
                return ::operator new(size);
            }
        }
    
        void deallocate(void* pointer)
        {
            if (pointer == &storage_) {
                in_use_ = false;
            } else {
                ::operator delete(pointer);
            }
        }
    
      private:
        // Storage space used for handler-based custom memory allocation.
        typename std::aligned_storage<1024>::type storage_;
    
        // Whether the handler-based custom allocation storage has been used.
        bool in_use_;
    };
    
    // The allocator to be associated with the handler objects. This allocator only
    // needs to satisfy the C++11 minimal allocator requirements.
    template <typename T> class handler_allocator {
      public:
        using value_type = T;
    
        explicit handler_allocator(handler_memory& mem) : memory_(mem)
        {
        }
    
        template <typename U>
        handler_allocator(const handler_allocator<U>& other) noexcept
            : memory_(other.memory_)
        {
        }
    
        bool operator==(const handler_allocator& other) const noexcept
        {
            return &memory_ == &other.memory_;
        }
    
        bool operator!=(const handler_allocator& other) const noexcept
        {
            return &memory_ != &other.memory_;
        }
    
        T* allocate(std::size_t n) const
        {
            return static_cast<T*>(memory_.allocate(sizeof(T) * n));
        }
    
        void deallocate(T* p, std::size_t /*n*/) const
        {
            return memory_.deallocate(p);
        }
    
      private:
        template <typename> friend class handler_allocator;
    
        // The underlying memory.
        handler_memory& memory_;
    };
    
    // Wrapper class template for handler objects to allow handler memory
    // allocation to be customised. The allocator_type type and get_allocator()
    // member function are used by the asynchronous operations to obtain the
    // allocator. Calls to operator() are forwarded to the encapsulated handler.
    template <typename Handler> class custom_alloc_handler {
      public:
        using allocator_type = handler_allocator<Handler>;
    
        custom_alloc_handler(handler_memory& m, Handler h) : memory_(m), handler_(h)
        {
        }
    
        allocator_type get_allocator() const noexcept
        {
            return allocator_type(memory_);
        }
    
        template <typename... Args> void operator()(Args&&... args)
        {
            handler_(std::forward<Args>(args)...);
        }
    
      private:
        handler_memory& memory_;
        Handler         handler_;
    };
    
    // Helper function to wrap a handler object to add custom allocation.
    template <typename Handler>
    inline custom_alloc_handler<Handler>
    make_custom_alloc_handler(handler_memory& m, Handler h)
    {
      return custom_alloc_handler<Handler>(m, h);
    }
    
    using executor_t = boost::asio::io_context::executor_type;
    using socket_t   = boost::asio::basic_stream_socket<tcp, executor_t>;
    using acceptor_t = boost::asio::basic_socket_acceptor<tcp, executor_t>;
    
    class session : public std::enable_shared_from_this<session> {
      public:
        session(socket_t socket) : socket_(std::move(socket))
        {
        }
    
        void start()
        {
            do_read();
        }
    
      private:
        void do_read()
        {
            auto self(shared_from_this());
            socket_.async_read_some(
                boost::asio::buffer(data_),
                make_custom_alloc_handler(
                    handler_memory_,
                    [this, self](boost::system::error_code ec, std::size_t length) {
                        if (!ec) {
                            do_write(length);
                        }
                    }));
        }
    
        void do_write(std::size_t length)
        {
            auto self(shared_from_this());
    
            if (length > 0) { // just for fun
                std::reverse(data_.begin(), data_.begin() + length - 1);
            }
    
            boost::asio::async_write(
                socket_, boost::asio::buffer(data_, length),
                make_custom_alloc_handler(handler_memory_,
                                          [this, self](boost::system::error_code ec,
                                                       std::size_t /*length*/) {
                                              if (!ec) {
                                                  do_read();
                                              }
                                          }));
        }
    
        // The socket used to communicate with the client.
        socket_t socket_;
    
        // Buffer used to store data received from the client.
        std::array<char, 1024> data_;
    
        // The memory to use for handler-based custom memory allocation.
        handler_memory handler_memory_;
    };
    
    class server {
      public:
        server(boost::asio::io_context& io_context, short port)
            : acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
        {
            do_accept();
        }
    
      private:
        void do_accept()
        {
            acceptor_.async_accept(
                [this](boost::system::error_code ec, socket_t socket) {
                    if (!ec) {
                        std::make_shared<session>(std::move(socket))->start();
                    }
    
                    do_accept();
                });
        }
    
        acceptor_t acceptor_;
    };
    
    int main(int argc, char* argv[])
    {
        try {
            if (argc != 2) {
                std::cerr << "Usage: server <port>\n";
                return 1;
            }
    
            boost::asio::io_context io_context;
            server                  s(io_context, std::atoi(argv[1]));
            using namespace std::chrono_literals;
            io_context.run_for(3s);
            return 0;
        } catch (std::exception& e) {
            std::cerr << "Exception: " << e.what() << "\n";
            return 2;
        }
    }
    

    BONUS

    Using C++17 CTAD, very few changes are required to template the entire server on executors.

    Note also that it passes executors by value, decoupling the execution context from the service objects more.

    Live On Coliru

    #include <array>
    #include <boost/asio.hpp>
    #include <cstdlib>
    #include <iostream>
    #include <memory>
    #include <type_traits>
    #include <utility>
    
    using boost::asio::ip::tcp;
    
    // Class to manage the memory to be used for handler-based custom allocation.
    // It contains a single block of memory which may be returned for allocation
    // requests. If the memory is in use when an allocation request is made, the
    // allocator delegates allocation to the global heap.
    class handler_memory {
      public:
        handler_memory() : in_use_(false)
        {
        }
    
        handler_memory(const handler_memory&) = delete;
        handler_memory& operator=(const handler_memory&) = delete;
    
        void* allocate(std::size_t size)
        {
            if (!in_use_ && size < sizeof(storage_)) {
                in_use_ = true;
                return &storage_;
            } else {
                return ::operator new(size);
            }
        }
    
        void deallocate(void* pointer)
        {
            if (pointer == &storage_) {
                in_use_ = false;
            } else {
                ::operator delete(pointer);
            }
        }
    
      private:
        // Storage space used for handler-based custom memory allocation.
        typename std::aligned_storage<1024>::type storage_;
    
        // Whether the handler-based custom allocation storage has been used.
        bool in_use_;
    };
    
    // The allocator to be associated with the handler objects. This allocator only
    // needs to satisfy the C++11 minimal allocator requirements.
    template <typename T> class handler_allocator {
      public:
        using value_type = T;
    
        explicit handler_allocator(handler_memory& mem) : memory_(mem)
        {
        }
    
        template <typename U>
        handler_allocator(const handler_allocator<U>& other) noexcept
            : memory_(other.memory_)
        {
        }
    
        bool operator==(const handler_allocator& other) const noexcept
        {
            return &memory_ == &other.memory_;
        }
    
        bool operator!=(const handler_allocator& other) const noexcept
        {
            return &memory_ != &other.memory_;
        }
    
        T* allocate(std::size_t n) const
        {
            return static_cast<T*>(memory_.allocate(sizeof(T) * n));
        }
    
        void deallocate(T* p, std::size_t /*n*/) const
        {
            return memory_.deallocate(p);
        }
    
      private:
        template <typename> friend class handler_allocator;
    
        // The underlying memory.
        handler_memory& memory_;
    };
    
    // Wrapper class template for handler objects to allow handler memory
    // allocation to be customised. The allocator_type type and get_allocator()
    // member function are used by the asynchronous operations to obtain the
    // allocator. Calls to operator() are forwarded to the encapsulated handler.
    template <typename Handler> class custom_alloc_handler {
      public:
        using allocator_type = handler_allocator<Handler>;
    
        custom_alloc_handler(handler_memory& m, Handler h) : memory_(m), handler_(h)
        {
        }
    
        allocator_type get_allocator() const noexcept
        {
            return allocator_type(memory_);
        }
    
        template <typename... Args> void operator()(Args&&... args)
        {
            handler_(std::forward<Args>(args)...);
        }
    
      private:
        handler_memory& memory_;
        Handler         handler_;
    };
    
    // Helper function to wrap a handler object to add custom allocation.
    template <typename Handler>
    inline custom_alloc_handler<Handler>
    make_custom_alloc_handler(handler_memory& m, Handler h)
    {
      return custom_alloc_handler<Handler>(m, h);
    }
    
    
    template <typename Executor>
    class server {
        using executor_t = Executor;
        using socket_t   = boost::asio::basic_stream_socket<tcp, executor_t>;
        using acceptor_t = boost::asio::basic_socket_acceptor<tcp, executor_t>;
    
        class session : public std::enable_shared_from_this<session> {
          public:
            session(socket_t socket) : socket_(std::move(socket))
            {
            }
    
            void start()
            {
                do_read();
            }
    
          private:
            void do_read()
            {
                auto self(this->shared_from_this());
                socket_.async_read_some(
                    boost::asio::buffer(data_),
                    make_custom_alloc_handler(
                        handler_memory_,
                        [this, self](boost::system::error_code ec,
                                     std::size_t               length) {
                            if (!ec) {
                                do_write(length);
                            }
                        }));
            }
    
            void do_write(std::size_t length)
            {
                auto self(this->shared_from_this());
    
                if (length > 0) { // just for fun
                    std::reverse(data_.begin(), data_.begin() + length - 1);
                }
    
                boost::asio::async_write(
                    socket_, boost::asio::buffer(data_, length),
                    make_custom_alloc_handler(
                        handler_memory_,
                        [this, self](boost::system::error_code ec,
                                     std::size_t /*length*/) {
                            if (!ec) {
                                do_read();
                            }
                        }));
            }
    
            // The socket used to communicate with the client.
            socket_t socket_;
    
            // Buffer used to store data received from the client.
            std::array<char, 1024> data_;
    
            // The memory to use for handler-based custom memory allocation.
            handler_memory handler_memory_;
        };
    
      public:
        server(Executor executor, short port)
            : acceptor_(executor, tcp::endpoint(tcp::v4(), port))
        {
            do_accept();
        }
    
      private:
        void do_accept()
        {
            acceptor_.async_accept(
                [this](boost::system::error_code ec, socket_t socket) {
                    if (!ec) {
                        std::make_shared<session>(std::move(socket))->start();
                    }
    
                    do_accept();
                });
        }
    
        acceptor_t acceptor_;
    };
    
    int main(int argc, char* argv[])
    {
        try {
            if (argc != 2) {
                std::cerr << "Usage: server <port>\n";
                return 1;
            }
    
            boost::asio::io_context io_context;
            server s(io_context.get_executor(), std::atoi(argv[1]));
    
            using namespace std::chrono_literals;
            io_context.run_for(3s);
            return 0;
        } catch (std::exception& e) {
            std::cerr << "Exception: " << e.what() << "\n";
            return 2;
        }
    }