Skip to content

Instantly share code, notes, and snippets.

@tsaarni
Last active August 13, 2021 17:39
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save tsaarni/bb0b8d1ca33e3a1bfea1 to your computer and use it in GitHub Desktop.
Save tsaarni/bb0b8d1ca33e3a1bfea1 to your computer and use it in GitHub Desktop.
boost::asio with handler callback as member function, guarded by weak_ptr

Using member function as boost::asio handler

This example demonstrates how to implement boost::asio handler callback as a member functions in a safe way, even while the ASIO timer or socket object is itself also contained in the same object.

Problem

When deleting the object containing both the handler method and ASIO timer (or socket object), one last call is triggered by ASIO to the handler. For example in case of timer, the handler will be called with error code Operation cancelled. However at this point of time the object has already been destroyed, leading to access of memory that was already freed.

Solution

The example uses shared_from_this(), together with weak_ptr to guard the call to the handler method. Non-member function is registered as a "proxy" handler, that will first check the weak_ptr status, if it is expired (i.e. the referred object has been deleted) the call to actual handler function is skipped.

Example code

The first example 01-asio-timer-with-weak-ptr.cpp implements handler callback guard as lambda function inside arm() method.

The second example 02-asio-timer-with-weak-ptr.cpp implements handler callback guard in a more generic way as weak_callback() template function.

