Context About Context Package in Golang

This post is about the context package of golang and how to get started with it. We’ll also disucss the various use cases of it. After reading this blog user should have comprehensive understanding of context package of golang.

Pre-requisites

  • goroutines: goroutines are the light weight threads which can be used to execute functions concurrently with other functions. You can read more about goroutines at link
  • channels: channels can be thought of as a pipe that connect multiple concurrent goroutines. Read more about channels at link
  • defer: A defer statement postpones the execution of a function until the surrounding function returns. Read more at link
  • Action completes at time instant t5.
  • This action initiates three different goroutines namely, goroutine 1, goroutine 2 and goroutine 3 etc.
  • goroutine 1 starts and completes at t1 and completes at t’1.
  • goroutine 2 starts and completes at t2 and completes at t’2.
  • goroutine 3 starts and completes at t3 and completes at t’3.

Using context | Examples

Example 1: run

func operationOne(ctx context.Context) {
n := 1
for {
select {
case <-ctx.Done():
fmt.Println("context canceled for op-1")
return // returning not to leak the goroutine
default:
fmt.Printf("OperationOne: %d\n", n)
time.Sleep(500 * time.Millisecond)
n++
}
}
}

func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
operationOne(ctx)
}
  • function operationOne is being passed context as argument.
  • context is not being cancelled or being mark as Done.
  • operationOne will never return as context is not being cancelled at all.
