Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to decide between a context.WithDeadline or a simple timer?

In Golang, I am fairly new to the intentions of passing contexts downstream to other methods and functions. I understand how a context works, how it is used, how it holds its values, how it's related to the parent context and their behaviors -- I just don't understand why to use a context in the first place.

In a more specific example, which is the actual reason of this question, in the company I work for, we have identified some very long-running queries that happen every so often due to an edge case.

An obvious solution we decided to take, given our constraints until we invest time to fix the root cause, is to kill the queries that take more than 5 minutes.

The method that runs our transactions accepts a context which is originally initiated in the API call. This context is passed down all the way to the transaction function. At that moment I found 2 solutions to kill that query:

1) Using a new context:

  • Initiate a new context.WithTimeout(ctx, time.Duration( 5 * time.Minute) )

  • Watch the Done channel in a go routine and kill the transaction when there's a signal there

  • If the transaction finished successfully in a timely manner, just cancel the context and commit the transaction as expected.

2) Using a Timer:

  • Create a Timer with 5 minutes duration
  • If the time is over, kill the transaction
  • Else, commit the transaction.

Logically speaking, they are the same solution, however, when and how to decide whether to use a context with a set Deadline or a good old Timer?

like image 891
Matheus Felipe Avatar asked Feb 21 '17 04:02

Matheus Felipe


1 Answers

The answer lies in how the context.Context and the time.Timer deliver the (cancel) signal.

context.Context gives you access to a channel via the Context.Done() method which will be closed when the goroutines using it should terminate.

time.Timer gives you access to a channel in the Timer.C struct field on which a value will be sent after the given time period (that value will be the current time, but not important here).

There, the key points are highlighted. The channel close may be observed by any number of goroutines, and infinite number of times. Spec: Receive operator:

A receive operation on a closed channel can always proceed immediately, yielding the element type's zero value after any previously sent values have been received.

So a Context may be used to signal cancelation to arbitrary number of goroutines and places. A Timer can only be used to signal one target, the one who receives the value from its channel. If multiple clients are listening or trying to receive from its channel, only one will be lucky to receive it.

Also if you're using / working with code that already supports / expects context.Context, then it ins't a question which one to use. Go 1.8 also added more context support. There have been significant additions to the database/sql package with context support; including DB.BeginTx():

The provided context is used until the transaction is committed or rolled back. If the context is canceled, the sql package will roll back the transaction. Tx.Commit will return an error if the context provided to BeginTx is canceled.

This is the primary use of context.Context: to carry a deadline and signal cancelation across API boundaries, and it's done in a concurrent-safe manner (as Context values are immutable, and channels are also safe for concurrent use, data races cannot occur, by design; more on this: If I am using channels properly should I need to use mutexes?).

Related blog posts:

The Go Blog: Go Concurrency Patterns: Context

Pitfalls of context values and how to avoid or mitigate them in Go

Dave Cheney: Context is for cancelation

like image 196
icza Avatar answered Nov 14 '22 13:11

icza