github.com/m3db/m3@v1.5.0/src/query/graphite/context/context.go (about)

     1  // Copyright (c) 2019 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  	ctx "context"
    25  	"sync"
    26  )
    27  
    28  // Closer is an interface implemented by objects that should be closed
    29  // when a context completes
    30  type Closer interface {
    31  	Close() error
    32  }
    33  
    34  // Context is an execution context.  A Context holds deadlines and request
    35  // scoped values.  Contexts should be scoped to a request;
    36  // created when the request begins and closed when the request completes.
    37  // Contexts are safe for use by multiple goroutines, but should not span
    38  // multiple requests.
    39  type Context interface {
    40  	Closer
    41  
    42  	// SetRequestContext sets the given context as the request context for this
    43  	// execution context. This is used for calls to the m3 storage wrapper.
    44  	SetRequestContext(ctx.Context)
    45  
    46  	// RequestContext will provide the wrapped request context. Used for calls
    47  	// to m3 storage wrapper.
    48  	RequestContext() ctx.Context
    49  
    50  	// RegisterCloser registers an object that should be closed when this
    51  	// context is closed.  Can be used to cleanup per-request objects.
    52  	RegisterCloser(closer Closer)
    53  
    54  	// AddAsyncTask allows asynchronous tasks to be enqueued that will
    55  	// ensure this context does not call its registered closers until
    56  	// the tasks are all complete
    57  	AddAsyncTasks(count int)
    58  
    59  	// DoneAsyncTask signals that an asynchronous task is complete, when
    60  	// all asynchronous tasks complete if the context has been closed and
    61  	// avoided calling its registered closers it will finally call them
    62  	DoneAsyncTask()
    63  }
    64  
    65  // New creates a new context
    66  func New() Context {
    67  	return &context{}
    68  }
    69  
    70  type contextStatus int
    71  
    72  const (
    73  	contextStatusOpen contextStatus = iota
    74  	contextStatusClosed
    75  )
    76  
    77  type context struct {
    78  	sync.RWMutex
    79  	closers    []Closer
    80  	status     contextStatus
    81  	asyncTasks int
    82  	reqCtx     ctx.Context
    83  }
    84  
    85  // Close closes the context
    86  func (c *context) Close() error {
    87  	finalize := false
    88  
    89  	c.Lock()
    90  	if c.status == contextStatusOpen {
    91  		if c.asyncTasks == 0 {
    92  			finalize = true
    93  		}
    94  		c.status = contextStatusClosed
    95  	}
    96  	c.Unlock()
    97  
    98  	if finalize {
    99  		return c.callClosers()
   100  	}
   101  	return nil
   102  }
   103  
   104  // SetRequestContext sets the given context as the request context for this
   105  // execution context. This is used for calls to the m3 storage wrapper.
   106  func (c *context) SetRequestContext(reqCtx ctx.Context) {
   107  	c.Lock()
   108  	c.reqCtx = reqCtx
   109  	c.Unlock()
   110  }
   111  
   112  // RequestContext will provide the wrapped request context. Used for calls
   113  // to m3 storage wrapper.
   114  func (c *context) RequestContext() ctx.Context {
   115  	c.RLock()
   116  	r := c.reqCtx
   117  	c.RUnlock()
   118  	return r
   119  }
   120  
   121  // RegisterCloser registers a new Closer with the context
   122  func (c *context) RegisterCloser(closer Closer) {
   123  	c.Lock()
   124  	c.closers = append(c.closers, closer)
   125  	c.Unlock()
   126  }
   127  
   128  // AddAsyncTasks adds tracked asynchronous task(s)
   129  func (c *context) AddAsyncTasks(count int) {
   130  	c.Lock()
   131  	c.asyncTasks += count
   132  	c.Unlock()
   133  }
   134  
   135  // DoneAsyncTask marks a single tracked asynchronous task complete
   136  func (c *context) DoneAsyncTask() {
   137  	finalize := false
   138  
   139  	c.Lock()
   140  	c.asyncTasks--
   141  	if c.asyncTasks == 0 && c.status == contextStatusClosed {
   142  		finalize = true
   143  	}
   144  	c.Unlock()
   145  
   146  	if finalize {
   147  		c.callClosers()
   148  	}
   149  }
   150  
   151  func (c *context) callClosers() error {
   152  	var firstErr error
   153  	c.RLock()
   154  	for _, closer := range c.closers {
   155  		if err := closer.Close(); err != nil {
   156  			//FIXME: log.Errorf("could not close %v: %v", closer, err)
   157  			if firstErr == nil {
   158  				firstErr = err
   159  			}
   160  		}
   161  	}
   162  	c.RUnlock()
   163  	return firstErr
   164  }