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  }