//
// This example implements handler callback guard as lambda function inside arm() method.
//
// compile with
// g++ -std=c++11 -g -Wall 01-asio-timer-with-weak-ptr.cpp -lboost_system -o 01-asio-timer-with-weak-ptr
// clang++ -std=c++11 -g -Wall 01-asio-timer-with-weak-ptr.cpp -lboost_system -o 01-asio-timer-with-weak-ptr
//
// run:
// ./01-asio-timer-with-weak-ptr
// valgrind --leak-check=full --show-leak-kinds=all ./01-asio-timer-with-weak-ptr
//
// output:
// my_timer_task::my_timer_task()
// my_timer_task::arm()
// running lambda
// my_timer_task::arm()
// my_timer_task::timer_expired_callback() 1
// running lambda
// my_timer_task::arm()
// my_timer_task::timer_expired_callback() 2
// DELETING TIMER OBJECT
// my_timer_task::~my_timer_task()
// running lambda
// timer ec: Operation canceled
//
#include <iostream>
#include <functional>
#include <memory>
#include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>
#include <chrono>
class my_timer_task;
static std::shared_ptr<my_timer_task> task;
class my_timer_task : public std::enable_shared_from_this<my_timer_task>
{
public:
my_timer_task(boost::asio::io_service& io_service)
: timer_(io_service), counter_(0)
{
std::cout << "my_timer_task::my_timer_task()" << std::endl;;
}
~my_timer_task()
{
std::cout << "my_timer_task::~my_timer_task()" << std::endl;
}
void arm()
{
std::cout << "my_timer_task::arm()" << std::endl;
// weak_ptr is used as guard:
// - if weak_ptr is not expired, call timer_expired_callback() member method with valid 'this'
// - if weak_ptr is expired, skip calling member method since 'this' is not valid anymore
std::weak_ptr<my_timer_task> self(shared_from_this());
timer_.expires_from_now(std::chrono::seconds(1));
timer_.async_wait(
[this, self](const boost::system::error_code& ec)
{
std::cout << "running lambda" << std::endl;
// check the 'self' weak_ptr to check if it is safe to use 'this'
if (!ec)
{
if (auto s = self.lock())
{
// timer object alive -> safe to use 'this'
arm(); // re-arm the timer
timer_expired_callback();
}
}
// timer object not alive anymore -> do not use 'this'
if (ec)
{
std::cout << "timer ec: " << ec.message() << std::endl;
}
});
}
void timer_expired_callback()
{
std::cout << "my_timer_task::timer_expired_callback() " << ++counter_ << std::endl;;
// Here we demonstrate deleting the timer object. This would be normally
// done from outside e.g. when 'my_timer_task' falls out of scope (RAII)
if (counter_ == 2)
{
std::cout << "DELETING TIMER OBJECT" << std::endl;
task.reset();
}
}
private:
boost::asio::steady_timer timer_;
unsigned int counter_;
};
int
main(int argc, char *argv[])
{
boost::asio::io_service io_service;
// In this example shared_ptr must be used for the timer object,
// since that enables using shared_from_this(). Note also, that
// shared_from_this() cannot be called directly from constructor,
// since shared_ptr has been created at that time yet.
task = std::make_shared<my_timer_task>(io_service);
task->arm();
io_service.run();
return 0;
}
//
// This example implements handler callback guard as weak_callback() template function
//
// compile with
// g++ -std=c++11 -g -Wall 02-asio-timer-with-weak-ptr.cpp -lboost_system -o 02-asio-timer-with-weak-ptr
// clang++ -std=c++11 -g -Wall 02-asio-timer-with-weak-ptr.cpp -lboost_system -o 02-asio-timer-with-weak-ptr
//
// run:
// ./02-asio-timer-with-weak-ptr
// valgrind --leak-check=full --show-leak-kinds=all ./02-asio-timer-with-weak-ptr
//
// output:
// my_timer_task::my_timer_task()
// my_timer_task::arm()
// guard object alive: executing function
// my_timer_task::timer_expired_callback() 1
// guard object alive: executing function
// my_timer_task::timer_expired_callback() 2
// DELETING TIMER OBJECT
// my_timer_task::~my_timer_task()
// guard object released: do not execute function
//
#include <iostream>
#include <functional>
#include <memory>
#include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>
#include <chrono>
class my_timer_task;
static std::shared_ptr<my_timer_task> task;
template <typename Guard, typename Function>
struct weak_callback_details
{
std::weak_ptr<typename Guard::element_type> g_;
Function f_;
weak_callback_details(Guard&& g, Function&& f)
: g_(std::forward<Guard>(g)), f_(std::forward<Function>(f)) { }
template <typename ...Args>
void operator() (Args&& ...args)
{
// weak_ptr is used as guard:
// - if weak_ptr is not expired, call provided function
// - if weak_ptr is expired, skip the call
if (auto s = g_.lock())
{
std::cout << "guard object alive: executing function" << std::endl;
f_(std::forward<Args>(args)...);
}
else
{
std::cout << "guard object released: do not execute function" << std::endl;
}
}
};
template <typename Guard, typename Function>
weak_callback_details<Guard, Function> weak_callback(Guard&& g, Function&& f)
{
return weak_callback_details<Guard, Function>(std::forward<Guard>(g), std::forward<Function>(f));
}
class my_timer_task : public std::enable_shared_from_this<my_timer_task>
{
public:
my_timer_task(boost::asio::io_service& io_service)
: timer_(io_service), counter_(0)
{
std::cout << "my_timer_task::my_timer_task()" << std::endl;;
}
~my_timer_task()
{
std::cout << "my_timer_task::~my_timer_task()" << std::endl;
}
void arm()
{
std::cout << "my_timer_task::arm()" << std::endl;
timer_.expires_from_now(std::chrono::seconds(1));
timer_.async_wait(weak_callback(shared_from_this(), std::bind(&my_timer_task::timer_expired_callback, this, std::placeholders::_1)));
}
void timer_expired_callback(const boost::system::error_code& ec)
{
std::cout << "my_timer_task::timer_expired_callback() " << ++counter_ << std::endl;;
if (!ec)
{
timer_.async_wait(weak_callback(shared_from_this(), std::bind(&my_timer_task::timer_expired_callback, this, std::placeholders::_1)));
// Here we demonstrate deleting the timer object. This would be normally
// done from outside e.g. when 'my_timer_task' falls out of scope (RAII)
if (counter_ == 2)
{
std::cout << "DELETING TIMER OBJECT" << std::endl;
task.reset();
}
}
else
{
std::cout << "error_code: " << ec.message() << std::endl;
}
}
private:
boost::asio::steady_timer timer_;
unsigned int counter_;
};
int
main(int argc, char *argv[])
{
boost::asio::io_service io_service;
// In this example shared_ptr must be used for the timer object,
// since that enables using shared_from_this(). Note also, that
// shared_from_this() cannot be called directly from constructor,
// since shared_ptr has been created at that time yet.
task = std::make_shared<my_timer_task>(io_service);
task->arm();
io_service.run();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment