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