Concurrency Control via context for Go Translations

Author: Sameer Ajmani | Address: blog.golang.org/context

Translator's Preface

The translation of the second official blog post is mainly about the context package for Go Concurrency Control.

Overall, I think Previous It is the foundation and core of Go concurrency.context is a set of easy-to-use libraries developed for goroutine control on the basis of the previous chapter.After all, only done channel s are passed between different goroutines, which really contains too little information.

This article briefly introduces the methods provided by context s and how they are used.Next, the use in real-world scenarios is illustrated through a search example.

The end of the article explains that there are third-party implementations besides the officially implemented context, such as github.com/context and Tomb But they have stopped updating since the official context appeared.Actually, the reason is very simple, after all, the government is generally stronger.Previously, go module management was flourishing, but more recently, the government has officially introduced its own solution, and other approaches may soon be phased out.

Actually, I don't think this article is good enough to read. I don't think it's gradual enough.A sudden example might be a bit confusing.

The translation text is as follows:

In the Go service, each request is handled by a separate goroutine, and each goroutine usually starts a new goroutine to perform additional work, such as database or RPC service access.The goroutine within the request needs to be able to share requests for data access, such as user authentication, authorization token, and request deadlines.If a request is cancelled or a timeout occurs, all goroutines within the scope of the request should exit immediately for resource recovery.

At Google, we developed a context package that allows us to easily transfer request data, cancel signals, and time-out information between goroutine s within requests.View details context.

This article details the use of the context package and provides a complete use case.

Context

The core of context is the Context type.Define as follows:

// A Context carries a deadline,cancellation signal,and request-scoped values
// across API. Its methods are safe for simultaneous use by multiple goroutines
// A Context can pass deadlines, cancel signals, and request data between API s, whether or not they are inter-protocol.
// The methods in the Context are all protocol safe.
type Context interface {
    // Done returns a channel that is closed when this context is cancelled
    // or times out.
    // The Done method returns a channel and Done closes when the context cancels or times out.
    Done() <-chan struct{}

    // Err indicates why this context was canceled, after the Done channel
    // is closed
    // After Done closes, Err can be used to indicate why the context was canceled
    Err() error

    // Deadline returns the time when this Context will be canceled, if any.
    // Cancel context when expired
    Deadline() (deadline time.Time, ok bool)

    // Value returns the value associated with key or nil if none
    Value(key interface{}) interface{}
}

Brief introduction, view details godoc.

The Done method returns a channel that can be used to receive a cancel signal from the context.When the channel is closed, the function that listens to the Done signal immediately discards the work currently in progress and returns.The Err method returns an error variable from which you can see why the context was cancelled. pipeline and cancelation Done channel is described in detail in this article.

Why Context doesn't have a cancel method is similar to Done channel's read-only reason that a goroutine that receives a cancel signal is not responsible for canceling the signal.In particular, when the parent promotes the child goroutine to perform an operation, the child cannot cancel the parent.Conversely, the WithCancel method (described next) provides a way to cancel the newly created Context.

Context is cooperative and concurrent secure.We can pass Context to any number of goroutines, and cancel can signal all goroutines.

The Deadline method lets the function decide whether to start work or not, and if the time remaining is too short, starting work is not worth it.In the code, we can set the timeout for IO operations through deadline.

The Value method allows context s to share request-scoped data between goroutine s, which needs to be concurrent and secure.

Derived Context

The context package provides multiple functions to derive a new Context from an existing Context instance.These contexts will form a tree structure, and as long as a Context is cancelled, all derived contexts will be cancelled.

The Background function returns a Context that is the root of any Context and cannot be cancelled.

// Background returns an empty Context. It is never canceled, has no deadline,
// and has no values. Background is typically used in main, init, and tests,
// and as the top-level Context for incoming requests.
// The Backgroundfunction returns an empty Context, which cannot be cancelled, has no deadline, and does not share data.Backgrounds are only used in main, init, or tests functions.
func Background() Context

WithCancel and WithTimeout derive new Context instances, which are cancelled earlier than the parent.The ontext instance associated with the request is cancelled after the request processing is complete.WithCancel can be used to cancel redundant requests when multiple copies of data requests are encountered.WithTimeout can be used to set timeout times when requesting back-end services.

