Background
In the last post, I shared the first part about the context
package: valueCtx
and cancelCtx
. Let us continue the journey to discover more in this post.
WithTimeout and WithDeadline
As usual, let us start with an example:
1 | package main |
Since we already know the behavior of cancelCtx
, it’s quite straightforward to understand how WithTimeout
works. It accepts a timeout duration after which the done
channel will be closed and context will be canceled. And a cancel function will be returned as well, which can be called in case the context needs to be canceled before timeout.
WithDeadline
usage is quite similar to WithTimeout
, you can find related example easily. Let us review the source code:
1 | type timerCtx struct { |
Since WithTimeout
and WithDeadline
have many common points between them, so they share the same type of context: timerCtx
, which embeds cancelCtx
and defines two more properties: timer
and deadline
.
Let us review what happens when we create a timerCtx
:
1 | func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { |
Compared to WithCancle
and WithValue
, WithDeadline
is more complex, let us go through bit by bit.
Firstly, parent.Deadline
will get the deadline time for parent context. The Deadline
method signature was defined in the Context
interface as below:
1 | type Context interface { |
In the context package, only emptyCtx
and timerCtx
type implement this method:
1 | func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { |
So when we call parent.Deadline()
, if the parent context is also type of timerCtx
which implements its own Deadline()
method, then you can get the deadline time of the parent context. Otherwise if the parent context is type of cancelCtx
or valueCtx
, then finally the Deadline()
method of emptyCtx
will be called and you will get the zero value of type time.Time
and bool
(if you have interest, you can verify by yourself the zero value: 0001-01-01 00:00:00 +0000 UTC and false).
If parent’s deadline is earlier than the passed in deadline parameter, then directly return a cancelCtx
by calling WithCancel(parent)
. Of course when the passed in deadline is reasonable, we need to create a timerCtx
:
1 | //inside WithDeadline() function |
In the above code, you see propagateCancel
method again, I have discussed about it in the last post, if you don’t understand it, please refer here.
Similar to cancelCtx
, timerCtx
sends the context cancel signal by closing the done channel by calling its own cancel
method. There two scenarios when cancelling the context:
- timeout cancel: when the deadline exceeded, automatically close the done channel;
1 | // inside WithDeadline function |
- manual cancel: call the returned cancel function to close the done channel before the deadline;
1
2
3
4
5// inside WithDeadline function
...
// return the cancel function as the second return value
return c, func() { c.cancel(true, Canceled) }
...
Both scenarios call cancel
method:
1 | func (c *timerCtx) cancel(removeFromParent bool, err error) { |
timerCtx
implements cancel
method to stop and reset the timer then delegate to cancelCtx.cancel
.
Summary
In the second part of this post series, we discussed how timeout
and deadline
context are implemented in the source code level. In this part, Golang struct embedding technique is used a lot, you can compare it with traditional OOP solution to have a deep understanding.