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 }