func operationOne(ctx context.Context) {
n := 1
for {
select {
case <-ctx.Done():
fmt.Println("context canceled for op-1")
return // returning not to leak the goroutine
default:
fmt.Printf("OperationOne: %d\n", n)
time.Sleep(500 * time.Millisecond)
n++
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go operationOne(ctx)
time.Sleep(5 * time.Second)
}
  • function operationOne is being passed context as argument.
  • main function will return after 5 seconds from the start of execution.
  • context will cancelled at return of main function.
func operationOne(ctx context.Context) {
n := 1
for {
select {
case <-ctx.Done():
fmt.Println("context canceled for op-1")
return // returning not to leak the goroutine
default:
fmt.Printf("OperationOne: %d\n", n)
time.Sleep(500 * time.Millisecond)
n++
}
}
}

func operationTwo(ctx context.Context) {
n := 1
for {
select {
case <-ctx.Done():
fmt.Println("context canceled for op-2")
return // returning not to leak the goroutine
default:
fmt.Printf("OperationTwo: %d\n", n)
time.Sleep(250 * time.Millisecond)
n++
}
}
}

func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
d := time.Now().Add(5000 * time.Millisecond)
ctx, cancel = context.WithDeadline(context.Background(), d)
defer cancel()
go operationOne(ctx)

d = time.Now().Add(10000 * time.Millisecond)
ctx, cancel = context.WithDeadline(context.Background(), d)
defer cancel()
go operationTwo(ctx)

time.Sleep(20 * time.Second)
}
  • drived context with differenty deadlines are being passed to operationOne and operationTwo functions.
  • main function will return after 20 seconds from the start of execution.
  • function operationOne will return after 5 seconds as deadline of context passed to it will be reached.
  • function operationTwo will return after 10 seconds as deadline of context passed to it will be reached.
  • main function will return only after 20 seconds even though operationOne and operationTwo will be completed in first 10 seconds.
func operationOne(ctx context.Context) {
n := 1
for {
select {
case <-ctx.Done():
fmt.Println("context canceled for op-1")
return // returning not to leak the goroutine
default:
fmt.Printf("OperationOne: %d\n", n)
time.Sleep(500 * time.Millisecond)
n++
}
}
}
func operationTwo(ctx context.Context) {
n := 1
for {
select {
case <-ctx.Done():
fmt.Println("context canceled for op-2")
return // returning not to leak the goroutine
default:
fmt.Printf("OperationTwo: %d\n", n)
time.Sleep(250 * time.Millisecond)
n++
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
d := time.Now().Add(5000 * time.Millisecond)
ctx, _ = context.WithDeadline(context.Background(), d)
go operationOne(ctx)
d = time.Now().Add(10000 * time.Millisecond)
ctx, _ = context.WithDeadline(context.Background(), d)
go operationTwo(ctx)
time.Sleep(3 * time.Second)
}
  • drived context with differenty deadlines are being passed to operationOne and operationTwo functions.
  • function operationOne will return after 5 seconds as deadline of context passed to it will be reached.
  • function operationTwo will return after 10 seconds as deadline of context passed to it will be reached.
  • main function will return after first 3 seconds.
type Key string  func operationOne(ctx context.Context) {
n := 1
for {
select {
case <-ctx.Done():
fmt.Printf("context canceled for %s\n", ctx.Value(Key("op_id")))
return // returning not to leak the goroutine
default:
fmt.Printf("OperationOne: %d : opeartion_id = %s\n", n, ctx.Value(Key("op_id")))
time.Sleep(500 * time.Millisecond)
n++
}
}
}
func operationTwo(ctx context.Context) {
n := 1
for {
select {
case <-ctx.Done():
fmt.Printf("context canceled for %s\n", ctx.Value(Key("op_id")))
return // returning not to leak the goroutine
default:
fmt.Printf("OperationTwo: %d : opeartion_id = %s\n", n, ctx.Value(Key("op_id")))
time.Sleep(250 * time.Millisecond)
n++
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
d := time.Now().Add(5000 * time.Millisecond)
ctx, cancel = context.WithDeadline(context.Background(), d)
defer cancel()
ctx = context.WithValue(ctx, Key("op_id"), "ONE")
go operationOne(ctx)
d = time.Now().Add(10000 * time.Millisecond)
ctx, cancel = context.WithDeadline(context.Background(), d)
defer cancel()
ctx = context.WithValue(ctx, Key("op_id"), "TWO")
go operationTwo(ctx)
time.Sleep(20 * time.Second)
}
  • drived context with differenty deadlines are being passed to operationOne and operationTwo functions.
  • a variable op_id and it’s value is also being passed with drived context in both the cases.
  • main function will return after 20 seconds from the start of execution.
  • function operationOne will return after 5 seconds as deadline of context passed to it will be reached.
  • function operationTwo will return after 10 seconds as deadline of context passed to it will be reached.
  • main function will return only after 20 seconds even though operationOne and operationTwo will be completed in first 10 seconds.
type Key string  func operationOneChild(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Printf("context canceled for %s\n", ctx.Value(Key("op_id")))
return // returning not to leak the goroutine
default:
fmt.Println("Child of operation one")
time.Sleep(100 * time.Millisecond)
}
}
} func operationOne(ctx context.Context) {
n := 1
// go operationOneChild(context.WithValue(ctx, Key("op_id"), "CHILD OF ONE"))
go operationOneChild(nil)
for {
select {
case <-ctx.Done():
fmt.Printf("context canceled for %s\n", ctx.Value(Key("op_id")))
return // returning not to leak the goroutine
default:
fmt.Printf("OperationOne: %d : opeartion_id = %s\n", n, ctx.Value(Key("op_id")))
time.Sleep(500 * time.Millisecond)
n++
}
}
}
func operationTwo(ctx context.Context) {
n := 1
for {
select {
case <-ctx.Done():
fmt.Printf("context canceled for %s\n", ctx.Value(Key("op_id")))
return // returning not to leak the goroutine
default:
fmt.Printf("OperationTwo: %d : opeartion_id = %s\n", n, ctx.Value(Key("op_id")))
time.Sleep(250 * time.Millisecond)
n++
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
d := time.Now().Add(5000 * time.Millisecond)
ctx, cancel = context.WithDeadline(context.Background(), d)
defer cancel()
ctx = context.WithValue(ctx, Key("op_id"), "ONE")
go operationOne(ctx)
d = time.Now().Add(10000 * time.Millisecond)
ctx, cancel = context.WithDeadline(context.Background(), d)
defer cancel()
ctx = context.WithValue(ctx, Key("op_id"), "TWO")
go operationTwo(ctx)
time.Sleep(20 * time.Second)
}
  • drived context with differenty deadlines are being passed to operationOne and operationTwo functions.
  • function operationOne will return after 5 seconds as deadline of context passed to it will be reached.
  • function operationTwo will return after 10 seconds as deadline of context passed to it will be reached.
  • main function will return only after 20 seconds even though operationOne and operationTwo will be completed in first 10 seconds.
  • nil drived context is being passed to function operationOneChild which will panic with following result.
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xf3966]
goroutine 9 [running]:
main.operationOneChild(0x0, 0x0)
/tmp/sandbox968352470/prog.go:14 +0xa6
created by main.operationOne
/tmp/sandbox968352470/prog.go:28 +0x40
type Key string  func operationOneChild(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Printf("context canceled for %s\n", ctx.Value(Key("op_id")))
return // returning not to leak the goroutine
default:
fmt.Println("Child of operation one")
time.Sleep(100 * time.Millisecond)
}
}
} func operationOne(ctx context.Context) {
n := 1
go operationOneChild(context.WithValue(ctx, Key("op_id"), "CHILD OF ONE"))
// go operationOneChild(nil)
for {
select {
case <-ctx.Done():
fmt.Printf("context canceled for %s\n", ctx.Value(Key("op_id")))
return // returning not to leak the goroutine
default:
fmt.Printf("OperationOne: %d : opeartion_id = %s\n", n, ctx.Value(Key("op_id")))
time.Sleep(500 * time.Millisecond)
n++
}
}
}
func operationTwo(ctx context.Context) {
n := 1
for {
select {
case <-ctx.Done():
fmt.Printf("context canceled for %s\n", ctx.Value(Key("op_id")))
return // returning not to leak the goroutine
default:
fmt.Printf("OperationTwo: %d : opeartion_id = %s\n", n, ctx.Value(Key("op_id")))
time.Sleep(250 * time.Millisecond)
n++
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
d := time.Now().Add(5000 * time.Millisecond)
ctx, cancel = context.WithDeadline(context.Background(), d)
defer cancel()
ctx = context.WithValue(ctx, Key("op_id"), "ONE")
go operationOne(ctx)
d = time.Now().Add(10000 * time.Millisecond)
ctx, cancel = context.WithDeadline(context.Background(), d)
defer cancel()
ctx = context.WithValue(ctx, Key("op_id"), "TWO")
go operationTwo(ctx)
time.Sleep(20 * time.Second)
}
  • drived context with differenty deadlines are being passed to operationOne and operationTwo functions.
  • function operationOne will return after 5 seconds as deadline of context passed to it will be reached.
  • function operationTwo will return after 10 seconds as deadline of context passed to it will be reached.
  • main function will return only after 20 seconds even though operationOne and operationTwo will be completed in first 10 seconds.
  • context which is being passed to operationOneChild is non nil, non drived context. This child go routine will also be terminated on cancellation of parent context.

Things to keep in mind while using the context

  • Incoming requests to a server should create a Context.
  • Outgoing calls to servers should accept a Context.
  • The chain of function calls between them must propagate the Context.
  • Replace the current Context with the derived Context.
  • Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it.
  • Do not pass a nil Context, even if a function permits it. Pass context.TODO if you are unsure about which Context to use.
  • The same Context may be passed to functions running in different goroutines; Contexts are safe for simultaneous use by multiple goroutines.

Product Engineer at Gojek.