github.com/m3db/m3@v1.5.0/src/dbnode/client/fetch_state.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 client
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"sync"
    27  
    28  	"github.com/m3db/m3/src/dbnode/encoding"
    29  	"github.com/m3db/m3/src/dbnode/namespace"
    30  	"github.com/m3db/m3/src/dbnode/storage/index"
    31  	"github.com/m3db/m3/src/dbnode/topology"
    32  	"github.com/m3db/m3/src/dbnode/x/xpool"
    33  	xerrors "github.com/m3db/m3/src/x/errors"
    34  	"github.com/m3db/m3/src/x/ident"
    35  	"github.com/m3db/m3/src/x/serialize"
    36  	xtime "github.com/m3db/m3/src/x/time"
    37  )
    38  
    39  type fetchStateType byte
    40  
    41  const (
    42  	fetchTaggedFetchState fetchStateType = iota
    43  	aggregateFetchState
    44  )
    45  
    46  const (
    47  	maxUint = ^uint(0)
    48  	maxInt  = int(maxUint >> 1)
    49  )
    50  
    51  var (
    52  	errFetchStateStillProcessing = errors.New("[invariant violated] fetch " +
    53  		"state is still processing, unable to create response")
    54  )
    55  
    56  type fetchState struct {
    57  	sync.Cond
    58  	sync.Mutex
    59  	refCounter
    60  
    61  	fetchTaggedOp *fetchTaggedOp
    62  	aggregateOp   *aggregateOp
    63  
    64  	nsID                 ident.ID
    65  	tagResultAccumulator fetchTaggedResultAccumulator
    66  	err                  error
    67  
    68  	pool fetchStatePool
    69  
    70  	// NB: stateType determines which type of op this fetchState
    71  	// is used for - fetchTagged or Aggregate.
    72  	stateType fetchStateType
    73  
    74  	done bool
    75  }
    76  
    77  func newFetchState(pool fetchStatePool) *fetchState {
    78  	f := &fetchState{
    79  		tagResultAccumulator: newFetchTaggedResultAccumulator(),
    80  		pool:                 pool,
    81  	}
    82  	f.destructorFn = f.close // Set refCounter completion as close
    83  	f.L = f                  // Set the embedded condition locker to the embedded mutex
    84  	return f
    85  }
    86  
    87  func (f *fetchState) close() {
    88  	if f.nsID != nil {
    89  		f.nsID.Finalize()
    90  		f.nsID = nil
    91  	}
    92  	if f.fetchTaggedOp != nil {
    93  		f.fetchTaggedOp.decRef()
    94  		f.fetchTaggedOp = nil
    95  	}
    96  	if f.aggregateOp != nil {
    97  		f.aggregateOp.decRef()
    98  		f.aggregateOp = nil
    99  	}
   100  	f.err = nil
   101  	f.done = false
   102  	f.tagResultAccumulator.Clear()
   103  
   104  	if f.pool == nil {
   105  		return
   106  	}
   107  	f.pool.Put(f)
   108  }
   109  
   110  func (f *fetchState) ResetFetchTagged(
   111  	startTime xtime.UnixNano,
   112  	endTime xtime.UnixNano,
   113  	op *fetchTaggedOp, topoMap topology.Map,
   114  	majority int,
   115  	consistencyLevel topology.ReadConsistencyLevel,
   116  ) {
   117  	op.incRef() // take a reference to the provided op
   118  	f.fetchTaggedOp = op
   119  	f.stateType = fetchTaggedFetchState
   120  	f.tagResultAccumulator.Reset(startTime, endTime, topoMap, majority, consistencyLevel)
   121  }
   122  
   123  func (f *fetchState) ResetAggregate(
   124  	startTime xtime.UnixNano,
   125  	endTime xtime.UnixNano,
   126  	op *aggregateOp, topoMap topology.Map,
   127  	majority int,
   128  	consistencyLevel topology.ReadConsistencyLevel,
   129  ) {
   130  	op.incRef() // take a reference to the provided op
   131  	f.aggregateOp = op
   132  	f.stateType = aggregateFetchState
   133  	f.tagResultAccumulator.Reset(startTime, endTime, topoMap, majority, consistencyLevel)
   134  }
   135  
   136  func (f *fetchState) completionFn(
   137  	result interface{},
   138  	resultErr error,
   139  ) {
   140  	if IsBadRequestError(resultErr) {
   141  		// Wrap with invalid params and non-retryable so it is
   142  		// not retried.
   143  		resultErr = xerrors.NewInvalidParamsError(resultErr)
   144  		resultErr = xerrors.NewNonRetryableError(resultErr)
   145  	}
   146  
   147  	f.Lock()
   148  	defer func() {
   149  		f.Unlock()
   150  		f.decRef() // release ref held onto by the hostQueue (via op.completionFn)
   151  	}()
   152  
   153  	if f.done {
   154  		// i.e. we've already failed, no need to continue processing any additional
   155  		// responses we receive
   156  		return
   157  	}
   158  
   159  	var (
   160  		done bool
   161  		err  error
   162  	)
   163  	switch r := result.(type) {
   164  	case fetchTaggedResultAccumulatorOpts:
   165  		done, err = f.tagResultAccumulator.AddFetchTaggedResponse(r, resultErr)
   166  	case aggregateResultAccumulatorOpts:
   167  		done, err = f.tagResultAccumulator.AddAggregateResponse(r, resultErr)
   168  	default:
   169  		// should never happen
   170  		done = true
   171  		err = fmt.Errorf(
   172  			"[invariant violated] expected result to be one of %v, received: %v",
   173  			[]string{"fetchTaggedResultAccumulatorOpts", "aggregateResultAccumulatorOpts"},
   174  			result)
   175  	}
   176  
   177  	if done {
   178  		f.markDoneWithLock(err)
   179  	}
   180  }
   181  
   182  func (f *fetchState) markDoneWithLock(err error) {
   183  	f.done = true
   184  	f.err = err
   185  	f.Signal()
   186  }
   187  
   188  func (f *fetchState) asTaggedIDsIterator(
   189  	pools fetchTaggedPools,
   190  	limit int,
   191  ) (TaggedIDsIterator, FetchResponseMetadata, error) {
   192  	f.Lock()
   193  	defer f.Unlock()
   194  
   195  	if expected := fetchTaggedFetchState; f.stateType != expected {
   196  		return nil, FetchResponseMetadata{},
   197  			fmt.Errorf("unexpected fetch state: expected=%v, actual=%v",
   198  				expected, f.stateType)
   199  	}
   200  
   201  	if !f.done {
   202  		return nil, FetchResponseMetadata{}, errFetchStateStillProcessing
   203  	}
   204  
   205  	if err := f.err; err != nil {
   206  		return nil, FetchResponseMetadata{}, err
   207  	}
   208  
   209  	if limit == 0 {
   210  		limit = maxInt
   211  	}
   212  	return f.tagResultAccumulator.AsTaggedIDsIterator(limit, pools)
   213  }
   214  
   215  func (f *fetchState) asEncodingSeriesIterators(
   216  	pools fetchTaggedPools,
   217  	descr namespace.SchemaDescr,
   218  	opts index.IterationOptions,
   219  	limit int,
   220  ) (encoding.SeriesIterators, FetchResponseMetadata, error) {
   221  	f.Lock()
   222  	defer f.Unlock()
   223  
   224  	if expected := fetchTaggedFetchState; f.stateType != expected {
   225  		return nil, FetchResponseMetadata{},
   226  			fmt.Errorf("unexpected fetch state: expected=%v, actual=%v",
   227  				expected, f.stateType)
   228  	}
   229  
   230  	if !f.done {
   231  		return nil, FetchResponseMetadata{}, errFetchStateStillProcessing
   232  	}
   233  
   234  	if err := f.err; err != nil {
   235  		return nil, FetchResponseMetadata{}, err
   236  	}
   237  
   238  	if limit == 0 {
   239  		limit = maxInt
   240  	}
   241  	return f.tagResultAccumulator.AsEncodingSeriesIterators(limit, pools, descr, opts)
   242  }
   243  
   244  func (f *fetchState) asAggregatedTagsIterator(pools fetchTaggedPools, limit int) (
   245  	AggregatedTagsIterator, FetchResponseMetadata, error) {
   246  	f.Lock()
   247  	defer f.Unlock()
   248  
   249  	if expected := aggregateFetchState; f.stateType != expected {
   250  		return nil, FetchResponseMetadata{},
   251  			fmt.Errorf("unexpected fetch state: expected=%v, actual=%v",
   252  				expected, f.stateType)
   253  	}
   254  
   255  	if !f.done {
   256  		return nil, FetchResponseMetadata{}, errFetchStateStillProcessing
   257  	}
   258  
   259  	if err := f.err; err != nil {
   260  		return nil, FetchResponseMetadata{}, err
   261  	}
   262  
   263  	if limit == 0 {
   264  		limit = maxInt
   265  	}
   266  	return f.tagResultAccumulator.AsAggregatedTagsIterator(limit, pools)
   267  }
   268  
   269  // NB(prateek): this is backed by the sessionPools struct, but we're restricting it to a narrow
   270  // interface to force the fetchTagged code-paths to be explicit about the pools they need access
   271  // to. The alternative is to either expose the sessionPools struct (which is a worse abstraction),
   272  // or make a new concrete implemtation (which requires an extra alloc). Chosing the best of the
   273  // three options and leaving as the interface below.
   274  type fetchTaggedPools interface {
   275  	MultiReaderIteratorArray() encoding.MultiReaderIteratorArrayPool
   276  	MultiReaderIterator() encoding.MultiReaderIteratorPool
   277  	SeriesIterator() encoding.SeriesIteratorPool
   278  	CheckedBytesWrapper() xpool.CheckedBytesWrapperPool
   279  	ID() ident.Pool
   280  	ReaderSliceOfSlicesIterator() *readerSliceOfSlicesIteratorPool
   281  	TagDecoder() serialize.TagDecoderPool
   282  }