go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/client/coordinator/fetcher.go (about) 1 // Copyright 2017 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package coordinator 16 17 import ( 18 "context" 19 "sync" 20 "time" 21 22 "go.chromium.org/luci/common/clock" 23 log "go.chromium.org/luci/common/logging" 24 "go.chromium.org/luci/logdog/api/logpb" 25 "go.chromium.org/luci/logdog/common/fetcher" 26 "go.chromium.org/luci/logdog/common/types" 27 ) 28 29 // coordinatorSource is a fetcher.Source implementation that uses the 30 // Coordinator API. 31 type coordinatorSource struct { 32 sync.Mutex 33 34 stream *Stream 35 tidx types.MessageIndex 36 37 requireCompleteStream bool 38 39 streamState *LogStream 40 } 41 42 func (s *coordinatorSource) LogEntries(c context.Context, req *fetcher.LogRequest) ( 43 []*logpb.LogEntry, types.MessageIndex, error) { 44 s.Lock() 45 defer s.Unlock() 46 47 params := append(make([]GetParam, 0, 4), 48 LimitBytes(int(req.Bytes)), 49 LimitCount(req.Count), 50 Index(req.Index), 51 ) 52 53 // If we haven't terminated, use this opportunity to fetch/update our stream 54 // state. 55 var streamState LogStream 56 reqStream := (s.streamState == nil || s.streamState.State.TerminalIndex < 0) 57 if reqStream { 58 params = append(params, WithState(&streamState)) 59 } 60 61 delayTimer := clock.NewTimer(c) 62 defer delayTimer.Stop() 63 for { 64 logs, err := s.stream.Get(c, params...) 65 66 // TODO(iannucci,dnj): use retry module + transient tags instead 67 delayTimer.Reset(5 * time.Second) 68 switch err { 69 case nil: 70 if reqStream { 71 s.streamState = &streamState 72 s.tidx = streamState.State.TerminalIndex 73 } 74 return logs, s.tidx, nil 75 76 case ErrNoSuchStream: 77 if s.requireCompleteStream { 78 return nil, 0, err 79 } 80 81 log.WithError(err).Warningf(c, "Stream does not exist. Sleeping pending registration.") 82 83 // Delay, interrupting if our Context is interrupted. 84 if tr := <-delayTimer.GetC(); tr.Incomplete() { 85 return nil, 0, tr.Err 86 } 87 88 default: 89 if err != nil { 90 return nil, 0, err 91 } 92 } 93 } 94 } 95 96 // Descriptor returns the LogStreamDescriptor for this stream, if known, 97 // or returns nil. 98 func (s *coordinatorSource) Descriptor() *logpb.LogStreamDescriptor { 99 if s.streamState != nil { 100 return &s.streamState.Desc 101 } 102 return nil 103 } 104 105 // Fetcher returns a Fetcher implementation for this Stream. 106 // 107 // If you pass a nil fetcher.Options, a default option set will be used. The 108 // o.Source field will always be overwritten to be based off this stream. 109 func (s *Stream) Fetcher(c context.Context, o *fetcher.Options) *fetcher.Fetcher { 110 if o == nil { 111 o = &fetcher.Options{} 112 } else { 113 o = &(*o) 114 } 115 o.Source = &coordinatorSource{ 116 stream: s, tidx: -1, requireCompleteStream: o.RequireCompleteStream} 117 return fetcher.New(c, *o) 118 }