// WithCancel returns a copy of parent whose Done channel is closed as soon as
// parent.Done is closed or cancel is called.
// WithCanal returns a copy of the parent Context, and its Done channel closes when the parent Done channel closes or cancel is called.
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

// A CancelFunc cancels a Context.
// CancelFunc is used to cancel Context
type CancelFunc func()

// WithTimeout returns a copy of parent whose Done channel is closed as soon as
// parent.Done is closed, cancel is called, or timeout elapses. The new
// Context's Deadline is the sooner of now+timeout and the parent's deadline, if
// any. If the timer is still running, the cancel function releases its
// resources.
// Returns a copy of the parent Context and CancellFunc, in which Done closes, the parent Done closes, cancel is called, and the timeout is reached.
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

WithValue provides a way to pass request-related data through the Context

// WithValue returns a copy of parent whose Value method returns val for key.
func WithValue(parent Context, key interface{}, val interface{}) Context

How does context work?The best way to do this is through a case study.

Case study: Google Web search

Demonstrates a case of implementing an HTTP service that handles requests like/search?Q=golang&timeout=1s.Timeout means cancel execution if request processing time exceeds the specified time.

The code mainly involves three package s:

  • server, main function entry and/search handler;
  • user ip, which implements the public function of exporting user ip from the request context;
  • Google, which implements the Search function and is responsible for sending search requests to Google;

Start introducing!

Package server

server handles requests like/search?q=golang, returns golang search results, handleSearch is the actual processing function that first initializes a Context named ctx and exits cancel through defer.If the request parameter contains timeout, the context is created with WithTimeout and automatically canceled after the timeout.

