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