Context Package For Cancellation
- Context is immutable.
- Context package provides function to add new behaviour.
- To add cancellation behaviour we have function like
- context.WithCancell()
- context.WithDeadline()
- context.WithTimeout()
- The derived context is passed to child goroutines to facilitate their cancellation.
WithCancel()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
- returns a copy of parent with a new Done channel.
- cancel() can be used to close context's done channel.
- Closing the done channel indicates to an operation to abandon its work and return.
- Canceling the context releases the resources associated with it.
- If we don't call the cancel function, then there will be a memory leak, the resources associated with the context won't be released until the current context is cancelled or the parent context is cancelled.
cancel()
cancel()does not wait for the work to stop.cancel()may be called by multiple goroutines simultaneously.- After the first call, subsequent calls to a cancell() do nothing.

exercises
https://go.dev/play/p/Qx_6jcLVnvj
package main
import (
"context"
"fmt"
)
func main() {
// generator - generates integers in a separate goroutine and
// sends them to the returned channel.
// The callers of gen need to cancel the goroutine once
// they consume 5th integer value
// so that internal goroutine
// started by gen is not leaked.
generator := func(ctx context.Context) <-chan int {
ch := make(chan int)
n := 1
go func() {
defer close(ch)
for {
select {
case ch <- n:
case <-ctx.Done():
return
}
n++
}
}()
return ch
}
// Create a context that is cancellable.
ctx, cancel := context.WithCancel(context.Background())
ch := generator(ctx)
for n := range ch {
fmt.Println(n)
if n == 5 {
cancel()
}
}
}
WithDeadline()
deadline := time.Now().Add(5 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(),deadline)
defer cancel()
// ok==false when no deadline is set.
deadline, ok := ctx.Deadline()
WithDeadline()takes parent context and clock time as input.- WithDeadline returns a new context that closes its done channel when the machine's clock advances past the given deadline

exercises
https://go.dev/play/p/MCOWYEDdEku
package main
import (
"context"
"fmt"
"time"
)
type data struct {
result string
}
func main() {
// set deadline for goroutine to return computational result.
deadline := time.Now().Add(100 * time.Millisecond)
ctx, cancell := context.WithDeadline(context.Background(), deadline)
defer cancell()
compute := func() <-chan data {
ch := make(chan data)
go func() {
defer close(ch)
deadline, ok := ctx.Deadline()
if ok {
if deadline.Sub(time.Now().Add(50*time.Millisecond)) < 0 {
// deadline 時間比現在時間+50Millisecond 還小時
fmt.Println("not sufficient time given, terminating")
return
}
}
// Simulate work.
time.Sleep(50 * time.Millisecond)
// Report result.
select {
case ch <- data{"123"}:
case <-ctx.Done():
fmt.Println("work cancelled")
return
}
}()
return ch
}
// Wait for the work to finish. If it takes too long move on.
ch := compute()
d, ok := <-ch
if ok {
fmt.Println("work complete", d)
}
}
WithTimeout()
duration := 5 * time.Millisecond
ctx, cancel := context.WithTimeout(context.Background(),duration)
defer cancel()
WithTimeout()takes parent context and time duration as input.WithTimeout()returns a new context that closes its done channel after the given timeout duration.WithTimeout()is useful for setting a deadline on ther requests to backend servers.WithTimeout()is a wrapper over WithDeadline().func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { return WithDeadline(parent, time.Now().Add(timeout)) }
exercises
package main
import (
"context"
"database/sql"
"errors"
"fmt"
"log"
"net/http"
"time"
_ "github.com/lib/pq"
)
var db *sql.DB
func slowQuery(ctx context.Context) error {
_, err := db.ExecContext(ctx, "SELECT pg_sleep(5)")
return err
}
func slowHandler(w http.ResponseWriter, req *http.Request) {
start := time.Now()
err := slowQuery(req.Context())
if err != nil {
switch {
case errors.Is(err, context.Canceled):
log.Printf("Warning: %s\n", err.Error())
default:
log.Printf("Error: %s\n", err.Error())
}
return
}
fmt.Fprintln(w, "OK")
fmt.Printf("slowHandler took: %v\n", time.Since(start))
}
func main() {
var err error
connstr := "host=localhost port=5432 user=alice password=pa$$word dbname=wonderland sslmode=disable"
db, err = sql.Open("postgres", connstr)
if err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err = db.PingContext(ctx); err != nil {
log.Fatal(err)
}
srv := http.Server{
Addr: "localhost:8000",
WriteTimeout: 2 * time.Second,
Handler: http.TimeoutHandler(http.HandlerFunc(slowHandler), 1*time.Second, "Timeout!\n"),
}
if err := srv.ListenAndServe(); err != nil {
fmt.Printf("Server failed: %s\n", err)
}
}
Difference in using WithTimeout and WithDeadline
- WithTimeout() - timer countdown begins from the moment the context is created.
- WithDeadline() - Set explicit time when timer will expire.