github.com/m3db/m3@v1.5.0/src/x/context/context.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package context 22 23 import ( 24 stdctx "context" 25 "fmt" 26 "sync" 27 28 xopentracing "github.com/m3db/m3/src/x/opentracing" 29 xresource "github.com/m3db/m3/src/x/resource" 30 31 lightstep "github.com/lightstep/lightstep-tracer-go" 32 "github.com/opentracing/opentracing-go" 33 "github.com/opentracing/opentracing-go/ext" 34 "github.com/opentracing/opentracing-go/mocktracer" 35 "github.com/uber/jaeger-client-go" 36 ) 37 38 const ( 39 maxDistanceFromRootContext = 100 40 ) 41 42 var ( 43 noopTracer opentracing.NoopTracer 44 45 errSpanTooDeep = fmt.Errorf("span created exceeds maximum depth allowed (%d)", maxDistanceFromRootContext) 46 ) 47 48 // NB(r): using golang.org/x/net/context is too GC expensive. 49 // Instead, we just embed one. 50 type ctx struct { 51 sync.RWMutex 52 53 goCtx stdctx.Context 54 pool contextPool 55 done bool 56 wg sync.WaitGroup 57 finalizeables *finalizeableList 58 parent Context 59 distanceFromRoot uint16 60 checkedAndNotSampled bool 61 } 62 63 type finalizeable struct { 64 finalizer xresource.Finalizer 65 closer xresource.SimpleCloser 66 } 67 68 // NewWithGoContext creates a new context with the provided go ctx. 69 func NewWithGoContext(goCtx stdctx.Context) Context { 70 ctx := newContext() 71 ctx.SetGoContext(goCtx) 72 return ctx 73 } 74 75 // NewBackground creates a new context with a Background go ctx. 76 func NewBackground() Context { 77 return NewWithGoContext(stdctx.Background()) 78 } 79 80 // NewPooledContext returns a new context that is returned to a pool when closed. 81 func newPooledContext(pool contextPool) Context { 82 return &ctx{pool: pool} 83 } 84 85 // newContext returns an empty ctx 86 func newContext() *ctx { 87 return &ctx{} 88 } 89 90 func (c *ctx) GoContext() stdctx.Context { 91 return c.goCtx 92 } 93 94 func (c *ctx) SetGoContext(v stdctx.Context) { 95 c.goCtx = v 96 } 97 98 func (c *ctx) IsClosed() bool { 99 parent := c.parentCtx() 100 if parent != nil { 101 return parent.IsClosed() 102 } 103 104 c.RLock() 105 done := c.done 106 c.RUnlock() 107 108 return done 109 } 110 111 func (c *ctx) RegisterFinalizer(f xresource.Finalizer) { 112 parent := c.parentCtx() 113 if parent != nil { 114 parent.RegisterFinalizer(f) 115 return 116 } 117 118 c.registerFinalizeable(finalizeable{finalizer: f}) 119 } 120 121 func (c *ctx) RegisterCloser(f xresource.SimpleCloser) { 122 parent := c.parentCtx() 123 if parent != nil { 124 parent.RegisterCloser(f) 125 return 126 } 127 128 c.registerFinalizeable(finalizeable{closer: f}) 129 } 130 131 func (c *ctx) registerFinalizeable(f finalizeable) { 132 if c.Lock(); c.done { 133 c.Unlock() 134 return 135 } 136 137 if c.finalizeables == nil { 138 if c.pool != nil { 139 c.finalizeables = c.pool.getFinalizeablesList() 140 } else { 141 c.finalizeables = newFinalizeableList(nil) 142 } 143 } 144 c.finalizeables.PushBack(f) 145 146 c.Unlock() 147 } 148 149 func (c *ctx) numFinalizeables() int { 150 if c.finalizeables == nil { 151 return 0 152 } 153 return c.finalizeables.Len() 154 } 155 156 func (c *ctx) DependsOn(blocker Context) { 157 parent := c.parentCtx() 158 if parent != nil { 159 parent.DependsOn(blocker) 160 return 161 } 162 163 c.Lock() 164 165 if !c.done { 166 c.wg.Add(1) 167 blocker.RegisterFinalizer(c) 168 } 169 170 c.Unlock() 171 } 172 173 // Finalize handles a call from another context that was depended upon closing. 174 func (c *ctx) Finalize() { 175 c.wg.Done() 176 } 177 178 type closeMode int 179 180 const ( 181 closeAsync closeMode = iota 182 closeBlock 183 ) 184 185 type returnToPoolMode int 186 187 const ( 188 returnToPool returnToPoolMode = iota 189 reuse 190 ) 191 192 func (c *ctx) Close() { 193 returnMode := returnToPool 194 parent := c.parentCtx() 195 if parent != nil { 196 if !parent.IsClosed() { 197 parent.Close() 198 } 199 c.tryReturnToPool(returnMode) 200 return 201 } 202 203 c.close(closeAsync, returnMode) 204 } 205 206 func (c *ctx) BlockingClose() { 207 returnMode := returnToPool 208 parent := c.parentCtx() 209 if parent != nil { 210 if !parent.IsClosed() { 211 parent.BlockingClose() 212 } 213 c.tryReturnToPool(returnMode) 214 return 215 } 216 217 c.close(closeBlock, returnMode) 218 } 219 220 func (c *ctx) BlockingCloseReset() { 221 returnMode := reuse 222 parent := c.parentCtx() 223 if parent != nil { 224 if !parent.IsClosed() { 225 parent.BlockingCloseReset() 226 } 227 c.tryReturnToPool(returnMode) 228 return 229 } 230 231 c.close(closeBlock, returnMode) 232 c.Reset() 233 } 234 235 func (c *ctx) close(mode closeMode, returnMode returnToPoolMode) { 236 if c.Lock(); c.done { 237 c.Unlock() 238 return 239 } 240 241 c.done = true 242 243 // Capture finalizeables to avoid concurrent r/w if Reset 244 // is used after a caller waits for the finalizers to finish 245 f := c.finalizeables 246 c.finalizeables = nil 247 248 c.Unlock() 249 250 if f == nil { 251 c.tryReturnToPool(returnMode) 252 return 253 } 254 255 switch mode { 256 case closeAsync: 257 go c.finalize(f, returnMode) 258 case closeBlock: 259 c.finalize(f, returnMode) 260 } 261 } 262 263 func (c *ctx) finalize(f *finalizeableList, returnMode returnToPoolMode) { 264 // Wait for dependencies. 265 c.wg.Wait() 266 267 // Now call finalizers. 268 for elem := f.Front(); elem != nil; elem = elem.Next() { 269 if elem.Value.finalizer != nil { 270 elem.Value.finalizer.Finalize() 271 } 272 if elem.Value.closer != nil { 273 elem.Value.closer.Close() 274 } 275 } 276 277 if c.pool != nil { 278 // NB(r): Always return finalizeables, only the 279 // context itself might want to be reused immediately. 280 c.pool.putFinalizeablesList(f) 281 } 282 283 c.tryReturnToPool(returnMode) 284 } 285 286 func (c *ctx) Reset() { 287 parent := c.parentCtx() 288 if parent != nil { 289 parent.Reset() 290 return 291 } 292 293 c.Lock() 294 c.done, c.finalizeables, c.goCtx, c.checkedAndNotSampled = false, nil, stdctx.Background(), false 295 c.distanceFromRoot = 0 296 c.Unlock() 297 } 298 299 func (c *ctx) tryReturnToPool(returnMode returnToPoolMode) { 300 if c.pool == nil || returnMode != returnToPool { 301 return 302 } 303 304 c.Reset() 305 c.pool.Put(c) 306 } 307 308 func (c *ctx) newChildContext() Context { 309 var childCtx *ctx 310 if c.pool != nil { 311 pooled, ok := c.pool.Get().(*ctx) 312 if ok { 313 childCtx = pooled 314 } 315 } 316 317 if childCtx == nil { 318 childCtx = newContext() 319 } 320 321 childCtx.setParentCtx(c) 322 return childCtx 323 } 324 325 func (c *ctx) setParentCtx(parentCtx Context) { 326 c.Lock() 327 c.parent = parentCtx 328 c.distanceFromRoot = parentCtx.DistanceFromRootContext() + 1 329 c.Unlock() 330 } 331 332 func (c *ctx) parentCtx() Context { 333 c.RLock() 334 parent := c.parent 335 c.RUnlock() 336 337 return parent 338 } 339 340 func (c *ctx) DistanceFromRootContext() uint16 { 341 c.RLock() 342 distanceFromRootContext := c.distanceFromRoot 343 c.RUnlock() 344 345 return distanceFromRootContext 346 } 347 348 func (c *ctx) CheckedAndNotSampled() bool { 349 c.RLock() 350 checkedAndNotSampled := c.checkedAndNotSampled 351 c.RUnlock() 352 353 return checkedAndNotSampled 354 } 355 356 func (c *ctx) setCheckedAndNotSampled(b bool) { 357 c.Lock() 358 c.checkedAndNotSampled = b 359 c.Unlock() 360 } 361 362 func (c *ctx) StartSampledTraceSpan(name string) (Context, opentracing.Span, bool) { 363 if c.CheckedAndNotSampled() || c.DistanceFromRootContext() >= maxDistanceFromRootContext { 364 return c, noopTracer.StartSpan(name), false 365 } 366 goCtx := c.GoContext() 367 368 childGoCtx, span, sampled := StartSampledTraceSpan(goCtx, name) 369 if !sampled { 370 c.setCheckedAndNotSampled(true) 371 return c, noopTracer.StartSpan(name), false 372 } 373 374 child := c.newChildContext() 375 child.SetGoContext(childGoCtx) 376 if child.DistanceFromRootContext() == maxDistanceFromRootContext { 377 ext.LogError(span, errSpanTooDeep) 378 } 379 return child, span, true 380 } 381 382 func (c *ctx) StartTraceSpan(name string) (Context, opentracing.Span) { 383 ctx, sp, _ := c.StartSampledTraceSpan(name) 384 return ctx, sp 385 } 386 387 // StartSampledTraceSpan starts a span that may or may not be sampled and will 388 // return whether it was sampled or not. 389 func StartSampledTraceSpan(ctx stdctx.Context, name string, opts ...opentracing.StartSpanOption) (stdctx.Context, opentracing.Span, bool) { 390 sp, spCtx := xopentracing.StartSpanFromContext(ctx, name, opts...) 391 sampled := spanIsSampled(sp) 392 if !sampled { 393 return ctx, noopTracer.StartSpan(name), false 394 } 395 return spCtx, sp, true 396 } 397 398 func spanIsSampled(sp opentracing.Span) bool { 399 if sp == nil { 400 return false 401 } 402 403 // Until OpenTracing supports the `IsSampled()` method, we need to cast to a Jaeger/Lightstep/etc. spans. 404 // See https://github.com/opentracing/specification/issues/92 for more information. 405 spanCtx := sp.Context() 406 jaegerSpCtx, ok := spanCtx.(jaeger.SpanContext) 407 if ok && jaegerSpCtx.IsSampled() { 408 return true 409 } 410 411 lightstepSpCtx, ok := spanCtx.(lightstep.SpanContext) 412 if ok && lightstepSpCtx.TraceID != 0 { 413 return true 414 } 415 416 mockSpCtx, ok := spanCtx.(mocktracer.MockSpanContext) 417 if ok && mockSpCtx.Sampled { 418 return true 419 } 420 421 return false 422 }