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

     1  package backend
     2  
     3  import (
     4  	"github.com/hashicorp/go-multierror"
     5  	"github.com/sony/gobreaker"
     6  	"google.golang.org/grpc/codes"
     7  	"google.golang.org/grpc/status"
     8  
     9  	"github.com/onflow/flow-go/model/flow"
    10  )
    11  
    12  // maxFailedRequestCount represents the maximum number of failed requests before returning errors.
    13  const maxFailedRequestCount = 3
    14  
    15  type Communicator interface {
    16  	CallAvailableNode(
    17  		//List of node identifiers to execute callback on
    18  		nodes flow.IdentityList,
    19  		//Callback function that represents an action to be performed on a node.
    20  		//It takes a node as input and returns an error indicating the result of the action.
    21  		call func(node *flow.Identity) error,
    22  		// Callback function that determines whether an error should terminate further execution.
    23  		// It takes an error as input and returns a boolean value indicating whether the error should be considered terminal.
    24  		shouldTerminateOnError func(node *flow.Identity, err error) bool,
    25  	) error
    26  }
    27  
    28  var _ Communicator = (*NodeCommunicator)(nil)
    29  
    30  // NodeCommunicator is responsible for calling available nodes in the backend.
    31  type NodeCommunicator struct {
    32  	nodeSelectorFactory NodeSelectorFactory
    33  }
    34  
    35  // NewNodeCommunicator creates a new instance of NodeCommunicator.
    36  func NewNodeCommunicator(circuitBreakerEnabled bool) *NodeCommunicator {
    37  	return &NodeCommunicator{
    38  		nodeSelectorFactory: NodeSelectorFactory{circuitBreakerEnabled: circuitBreakerEnabled},
    39  	}
    40  }
    41  
    42  // CallAvailableNode calls the provided function on the available nodes.
    43  // It iterates through the nodes and executes the function.
    44  // If an error occurs, it applies the custom error terminator (if provided) and keeps track of the errors.
    45  // If the error occurs in circuit breaker, it continues to the next node.
    46  // If the maximum failed request count is reached, it returns the accumulated errors.
    47  func (b *NodeCommunicator) CallAvailableNode(
    48  	//List of node identifiers to execute callback on
    49  	nodes flow.IdentityList,
    50  	//Callback function that determines whether an error should terminate further execution.
    51  	// It takes an error as input and returns a boolean value indicating whether the error should be considered terminal.
    52  	call func(id *flow.Identity) error,
    53  	// Callback function that determines whether an error should terminate further execution.
    54  	// It takes an error as input and returns a boolean value indicating whether the error should be considered terminal.
    55  	shouldTerminateOnError func(node *flow.Identity, err error) bool,
    56  ) error {
    57  	var errs *multierror.Error
    58  	nodeSelector, err := b.nodeSelectorFactory.SelectNodes(nodes)
    59  	if err != nil {
    60  		return err
    61  	}
    62  
    63  	for node := nodeSelector.Next(); node != nil; node = nodeSelector.Next() {
    64  		err := call(node)
    65  		if err == nil {
    66  			return nil
    67  		}
    68  
    69  		if shouldTerminateOnError != nil && shouldTerminateOnError(node, err) {
    70  			return err
    71  		}
    72  
    73  		if err == gobreaker.ErrOpenState {
    74  			if !nodeSelector.HasNext() && errs == nil {
    75  				errs = multierror.Append(errs, status.Error(codes.Unavailable, "there are no available nodes"))
    76  			}
    77  			continue
    78  		}
    79  
    80  		errs = multierror.Append(errs, err)
    81  		if len(errs.Errors) >= maxFailedRequestCount {
    82  			return errs.ErrorOrNil()
    83  		}
    84  	}
    85  
    86  	return errs.ErrorOrNil()
    87  }