github.com/onflow/flow-go@v0.33.17/engine/access/state_stream/backend/streamer.go (about)

     1  package backend
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	"github.com/rs/zerolog"
    10  	"golang.org/x/time/rate"
    11  
    12  	"github.com/onflow/flow-go/engine"
    13  	"github.com/onflow/flow-go/engine/access/state_stream"
    14  	"github.com/onflow/flow-go/module/executiondatasync/execution_data"
    15  	"github.com/onflow/flow-go/storage"
    16  )
    17  
    18  // Streamer
    19  type Streamer struct {
    20  	log         zerolog.Logger
    21  	sub         state_stream.Streamable
    22  	broadcaster *engine.Broadcaster
    23  	sendTimeout time.Duration
    24  	limiter     *rate.Limiter
    25  }
    26  
    27  func NewStreamer(
    28  	log zerolog.Logger,
    29  	broadcaster *engine.Broadcaster,
    30  	sendTimeout time.Duration,
    31  	limit float64,
    32  	sub state_stream.Streamable,
    33  ) *Streamer {
    34  	var limiter *rate.Limiter
    35  	if limit > 0 {
    36  		// allows for 1 response per call, averaging `limit` responses per second over longer time frames
    37  		limiter = rate.NewLimiter(rate.Limit(limit), 1)
    38  	}
    39  
    40  	return &Streamer{
    41  		log:         log.With().Str("sub_id", sub.ID()).Logger(),
    42  		broadcaster: broadcaster,
    43  		sendTimeout: sendTimeout,
    44  		limiter:     limiter,
    45  		sub:         sub,
    46  	}
    47  }
    48  
    49  // Stream is a blocking method that streams data to the subscription until either the context is
    50  // cancelled or it encounters an error.
    51  func (s *Streamer) Stream(ctx context.Context) {
    52  	s.log.Debug().Msg("starting streaming")
    53  	defer s.log.Debug().Msg("finished streaming")
    54  
    55  	notifier := engine.NewNotifier()
    56  	s.broadcaster.Subscribe(notifier)
    57  
    58  	// always check the first time. This ensures that streaming continues to work even if the
    59  	// execution sync is not functioning (e.g. on a past spork network, or during an temporary outage)
    60  	notifier.Notify()
    61  
    62  	for {
    63  		select {
    64  		case <-ctx.Done():
    65  			s.sub.Fail(fmt.Errorf("client disconnected: %w", ctx.Err()))
    66  			return
    67  		case <-notifier.Channel():
    68  			s.log.Debug().Msg("received broadcast notification")
    69  		}
    70  
    71  		err := s.sendAllAvailable(ctx)
    72  
    73  		if err != nil {
    74  			s.log.Err(err).Msg("error sending response")
    75  			s.sub.Fail(err)
    76  			return
    77  		}
    78  	}
    79  }
    80  
    81  // sendAllAvailable reads data from the streamable and sends it to the client until no more data is available.
    82  func (s *Streamer) sendAllAvailable(ctx context.Context) error {
    83  	for {
    84  		// blocking wait for the streamer's rate limit to have available capacity
    85  		if err := s.checkRateLimit(ctx); err != nil {
    86  			return fmt.Errorf("error waiting for response capacity: %w", err)
    87  		}
    88  
    89  		response, err := s.sub.Next(ctx)
    90  
    91  		if err != nil {
    92  			if errors.Is(err, storage.ErrNotFound) || errors.Is(err, storage.ErrHeightNotIndexed) || execution_data.IsBlobNotFoundError(err) {
    93  				// no more available
    94  				return nil
    95  			}
    96  
    97  			return fmt.Errorf("could not get response: %w", err)
    98  		}
    99  
   100  		if ssub, ok := s.sub.(*HeightBasedSubscription); ok {
   101  			s.log.Trace().
   102  				Uint64("next_height", ssub.nextHeight).
   103  				Msg("sending response")
   104  		}
   105  
   106  		err = s.sub.Send(ctx, response, s.sendTimeout)
   107  		if err != nil {
   108  			return err
   109  		}
   110  	}
   111  }
   112  
   113  // checkRateLimit checks the stream's rate limit and blocks until there is room to send a response.
   114  // An error is returned if the context is canceled or the expected wait time exceeds the context's
   115  // deadline.
   116  func (s *Streamer) checkRateLimit(ctx context.Context) error {
   117  	if s.limiter == nil {
   118  		return nil
   119  	}
   120  
   121  	return s.limiter.WaitN(ctx, 1)
   122  }