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.

Image

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

Image Image

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.
© Kimi Tsai all right reserved.            Updated : 2023-07-12 09:04:54

results matching ""

    No results matching ""

    results matching ""

      No results matching ""