github.com/cilium/cilium@v1.16.2/pkg/hubble/container/ring_reader.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Hubble 3 4 package container 5 6 import ( 7 "context" 8 "sync" 9 10 v1 "github.com/cilium/cilium/pkg/hubble/api/v1" 11 "github.com/cilium/cilium/pkg/lock" 12 ) 13 14 // RingReader is a reader for a Ring container. 15 type RingReader struct { 16 ring *Ring 17 idx uint64 18 ctx context.Context 19 mutex lock.Mutex // protects writes to followChan 20 followChan chan *v1.Event 21 followChanLen int 22 wg sync.WaitGroup 23 } 24 25 // NewRingReader creates a new RingReader that starts reading the ring at the 26 // position given by start. 27 func NewRingReader(ring *Ring, start uint64) *RingReader { 28 return newRingReader(ring, start, 1000) 29 } 30 31 func newRingReader(ring *Ring, start uint64, bufferLen int) *RingReader { 32 return &RingReader{ 33 ring: ring, 34 idx: start, 35 ctx: nil, 36 followChanLen: bufferLen, 37 } 38 } 39 40 // Previous reads the event at the current position and decrement the read 41 // position. Returns ErrInvalidRead if there are no older entries. 42 func (r *RingReader) Previous() (*v1.Event, error) { 43 // We only expect ErrInvalidRead to be returned when reading backwards, 44 // therefore we don't try to handle any errors here. 45 e, err := r.ring.read(r.idx) 46 if err != nil { 47 return nil, err 48 } 49 r.idx-- 50 return e, nil 51 } 52 53 // Next reads the event at the current position and increment the read position. 54 // Returns io.EOF if there are no more entries. May return ErrInvalidRead 55 // if the writer overtook this RingReader. 56 func (r *RingReader) Next() (*v1.Event, error) { 57 // There are two possible errors returned by read(): 58 // 59 // Reader ahead of writer (io.EOF): We have read past the writer. 60 // In this case, we want to return nil and don't bump the index, as we have 61 // read all existing values that exist now. 62 // Writer ahead of reader (ErrInvalidRead): The writer has already 63 // overwritten the values we wanted to read. In this case, we want to 64 // propagate the error, as trying to catch up would be very racy. 65 e, err := r.ring.read(r.idx) 66 if err != nil { 67 return nil, err 68 } 69 r.idx++ 70 return e, nil 71 } 72 73 // NextFollow reads the event at the current position and increment the read 74 // position by one. If there are no more event to read, NextFollow blocks 75 // until the next event is added to the ring or the context is cancelled. 76 func (r *RingReader) NextFollow(ctx context.Context) *v1.Event { 77 // if the context changed between invocations, we also have to restart 78 // readFrom, as the old readFrom instance will be using the old context. 79 if r.ctx != ctx { 80 r.mutex.Lock() 81 if r.followChan == nil { 82 r.followChan = make(chan *v1.Event, r.followChanLen) 83 } 84 r.mutex.Unlock() 85 86 r.wg.Add(1) 87 go func(ctx context.Context) { 88 r.ring.readFrom(ctx, r.idx, r.followChan) 89 r.mutex.Lock() 90 if ctx.Err() != nil && r.followChan != nil { // context is done 91 close(r.followChan) 92 r.followChan = nil 93 } 94 r.mutex.Unlock() 95 r.wg.Done() 96 }(ctx) 97 r.ctx = ctx 98 } 99 defer func() { 100 if ctx.Err() != nil { // context is done 101 r.ctx = nil 102 } 103 }() 104 105 r.mutex.Lock() 106 followChan := r.followChan 107 r.mutex.Unlock() 108 109 select { 110 case e, ok := <-followChan: 111 if !ok { 112 // the channel is closed so the context is done 113 return nil 114 } 115 // increment idx so that future calls to the ring reader will 116 // continue reading from were we stopped. 117 r.idx++ 118 return e 119 case <-ctx.Done(): 120 return nil 121 } 122 } 123 124 // Close waits for any spawned goroutines to finish. It is not 125 // required to call Close on a RingReader but it may be useful for specific 126 // situations such as testing. Must not be called concurrently with NextFollow, 127 // as otherwise NextFollow spawns new goroutines that are not waited on. 128 func (r *RingReader) Close() error { 129 r.wg.Wait() 130 return nil 131 }