github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/talks/2014/gotham-context.slide (about)

     1  Cancelation, Context, and Plumbing
     2  GothamGo 2014
     3  
     4  Sameer Ajmani
     5  sameer@golang.org
     6  
     7  * Video
     8  
     9  This talk was presented at GothamGo in New York City, November 2014.
    10  
    11  .link http://vimeo.com/115309491 Watch the talk on Vimeo
    12  
    13  * Introduction
    14  
    15  In Go servers, each incoming request is handled in its own goroutine.
    16  
    17  Handler code needs access to request-specific values:
    18  
    19  - security credentials
    20  - request deadline
    21  - operation priority
    22  
    23  When the request completes or times out, its work should be canceled.
    24  
    25  * Cancelation
    26  
    27  Abandon work when the caller no longer needs the result.
    28  
    29  - user navigates away, closes connection
    30  - operation exceeds its deadline
    31  - when using hedged requests, cancel the laggards
    32  
    33  Efficiently canceling unneeded work saves resources.
    34  
    35  * Cancelation is advisory
    36  
    37  Cancelation does not stop execution or trigger panics.
    38  
    39  Cancelation informs code that its work is no longer needed.
    40  
    41  Code checks for cancelation and decides what to do:
    42  shut down, clean up, return errors.
    43  
    44  * Cancelation is transitive
    45  
    46  .image gotham-context/transitive.svg
    47  
    48  * Cancelation affects all APIs on the request path
    49  
    50  Network protocols support cancelation.
    51  
    52  - HTTP: close the connection
    53  - RPC: send a control message
    54  
    55  APIs above network need cancelation, too.
    56  
    57  - Database clients
    58  - Network file system clients
    59  - Cloud service clients
    60  
    61  And all the layers atop those, up to the UI.
    62  
    63  *Goal:* provide a uniform cancelation API that works across package boundaries.
    64  
    65  * Cancelation APIs
    66  
    67  Many Go APIs support cancelation and deadlines already.
    68  
    69  Go APIs are synchronous, so cancelation comes from another goroutine.
    70  
    71  Method on the connection or client object:
    72  
    73    // goroutine #1
    74    result, err := conn.Do(req)
    75  
    76    // goroutine #2
    77    conn.Cancel(req)
    78  
    79  Method on the request object:
    80  
    81    // goroutine #1
    82    result, err := conn.Do(req)
    83  
    84    // goroutine #2
    85    req.Cancel()
    86  
    87  * Cancelation APIs (continued)
    88  
    89  Method on the pending result object:
    90  
    91    // goroutine #1
    92    pending := conn.Start(req)
    93    ...
    94    result, err := pending.Result()
    95  
    96    // goroutine #2
    97    pending.Cancel()
    98  
    99  
   100  Different cancelation APIs in each package are a headache.
   101  
   102  We need one that's independent of package or transport:
   103  
   104    // goroutine #1
   105    result, err := conn.Do(x, req)
   106  
   107    // goroutine #2
   108    x.Cancel()
   109  
   110  * Context
   111  
   112  A `Context` carries a cancelation signal and request-scoped values to all functions running on behalf of the same task.  It's safe for concurrent access.
   113  
   114  .code gotham-context/interface.go /type Context/,/^}/
   115  
   116  *Idiom:* pass `ctx` as the first argument to a function.
   117  
   118    import "golang.org/x/net/context"
   119  
   120    // ReadFile reads file name and returns its contents.
   121    // If ctx.Done is closed, ReadFile returns ctx.Err immediately.
   122    func ReadFile(ctx context.Context, name string) ([]byte, error)
   123  
   124  Examples and discussion in [[http://blog.golang.org/context][blog.golang.org/context]].
   125  
   126  * Contexts are hierarchical
   127  
   128  `Context` has no `Cancel` method; obtain a cancelable `Context` using `WithCancel`:
   129  
   130  .code gotham-context/interface.go /WithCancel /,/func WithCancel/
   131  
   132  Passing a `Context` to a function does not pass the ability to cancel that `Context`.
   133  
   134    // goroutine #1
   135    ctx, cancel := context.WithCancel(parent)
   136    ...
   137    data, err := ReadFile(ctx, name)
   138  
   139    // goroutine #2
   140    cancel()
   141  
   142  Contexts form a tree, any subtree of which can be canceled.
   143  
   144  * Why does Done return a channel?
   145  
   146  Closing a channel works well as a broadcast signal.
   147  
   148  _After_the_last_value_has_been_received_from_a_closed_channel_c,_any_receive_from_c_will_succeed_without_blocking,_returning_the_zero_value_for_the_channel_element._
   149  
   150  Any number of goroutines can `select` on `<-ctx.Done()`.
   151  
   152  Examples and discussion in in [[http://blog.golang.org/pipelines][blog.golang.org/pipelines]].
   153  
   154  Using `close` requires care.
   155  
   156  - closing a nil channel panics
   157  - closing a closed channel panics
   158  
   159  `Done` returns a receive-only channel that can only be canceled using the `cancel` function returned by `WithCancel`.  It ensures the channel is closed exactly once.
   160  
   161  * Context values
   162  
   163  Contexts carry request-scoped values across API boundaries.
   164  
   165  - deadline
   166  - cancelation signal
   167  - security credentials
   168  - distributed trace IDs
   169  - operation priority
   170  - network QoS label
   171  
   172  RPC clients encode `Context` values onto the wire.
   173  
   174  RPC servers decode them into a new `Context` for the handler function.
   175  
   176  * Replicated Search
   177  
   178  Example from [[https://talks.golang.org/2012/concurrency.slide][Go Concurrency Patterns]].
   179  
   180  .code gotham-context/first.go /START1/,/STOP1/
   181  
   182  Remaining searches may continue running after First returns.
   183  
   184  * Cancelable Search
   185  
   186  .code gotham-context/first-context.go /START1/,/STOP1/
   187  
   188  * Context plumbing
   189  
   190  *Goal:* pass a `Context` parameter from each inbound RPC at a server through the call stack to each outgoing RPC.
   191  
   192  .code gotham-context/before.go /START/,/END/
   193  
   194  * Context plumbing (after)
   195  
   196  .code gotham-context/after.go /START/,/END/
   197  
   198  * Problem: Existing and future code
   199  
   200  Google has millions of lines of Go code.
   201  
   202  We've retrofitted the internal RPC and distributed file system APIs to take a Context.
   203  
   204  Lots more to do, growing every day.
   205  
   206  * Why not use (something like) thread local storage?
   207  
   208  C++ and Java pass request state in thread-local storage.
   209  
   210  Requires no API changes, but ...
   211  requires custom thread and callback libraries.
   212  
   213  Mostly works, except when it doesn't. Failures are hard to debug.
   214  
   215  Serious consequences if credential-passing bugs affect user privacy.
   216  
   217  "Goroutine-local storage" doesn't exist, and even if it did,
   218  request processing may flow between goroutines via channels.
   219  
   220  We won't sacrifice clarity for convenience.
   221  
   222  * In Go, pass Context explicitly
   223  
   224  Easy to tell when a Context passes between functions, goroutines, and processes.
   225  
   226  Invest up front to make the system easier to maintain:
   227  
   228  - update relevant functions to accept a `Context`
   229  - update function calls to provide a `Context`
   230  - update interface methods and implementations
   231  
   232  Go's awesome tools can help.
   233  
   234  * Automated refactoring
   235  
   236  *Initial*State:*
   237  
   238  Pass `context.TODO()` to outbound RPCs.
   239  
   240  `context.TODO()` is a sentinel for static analysis tools. Use it wherever a `Context` is needed but there isn't one available.
   241  
   242  *Iteration:*
   243  
   244  For each function `F(x)` whose body contains `context.TODO()`,
   245  
   246  - add a `Context` parameter to `F`
   247  - update callers to use `F(context.TODO(),`x)`
   248  - if the caller has a `Context` available, pass it to `F` instead
   249  
   250  Repeat until `context.TODO()` is gone.
   251  
   252  * Finding relevant functions
   253  
   254  The [[http://godoc.org/golang.org/x/tools/cmd/callgraph][golang.org/x/tools/cmd/callgraph]] tool constructs the call graph of a Go program.
   255  
   256  It uses whole-program pointer analysis to find dynamic calls (via interfaces or function values).
   257  
   258  *For*context*plumbing:*
   259  
   260  Find all functions on call paths from `Context` _suppliers_ (inbound RPCs) to `Context` _consumers_ (`context.TODO`).
   261  
   262  * Updating function calls
   263  
   264  To change add all `F(x)` to `F(context.TODO(),`x)`:
   265  
   266  - define `FContext(ctx,`x)`
   267  - `F(x)` → `FContext(context.TODO(),`x)`
   268  - change `F(x)` to `F(ctx,`x)`
   269  - `FContext(context.TODO(),`x)` → `F(context.TODO(),`x)`
   270  - remove `FContext(ctx,`x)`
   271  
   272  * gofmt -r
   273  
   274  Works well for simple replacements:
   275  
   276    gofmt -r 'pkg.F(a) -> pkg.FContext(context.TODO(), a)'
   277  
   278  But this is too imprecise for methods.  There may be many methods named M:
   279  
   280    gofmt -r 'x.M(y) -> x.MContext(context.TODO(), y)'
   281  
   282  We want to restrict the transformation to specific method signatures.
   283  
   284  * The eg tool
   285  
   286  The [[http://godoc.org/golang.org/x/tools/cmd/eg][golang.org/x/tools/cmd/eg]] tool performs precise example-based refactoring.
   287  
   288  The `before` expression specifies a pattern and the `after` expression its replacement.
   289  
   290  To replace `x.M(y)` with `x.MContext(context.TODO(),`y)`:
   291  
   292  .code gotham-context/eg.go
   293  
   294  * Dealing with interfaces
   295  
   296  We need to update dynamic calls to `x.M(y)`.
   297  
   298  If `M` called via interface `I`, then `I.M` also needs to change.  The eg tool can update call sites with receiver type `I`.
   299  
   300  When we change `I`, we need to update all of its implementations.
   301  
   302  Find types assignable to `I` using [[http://godoc.org/golang.org/x/tools/go/types][golang.org/x/tools/go/types]].
   303  
   304  More to do here.
   305  
   306  * What about the standard library?
   307  
   308  The Go 1.0 compatibility guarantee means we will not break existing code.
   309  
   310  Interfaces like `io.Reader` and `io.Writer` are widely used.
   311  
   312  For Google files, used a currying approach:
   313  
   314    f, err := file.Open(ctx, "/gfs/cell/path")
   315    ...
   316    fio := f.IO(ctx)  // returns an io.ReadWriteCloser that passes ctx
   317    data, err := ioutil.ReadAll(fio)
   318  
   319  For versioned public packages, add `Context` parameters in a new API version and provide `eg` templates to insert `context.TODO()`.
   320  
   321  More to do here.
   322  
   323  * Conclusion
   324  
   325  Cancelation needs a uniform API across package boundaries.
   326  
   327  Retrofitting code is hard, but Go is tool-friendly.
   328  
   329  New code should use `Context`.
   330  
   331  Links:
   332  
   333  - [[http://golang.org/x/net/context][golang.org/x/net/context]] - package
   334  - [[http://blog.golang.org/context][blog.golang.org/context]] - blog post
   335  - [[http://golang.org/x/tools/cmd/eg][golang.org/x/tools/cmd/eg]] - eg tool