func handleSearch(w http.ResponseWriter, req *http.Request) {
    // ctx is the Context for this handler. Calling cancel closes the
    // cxt.Done channel, which is the cancellation signal for requests
    // started by this handler
    var (
        ctx context.Context
        cancel context.Context
    )

    timeout, err := time.ParseDuration(req.FromValue("timeout"))
    if err != nil {
        // the request has a timeout, so create a context that is
        // canceled automatically when the timeout expires.
        ctx, cancel = context.WithTimeout(context.Background(), timeout)
    } else {
        ctx, cancel = context.WithCancel(context.Background())
    }
    defer cancel() // Cancel ctx as soon as handlSearch returns.

Next, the processing function retrieves the query keywords and client IP from the request, which is achieved by calling the userip package function.At the same time, since requests for back-end services also require client IP, they are attached to ctx.

    // Check the search query
    query := req.FormValue("q")
    if query == "" {
        http.Error(w, "no query", http.StatusBadRequest)
        return
    }

    // Store the user IP in ctx for use by code in other packages.
    userIP, err := userip.FormRequest(req)
    if err != nil {
        http.Error(w, e.Error(), http.StatusBadRequest)
        return
    }

    ctx = userip.NewContext(ctx, userIP)

Call google.Search and pass in the ctx and query parameters.

    // Run the Google search and print the results
    start := time.Now()
    results, err := google.Search(ctx, query)
    elapsed := time.Since(start)

After the search succeeds, the handler renders the result page.

    if err := resultsTemplate.Execute(w, struct{
        Results     google.Results
        Timeout, Elapsed time.Duration
    }{
        Results: results,
        Timeout: timeout,
        Elaplsed: elaplsed,
    }); err != nil {
        log.Print(err)
        return
    }

Package userip

Two functions are provided in the userip package that export the user IP from the request and bind the user IP to the Context.Context contains a key-value mapping. Both key and value are of type interface {}. The key must support equal comparisons. Value is safe for concurrent processes.The userip package hides the map's details by typing the value in the Context, which is the client IP.To avoid key conflicts, userip defines a non-exportable type key.

// The key type is unexported to prevent collision with context keys defined in
// other package
type key int

// userIPkey is the context key for the user IP address. Its value of zero is
// arbitrary. If this package defined other context keys, they would have
// different integer values.
const userIPKye key = 0

The function FromRequest is responsible for exporting user IP from http.Request:

func FromRequest(req *http.Request) (net.IP, error) {
    ip, _, err := net.SplitHostPort(req.RemoteAddr)
    if err != nil {
        return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
    }

The function NewContext generates a Context with userIP:

func NewContext(ctx context.Context, userIP net.IP) context.Context {
    return context.WithValue(ctx, userIPKey, userIP)
}

FromContext is responsible for exporting userIP from the Context:

func FromContext(ctx context.Context) (net.IP. bool) {
    // ctx.Value returns nil if ctx has no value for the key;
    // the net.IP type assertion returns ok=false for nil
    userIP, ok := ctx.Value(userIPKey).(net.IP)
    return userIP, ok
}

Package google

google.Search is responsible for requests to the Google Web Search interface and for parsing the JSON data returned by the interface.It receives the Context type parameter CTX and returns immediately if ctx.Done is closed, even if the request is running.

The request parameters for the query include the query keyword and the user IP.

func Search(ctx context.Context, query string) (Results, error) {
    // Prepare the Google Search API request
    req, err := http.NewRequest("GET", "http://ajax.googleapis.com/ajax/services/search/web?v=1.0", nil)
    if err != nil {
        return nil, err
    }
    q := req.URL.Query()
    q.Set("q", query)

    // If ctx is carrying the user IP address, forward it to the server
    // Google APIs use the user IP to distinguish server-initiated requests 
    // from end-users requests
    if userIP, ok := userip.FromContext(ctx); ok {
        q.Set("userip", userIP.String())
    }
    req.URL.RawQuery = q.Encode()

The Search function uses a help function, httpDo, which is responsible for initiating HTTP requests and will be closed if ctx.Done closes, even if the request is executing.Search passes a closure function to httpDo to process the response results.

    var results Results
    err = httpDo(ctx, req, func(resp *http.Response, err error) error {
        if err != nil {
            return err
        }
        defer resp.Body.Close()

        // Parse the JSON search result.
        // https://developers.google.com/web-search/docs/#fonje
        var data struct {
            ResponseData struct {
                Results []struct {
                    TitleNoFormatting string
                    URL               string
                }
            }
        }
        if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
            return err
        }

        for _, res := range data.ResponseData.Results {
            results = append(results, Result{Title: res.TitleNoFormatting, URL: res.URL})
        }

        return nil
    })

    return results, err

The httpDo function opens a new goroutine responsible for the execution of HTTP requests and the processing of response results.If the request has not finished executing before goroutine exits, if ctx.Done closes, request execution will be cancelled.

func httpDo(ctx context.Context, req *http.Request, f func(*http.Request, error) error) error {
    // Run the HTTP request in a goroutine and pass the response to f.
    c := make(chan error, 1)
    req := req.WithContext(ctx)
    go func() { c <- f(http.DefaultClient.Do(req)) }()
    select {
    case <-ctx.Done():
        <- c
        return ctx.Err
    case  err := <-c:
        return err
    }
}

Adjust code based on ontext

Many service-side frameworks provide the appropriate packages and data types for requesting data transfer.We can write new implementation code based on the ontext interface to connect the framework to the processing function.

Translator's Note: The following are the third-party implementations of the two context s discussed by the developers, some of which require a simple understanding to fully understand.

For example, Gorilla's context Associated data binding is achieved by providing a key value mapping on the request.stay gorilla.go Provides an implementation of the Context whose Value method returns the value associated with a specific HTTP request.

Other packages provide cancellation support similar to Context.For example, Tomb Kill method cancels signaling by turning off Dying channel.Tomb also provides a way to wait for goroutine to exit, similar to sync.WaitGroup.stay tomb.go An implementation is provided to cancel the current Context when the parent Context cancels or Tomb is kill ed.

summary

At Google, for functions that receive or send request classes, we require that the Context be passed as the first parameter.That way, even GoCode from different teams works well.Context makes it easy for goroutine s to timeout and deregister, as well as to ensure the safe transfer of important data, such as secure credentials.

context-based service frameworks need to be implemented to help connect the framework and the consumer, who expect to receive context parameters from the framework.Instead, the client library receives the context parameter from the caller.Contexts make it easy for package developers to share their code and create more scalable services by establishing a common interface between requesting data and removing control.

Keywords: Go Google JSON github Database

Added by phenley on Mon, 12 Aug 2019 06:14:02 +0300