github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/access/subscription/subscription.go (about)

     1  package subscription
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/google/uuid"
    10  	"google.golang.org/grpc/status"
    11  )
    12  
    13  const (
    14  	// DefaultSendBufferSize is the default buffer size for the subscription's send channel.
    15  	// The size is chosen to balance memory overhead from each subscription with performance when
    16  	// streaming existing data.
    17  	DefaultSendBufferSize = 10
    18  
    19  	// DefaultMaxGlobalStreams defines the default max number of streams that can be open at the same time.
    20  	DefaultMaxGlobalStreams = 1000
    21  
    22  	// DefaultCacheSize defines the default max number of objects for the execution data cache.
    23  	DefaultCacheSize = 100
    24  
    25  	// DefaultSendTimeout is the default timeout for sending a message to the client. After the timeout
    26  	// expires, the connection is closed.
    27  	DefaultSendTimeout = 30 * time.Second
    28  
    29  	// DefaultResponseLimit is default max responses per second allowed on a stream. After exceeding
    30  	// the limit, the stream is paused until more capacity is available.
    31  	DefaultResponseLimit = float64(0)
    32  
    33  	// DefaultHeartbeatInterval specifies the block interval at which heartbeat messages should be sent.
    34  	DefaultHeartbeatInterval = 1
    35  )
    36  
    37  // GetDataByHeightFunc is a callback used by subscriptions to retrieve data for a given height.
    38  // Expected errors:
    39  // - storage.ErrNotFound
    40  // - execution_data.BlobNotFoundError
    41  // All other errors are considered exceptions
    42  type GetDataByHeightFunc func(ctx context.Context, height uint64) (interface{}, error)
    43  
    44  // Subscription represents a streaming request, and handles the communication between the grpc handler
    45  // and the backend implementation.
    46  type Subscription interface {
    47  	// ID returns the unique identifier for this subscription used for logging
    48  	ID() string
    49  
    50  	// Channel returns the channel from which subscription data can be read
    51  	Channel() <-chan interface{}
    52  
    53  	// Err returns the error that caused the subscription to fail
    54  	Err() error
    55  }
    56  
    57  // Streamable represents a subscription that can be streamed.
    58  type Streamable interface {
    59  	// ID returns the subscription ID
    60  	// Note: this is not a cryptographic hash
    61  	ID() string
    62  	// Close is called when a subscription ends gracefully, and closes the subscription channel
    63  	Close()
    64  	// Fail registers an error and closes the subscription channel
    65  	Fail(error)
    66  	// Send sends a value to the subscription channel or returns an error
    67  	// Expected errors:
    68  	// - context.DeadlineExceeded if send timed out
    69  	// - context.Canceled if the client disconnected
    70  	Send(context.Context, interface{}, time.Duration) error
    71  	// Next returns the value for the next height from the subscription
    72  	Next(context.Context) (interface{}, error)
    73  }
    74  
    75  var _ Subscription = (*SubscriptionImpl)(nil)
    76  
    77  type SubscriptionImpl struct {
    78  	id string
    79  
    80  	// ch is the channel used to pass data to the receiver
    81  	ch chan interface{}
    82  
    83  	// err is the error that caused the subscription to fail
    84  	err error
    85  
    86  	// once is used to ensure that the channel is only closed once
    87  	once sync.Once
    88  
    89  	// closed tracks whether or not the subscription has been closed
    90  	closed bool
    91  }
    92  
    93  func NewSubscription(bufferSize int) *SubscriptionImpl {
    94  	return &SubscriptionImpl{
    95  		id: uuid.New().String(),
    96  		ch: make(chan interface{}, bufferSize),
    97  	}
    98  }
    99  
   100  // ID returns the subscription ID
   101  // Note: this is not a cryptographic hash
   102  func (sub *SubscriptionImpl) ID() string {
   103  	return sub.id
   104  }
   105  
   106  // Channel returns the channel from which subscription data can be read
   107  func (sub *SubscriptionImpl) Channel() <-chan interface{} {
   108  	return sub.ch
   109  }
   110  
   111  // Err returns the error that caused the subscription to fail
   112  func (sub *SubscriptionImpl) Err() error {
   113  	return sub.err
   114  }
   115  
   116  // Fail registers an error and closes the subscription channel
   117  func (sub *SubscriptionImpl) Fail(err error) {
   118  	sub.err = err
   119  	sub.Close()
   120  }
   121  
   122  // Close is called when a subscription ends gracefully, and closes the subscription channel
   123  func (sub *SubscriptionImpl) Close() {
   124  	sub.once.Do(func() {
   125  		close(sub.ch)
   126  		sub.closed = true
   127  	})
   128  }
   129  
   130  // Send sends a value to the subscription channel or returns an error
   131  // Expected errors:
   132  // - context.DeadlineExceeded if send timed out
   133  // - context.Canceled if the client disconnected
   134  func (sub *SubscriptionImpl) Send(ctx context.Context, v interface{}, timeout time.Duration) error {
   135  	if sub.closed {
   136  		return fmt.Errorf("subscription closed")
   137  	}
   138  
   139  	waitCtx, cancel := context.WithTimeout(ctx, timeout)
   140  	defer cancel()
   141  
   142  	select {
   143  	case <-waitCtx.Done():
   144  		return waitCtx.Err()
   145  	case sub.ch <- v:
   146  		return nil
   147  	}
   148  }
   149  
   150  // NewFailedSubscription returns a new subscription that has already failed with the given error and
   151  // message. This is useful to return an error that occurred during subscription setup.
   152  func NewFailedSubscription(err error, msg string) *SubscriptionImpl {
   153  	sub := NewSubscription(0)
   154  
   155  	// if error is a grpc error, wrap it to preserve the error code
   156  	if st, ok := status.FromError(err); ok {
   157  		sub.Fail(status.Errorf(st.Code(), "%s: %s", msg, st.Message()))
   158  		return sub
   159  	}
   160  
   161  	// otherwise, return wrap the message normally
   162  	sub.Fail(fmt.Errorf("%s: %w", msg, err))
   163  	return sub
   164  }
   165  
   166  var _ Subscription = (*HeightBasedSubscription)(nil)
   167  var _ Streamable = (*HeightBasedSubscription)(nil)
   168  
   169  // HeightBasedSubscription is a subscription that retrieves data sequentially by block height
   170  type HeightBasedSubscription struct {
   171  	*SubscriptionImpl
   172  	nextHeight uint64
   173  	getData    GetDataByHeightFunc
   174  }
   175  
   176  func NewHeightBasedSubscription(bufferSize int, firstHeight uint64, getData GetDataByHeightFunc) *HeightBasedSubscription {
   177  	return &HeightBasedSubscription{
   178  		SubscriptionImpl: NewSubscription(bufferSize),
   179  		nextHeight:       firstHeight,
   180  		getData:          getData,
   181  	}
   182  }
   183  
   184  // Next returns the value for the next height from the subscription
   185  func (s *HeightBasedSubscription) Next(ctx context.Context) (interface{}, error) {
   186  	v, err := s.getData(ctx, s.nextHeight)
   187  	if err != nil {
   188  		return nil, fmt.Errorf("could not get data for height %d: %w", s.nextHeight, err)
   189  	}
   190  	s.nextHeight++
   191  	return v, nil
   192  }