github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/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  }