I'm using multiple boost::asio::deadline_timer
on one io_service object. std::shared_ptr
of boost::asio::deadline_timer
are stored in the container std::map<int, std::shared_ptr<debug_tim>> timers
with index.
In the timer handler, I erase other boost::asio::deadline_timer
. However, it seems that the erased timer woule be often fired with success error code.
Is there any way to avoid that. I expect that the timer handler that corresponding to the erased boost::asio::deadline_timer
always fires with Operation canceled
.
Am I missing something?
Here is the code that reproduces the behavior
https://wandbox.org/permlink/G0qzYcqauxdqw4i7
#include <iostream>
#include <memory>
#include <boost/asio.hpp>
// deadline_timer with index ctor/dtor print
struct debug_tim : boost::asio::deadline_timer {
debug_tim(boost::asio::io_service& ios, int i) : boost::asio::deadline_timer(ios), i(i) {
std::cout << "debug_tim() " << i << std::endl;
}
~debug_tim() {
std::cout << "~debug_tim() " << i << std::endl;
}
int i;
};
int main() {
boost::asio::io_service ios;
std::map<int, std::shared_ptr<debug_tim>> timers;
{
for (int i = 0; i != 5; ++i) {
auto tim = std::make_shared<debug_tim>(ios, i);
std::cout << "set timer " << i << std::endl;
tim->expires_from_now(boost::posix_time::seconds(1));
timers.emplace(i, tim);
tim->async_wait([&timers, i](auto ec){
std::cout << "timer fired " << i << " : " << ec.message() << std::endl;
auto it = timers.find(i);
if (it == timers.end()) {
std::cout << " already destructed." << std::endl;
}
else {
int other_idx = i + 1; // erase other timer (e.g. i + 1)
timers.erase(other_idx);
std::cout << " erased " << other_idx << std::endl;
}
}
);
}
}
ios.run();
}
I also call boost::asio::deadline_timer::cancel()
before I erase the timer. However, I got similar result. Here is the cancel version:
https://wandbox.org/permlink/uM0yMFufkyn9ipdG
#include <iostream>
#include <memory>
#include <boost/asio.hpp>
// deadline_timer with index ctor/dtor print
struct debug_tim : boost::asio::deadline_timer {
debug_tim(boost::asio::io_service& ios, int i) : boost::asio::deadline_timer(ios), i(i) {
std::cout << "debug_tim() " << i << std::endl;
}
~debug_tim() {
std::cout << "~debug_tim() " << i << std::endl;
}
int i;
};
int main() {
boost::asio::io_service ios;
std::map<int, std::shared_ptr<debug_tim>> timers;
{
for (int i = 0; i != 5; ++i) {
auto tim = std::make_shared<debug_tim>(ios, i);
std::cout << "set timer " << i << std::endl;
tim->expires_from_now(boost::posix_time::seconds(1));
timers.emplace(i, tim);
tim->async_wait([&timers, i](auto ec){
std::cout << "timer fired " << i << " : " << ec.message() << std::endl;
auto it = timers.find(i);
if (it == timers.end()) {
std::cout << " already destructed." << std::endl;
}
else {
int other_idx = i + 1; // erase other timer (e.g. i + 1)
auto other_it = timers.find(other_idx);
if (other_it != timers.end()) {
other_it->second->cancel();
timers.erase(other_it);
}
std::cout << " erased " << other_idx << std::endl;
}
}
);
}
}
ios.run();
}
Edit
Felix, thank you for the answer. I understand the boost::asio::deadline::timer::cancel()
behavior. I always need to care the lifetime of boost::asio::deadline::timer
. I my actual code of my project, the ``boost::asio::deadline::timer` is a member variable of another object such as a session object. And in the timer handler, it accesses the object. It's dangerous.
I consider how to write safe code. And I come up with using std::weak_ptr
in order to check the object's lifetime.
Here is the updated code:
#include <iostream>
#include <memory>
#include <boost/asio.hpp>
// deadline_timer with index ctor/dtor print
struct debug_tim : boost::asio::deadline_timer {
debug_tim(boost::asio::io_service& ios, int i) : boost::asio::deadline_timer(ios), i(i) {
std::cout << "debug_tim() " << i << std::endl;
}
~debug_tim() {
std::cout << "~debug_tim() " << i << std::endl;
}
int i;
};
int main() {
boost::asio::io_service ios;
std::map<int, std::shared_ptr<debug_tim>> timers;
{
for (int i = 0; i != 5; ++i) {
auto tim = std::make_shared<debug_tim>(ios, i);
std::cout << "set timer " << i << std::endl;
tim->expires_from_now(boost::posix_time::seconds(1));
timers.emplace(i, tim);
// Capture tim as the weak_ptr wp
tim->async_wait([&timers, i, wp = std::weak_ptr<debug_tim>(tim)](auto ec){
std::cout << "timer fired " << i << " : " << ec.message() << std::endl;
// Check the lifetime of wp
if (!wp.lock()) std::cout << " timer freed." << std::endl; // return here on actual code
auto it = timers.find(i);
if (it == timers.end()) {
std::cout << " already destructed." << std::endl;
}
else {
int other_idx = i + 1; // erase other timer (e.g. i + 1)
timers.erase(other_idx);
std::cout << " erased " << other_idx << std::endl;
}
}
);
}
}
ios.run();
}
Is this a good way to avoid accessing the deleted object that has the boost::asio::deadline_timer
?
Edit
My weak_ptr solution works well.
See How to avoid firing already destroyed boost::asio::deadline_timer