Discussion:
[asio-users] [asio] when deadline_timer::cancel() returns
Yuval Ronen
2007-07-29 15:41:10 UTC
Permalink
[this a copy of a post sent to the Boost.Users list]

The Asio docs for deadline_timer::cancel say that "This function forces
the completion of any pending asynchronous wait operations against the
timer". Let's say I have a thread executing io_service::run, and is now
invoking a completion handler of a deadline_timer. While this completion
handler is underway, a second thread is calling cancel() on that
deadline_timer. Is the cancel() call supposed to block until the
completion handler is finished? To my best understanding, both according
to the docs, and according to what seems to be logical to me, the answer
is "yes, it should block". However, it doesn't. Is it a bug, or by design?

Yuval
Olaf van der Spek
2007-07-29 17:12:20 UTC
Permalink
Post by Yuval Ronen
[this a copy of a post sent to the Boost.Users list]
The Asio docs for deadline_timer::cancel say that "This function forces
the completion of any pending asynchronous wait operations against the
timer". Let's say I have a thread executing io_service::run, and is now
invoking a completion handler of a deadline_timer. While this completion
handler is underway, a second thread is calling cancel() on that
deadline_timer. Is the cancel() call supposed to block until the
completion handler is finished? To my best understanding, both according
to the docs, and according to what seems to be logical to me, the answer
is "yes, it should block". However, it doesn't. Is it a bug, or by design?
All AFAIK:
No, the docs don't say the handlers are run right away. In fact, the
handlers won't be run until after the current handler (in which you
call cancel has returned).
Christopher Kohlhoff
2007-07-30 00:19:23 UTC
Permalink
Post by Olaf van der Spek
No, the docs don't say the handlers are run right away. In fact, the
handlers won't be run until after the current handler (in which you
call cancel has returned).
Yep. To expand on this scenario a bit:

- You call expires_at() or expires_from_now() to set the timer.

- You start the operation by calling async_wait().

- Before the timer has a chance to expire "in the background" you call
cancel().

- The completion handler is posted for execution (again as-if by calling
io_service::post()).

Cheers,
Chris
Christopher Kohlhoff
2007-07-30 00:16:38 UTC
Permalink
Post by Yuval Ronen
[this a copy of a post sent to the Boost.Users list]
The Asio docs for deadline_timer::cancel say that "This function forces
the completion of any pending asynchronous wait operations against the
timer". Let's say I have a thread executing io_service::run, and is now
invoking a completion handler of a deadline_timer. While this completion
handler is underway, a second thread is calling cancel() on that
deadline_timer. Is the cancel() call supposed to block until the
completion handler is finished? To my best understanding, both according
to the docs, and according to what seems to be logical to me, the answer
is "yes, it should block".
I'm curious: why do you think that would be the logical approach?
Post by Yuval Ronen
However, it doesn't. Is it a bug, or by design?
That's the way it is designed to work. The cancel() function is
essentially a non-blocking request for the timer to cancel any
outstanding operations.

This is what is happening in the scenario you describe above:

- You set the timer's expiry using expires_at() or expires_from now().

- You call async_wait() to initiate the operation.

- The timer expires "in the background" and the completion handler is
posted (as-if by calling io_service::post()) for execution. At this
point the handler is decoupled from the timer. The timer's work is done
and the asynchronous wait operation is no longer pending.

- The completion handler is executed from inside io_service::run(). The
timer is not involved in any way.

- You call cancel() from another thread. The timer has nothing to cancel.

If you actually want your code to block until a specific completion
handler has executed then you will want to look at using something like
a future.

Cheers,
Chris
Yuval Ronen
2007-07-30 11:22:04 UTC
Permalink
Post by Christopher Kohlhoff
Post by Yuval Ronen
[this a copy of a post sent to the Boost.Users list]
The Asio docs for deadline_timer::cancel say that "This function forces
the completion of any pending asynchronous wait operations against the
timer". Let's say I have a thread executing io_service::run, and is now
invoking a completion handler of a deadline_timer. While this completion
handler is underway, a second thread is calling cancel() on that
deadline_timer. Is the cancel() call supposed to block until the
completion handler is finished? To my best understanding, both according
to the docs, and according to what seems to be logical to me, the answer
is "yes, it should block".
I'm curious: why do you think that would be the logical approach?
Let's say I have a class (MyClass) which contains an instance of
deadline_timer, in order to perform a specific task every 10 seconds, as
long the MyClass instance is alive. I use the technique demonstrated by
Timer.4 tutorial example, of calling timer.expires_at() and
timer.async_wait() from the timer completion handler.

The d'tor of MyClass needs to stop execution of that specific task. How
to do it? AFAIU, I need to do it using timer.cancel(). The d'tor is
called in a different thread than the thread that execute the completion
handler (which is the thread calling io_service::run). So let's assume
the completion handler has just begun executing at some point, and then
there is a context switch. The other thread is calling
MyClass::~MyClass, and then there is another context switch. The first
thread continues the completion handler, and in the end it tries to
activate the timer (expires_at & async_wait). However, at that point the
deadline_timer is already destroyed! The timer d'tor was already called
(from MyClass d'tor)...

I can supply some code if necessary.
Post by Christopher Kohlhoff
Post by Yuval Ronen
However, it doesn't. Is it a bug, or by design?
That's the way it is designed to work. The cancel() function is
essentially a non-blocking request for the timer to cancel any
outstanding operations.
- You set the timer's expiry using expires_at() or expires_from now().
- You call async_wait() to initiate the operation.
- The timer expires "in the background" and the completion handler is
posted (as-if by calling io_service::post()) for execution. At this
point the handler is decoupled from the timer. The timer's work is done
and the asynchronous wait operation is no longer pending.
- The completion handler is executed from inside io_service::run(). The
timer is not involved in any way.
- You call cancel() from another thread. The timer has nothing to cancel.
If you actually want your code to block until a specific completion
handler has executed then you will want to look at using something like
a future.
OK, I now understand better how the deadline_timer interacts with the
io_service. The problem is, as I described above, that I can't implement
a recurring asynchronous timer. Or is there a way I didn't think of?
Nolan Hurlburt
2007-07-30 18:12:04 UTC
Permalink
Post by Yuval Ronen
Post by Christopher Kohlhoff
Post by Yuval Ronen
[this a copy of a post sent to the Boost.Users list]
The Asio docs for deadline_timer::cancel say that "This function forces
the completion of any pending asynchronous wait operations against the
timer". Let's say I have a thread executing io_service::run, and is now
invoking a completion handler of a deadline_timer. While this completion
handler is underway, a second thread is calling cancel() on that
deadline_timer. Is the cancel() call supposed to block until the
completion handler is finished? To my best understanding, both according
to the docs, and according to what seems to be logical to me, the answer
is "yes, it should block".
I'm curious: why do you think that would be the logical approach?
Let's say I have a class (MyClass) which contains an instance of
deadline_timer, in order to perform a specific task every 10 seconds, as
long the MyClass instance is alive. I use the technique demonstrated by
Timer.4 tutorial example, of calling timer.expires_at() and
timer.async_wait() from the timer completion handler.
The d'tor of MyClass needs to stop execution of that specific task. How
to do it? AFAIU, I need to do it using timer.cancel(). The d'tor is
called in a different thread than the thread that execute the completion
handler (which is the thread calling io_service::run). So let's assume
the completion handler has just begun executing at some point, and then
there is a context switch. The other thread is calling
MyClass::~MyClass, and then there is another context switch. The first
thread continues the completion handler, and in the end it tries to
activate the timer (expires_at & async_wait). However, at that point the
deadline_timer is already destroyed! The timer d'tor was already called
(from MyClass d'tor)...
I can supply some code if necessary.
Post by Christopher Kohlhoff
Post by Yuval Ronen
However, it doesn't. Is it a bug, or by design?
That's the way it is designed to work. The cancel() function is
essentially a non-blocking request for the timer to cancel any
outstanding operations.
- You set the timer's expiry using expires_at() or expires_from now().
- You call async_wait() to initiate the operation.
- The timer expires "in the background" and the completion handler is
posted (as-if by calling io_service::post()) for execution. At this
point the handler is decoupled from the timer. The timer's work is done
and the asynchronous wait operation is no longer pending.
- The completion handler is executed from inside io_service::run(). The
timer is not involved in any way.
- You call cancel() from another thread. The timer has nothing to cancel.
If you actually want your code to block until a specific completion
handler has executed then you will want to look at using something like
a future.
OK, I now understand better how the deadline_timer interacts with the
io_service. The problem is, as I described above, that I can't implement
a recurring asynchronous timer. Or is there a way I didn't think of?
-------------------------------------------------------------------------
This SF.net email is sponsored by: Splunk Inc.
Still grepping through log files to find problems? Stop.
Now Search log events and configuration files using AJAX and a browser.
Download your FREE copy of Splunk now >> http://get.splunk.com/
_______________________________________________
asio-users mailing list
https://lists.sourceforge.net/lists/listinfo/asio-users
Yuval,

Consider using shared pointers such that the object referred to in the
async callback isn't destroyed until after all operations have been
canceled/completed.

Nolan
Yuval Ronen
2007-07-31 07:09:47 UTC
Permalink
Post by Nolan Hurlburt
Post by Yuval Ronen
OK, I now understand better how the deadline_timer interacts with the
io_service. The problem is, as I described above, that I can't implement
a recurring asynchronous timer. Or is there a way I didn't think of?
Consider using shared pointers such that the object referred to in the
async callback isn't destroyed until after all operations have been
canceled/completed.
Well, I assume using shared pointers can make it work (haven't looked at
it in depth, so there may be surprises), but I don't want to do that.
Conceptually, I don't have any shared ownership in my design, so using
shared_ptr is a kludge. What I need is an instance of a timer, so the
straight forward way to do it, is simply to hold an instance of a timer,
and that's how I'd like to do it. If at all possible, of course...
Christopher Kohlhoff
2007-08-03 07:16:03 UTC
Permalink
Post by Yuval Ronen
Well, I assume using shared pointers can make it work (haven't looked at
it in depth, so there may be surprises), but I don't want to do that.
Conceptually, I don't have any shared ownership in my design
Actually you do, in the sense that you are sharing your object between
the main thread and the (logical) background thread that is performing
the asynchronous operation for you.

Cheers,
Chris
Yuval Ronen
2007-08-07 14:29:45 UTC
Permalink
Post by Christopher Kohlhoff
Post by Yuval Ronen
Well, I assume using shared pointers can make it work (haven't looked at
it in depth, so there may be surprises), but I don't want to do that.
Conceptually, I don't have any shared ownership in my design
Actually you do, in the sense that you are sharing your object between
the main thread and the (logical) background thread that is performing
the asynchronous operation for you.
The way I see it, shared ownership (as in shared_ptr) is about sharing
among *instances*, and not among threads. AFAIU, threading is orthogonal
to ownership, and therefore irrelevant. But since you're not suggesting
using shared_ptr to solve my problem (your other post is about using a
condition), I guess this discussion is purely academic...
Christopher Kohlhoff
2007-08-09 00:05:14 UTC
Permalink
(Getting away from the topic here, but...)
Post by Yuval Ronen
The way I see it, shared ownership (as in shared_ptr) is about sharing
among *instances*, and not among threads. AFAIU, threading is orthogonal
to ownership, and therefore irrelevant.
In my view, a thread implies ownership because a thread has a stack. Any
object declared on that thread's stack is owned by that thread. Any
resource that is managed by a stack-based variable on that thread is
itself owned by the thread. Your shared_ptr instances have to reside
somewhere, and if you trace the ownership tree of a shared_ptr you're
likely to encounter the stack at some point.

Cheers,
Chris
Yuval Ronen
2007-08-09 09:18:27 UTC
Permalink
Post by Christopher Kohlhoff
(Getting away from the topic here, but...)
Post by Yuval Ronen
The way I see it, shared ownership (as in shared_ptr) is about sharing
among *instances*, and not among threads. AFAIU, threading is orthogonal
to ownership, and therefore irrelevant.
In my view, a thread implies ownership because a thread has a stack. Any
object declared on that thread's stack is owned by that thread. Any
resource that is managed by a stack-based variable on that thread is
itself owned by the thread. Your shared_ptr instances have to reside
somewhere, and if you trace the ownership tree of a shared_ptr you're
likely to encounter the stack at some point.
The 'object' declared on the thread stack can actually be a reference
(or a pointer) to an object somewhere else, so the thread doesn't own
that object. This is not only possible, but also common, IMO. So my
opinion is still that threading and ownership are orthogonal...
Christopher Kohlhoff
2007-08-02 00:04:26 UTC
Permalink
Post by Yuval Ronen
The d'tor of MyClass needs to stop execution of that specific task. How
to do it? AFAIU, I need to do it using timer.cancel(). The d'tor is
called in a different thread than the thread that execute the completion
handler (which is the thread calling io_service::run). So let's assume
the completion handler has just begun executing at some point, and then
there is a context switch. The other thread is calling
MyClass::~MyClass, and then there is another context switch. The first
thread continues the completion handler, and in the end it tries to
activate the timer (expires_at & async_wait). However, at that point the
deadline_timer is already destroyed! The timer d'tor was already called
(from MyClass d'tor)...
The rule is that you need to ensure any objects you refer to in your
completion handlers remain valid until the handlers have executed and no
longer need to access the objects.

So, you need to ensure the destruction does not complete until all
handlers associated with the timer have run. Whether you cancel the
timer or not is irrelevant to this aspect of the discussion.

Your requirement is that the "shutdown" be initiated by the MyClass
destructor. Since you are presumably sharing the io_service (and the
thread running the io_service) between many MyClass instances, you
therefore need to add your own way of tracking when all completion
handlers have finished for a specific MyClass instance.

For example, you could use a mutex + condition variable for this like so:

class MyClass
{
private:
boost::asio::deadline_timer timer_;
boost::mutex mutex_;
boost::condition condition_;
enum { running, stopping, stopped } state_;

public:
...

~MyClass()
{
boost::mutex::scoped_lock lock(mutex_);
state_ = stopping;
timer_.cancel();
while (state_ != stopped)
condition_.wait(lock);
}

...

void handle_timeout()
{
boost::mutex::scoped_lock lock(mutex_);
if (state_ == running)
{
// Do your work and reschedule timer.
}
else
{
state_ = stopped;
condition_.notify_all();
}
}
};

Cheers,
Chris
Yuval Ronen
2007-08-07 14:49:12 UTC
Permalink
Post by Christopher Kohlhoff
Post by Yuval Ronen
The d'tor of MyClass needs to stop execution of that specific task. How
to do it? AFAIU, I need to do it using timer.cancel(). The d'tor is
called in a different thread than the thread that execute the completion
handler (which is the thread calling io_service::run). So let's assume
the completion handler has just begun executing at some point, and then
there is a context switch. The other thread is calling
MyClass::~MyClass, and then there is another context switch. The first
thread continues the completion handler, and in the end it tries to
activate the timer (expires_at & async_wait). However, at that point the
deadline_timer is already destroyed! The timer d'tor was already called
(from MyClass d'tor)...
The rule is that you need to ensure any objects you refer to in your
completion handlers remain valid until the handlers have executed and no
longer need to access the objects.
Of course, unless the infrastructure handles that for me.
Post by Christopher Kohlhoff
Your requirement is that the "shutdown" be initiated by the MyClass
destructor. Since you are presumably sharing the io_service (and the
thread running the io_service) between many MyClass instances,
That's correct.
Post by Christopher Kohlhoff
you
therefore need to add your own way of tracking when all completion
handlers have finished for a specific MyClass instance.
I have some (not much) experience with ACE, and the timer.cancel() there
returns only when all handlers are finished, so I don't have to track
this myself. I assumed it's the same in Asio, but I guess I was wrong.
At first, the ACE approach seems a bit more intuitive to me. After all,
in the eyes of the user (me) a timer *is* it's completion handlers,
because there's nothing else important with a timer. So it makes sense
to me that after a cancel() returns, no handlers will be called (which
implies it waits for all of them to finish). Can you please give some
rationale as to why a different approach was chosen?
I was hoping that the library will save me from using a mutex+condition,
but I guess a man's got to do what a man's got to do... :-)

Thanks for your help!
Yuval
Christopher Kohlhoff
2007-08-09 08:24:05 UTC
Permalink
Post by Yuval Ronen
I have some (not much) experience with ACE, and the timer.cancel() there
returns only when all handlers are finished, so I don't have to track
this myself. I assumed it's the same in Asio, but I guess I was wrong.
At first, the ACE approach seems a bit more intuitive to me. After all,
in the eyes of the user (me) a timer *is* it's completion handlers,
Well, from the asio point-of-view, the timer is it's completion handlers
only until the timer expires, at which point they are separated when the
completion handlers are posted for execution.
Post by Yuval Ronen
because there's nothing else important with a timer. So it makes sense
to me that after a cancel() returns, no handlers will be called (which
implies it waits for all of them to finish). Can you please give some
rationale as to why a different approach was chosen?
Well, it has been a while, but these come to mind:

- Operations like this should avoid blocking the calling thread since
that may hold up other completion handlers that are ready to run.

- Block-and-wait designs are more prone to deadlocks. E.g. if you called
cancel while holding a lock, and the handlers that needed to be run
wanted to obtain the same lock. The asio approach of deferred execution
helps prevent the cycle that's a necessary precondition for deadlock
from occurring in the first place.

- Finally, a guiding philosophy of mine is to provide the fundamental
building blocks on which you can build other designs. E.g. the behaviour
you want can be obtained by adding a mutex+condition or a future.
Building a non-blocking design on top of a blocking one is more difficult.

Cheers,
Chris
Yuval Ronen
2007-08-09 09:19:22 UTC
Permalink
Post by Christopher Kohlhoff
Post by Yuval Ronen
I have some (not much) experience with ACE, and the timer.cancel() there
returns only when all handlers are finished, so I don't have to track
this myself. I assumed it's the same in Asio, but I guess I was wrong.
At first, the ACE approach seems a bit more intuitive to me. After all,
in the eyes of the user (me) a timer *is* it's completion handlers,
Well, from the asio point-of-view, the timer is it's completion handlers
only until the timer expires, at which point they are separated when the
completion handlers are posted for execution.
Post by Yuval Ronen
because there's nothing else important with a timer. So it makes sense
to me that after a cancel() returns, no handlers will be called (which
implies it waits for all of them to finish). Can you please give some
rationale as to why a different approach was chosen?
- Operations like this should avoid blocking the calling thread since
that may hold up other completion handlers that are ready to run.
- Block-and-wait designs are more prone to deadlocks. E.g. if you called
cancel while holding a lock, and the handlers that needed to be run
wanted to obtain the same lock. The asio approach of deferred execution
helps prevent the cycle that's a necessary precondition for deadlock
from occurring in the first place.
- Finally, a guiding philosophy of mine is to provide the fundamental
building blocks on which you can build other designs. E.g. the behaviour
you want can be obtained by adding a mutex+condition or a future.
Building a non-blocking design on top of a blocking one is more difficult.
Got you. Thanks!

Continue reading on narkive:
Search results for '[asio-users] [asio] when deadline_timer::cancel() returns' (Questions and Answers)
15
replies
What are some things i can do to stay focused on my homework?
started 2007-01-28 17:04:25 UTC
singles & dating
Loading...