github.com/MetalBlockchain/metalgo@v1.11.9/vms/proposervm/scheduler/scheduler.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package scheduler
     5  
     6  import (
     7  	"time"
     8  
     9  	"go.uber.org/zap"
    10  
    11  	"github.com/MetalBlockchain/metalgo/snow/engine/common"
    12  	"github.com/MetalBlockchain/metalgo/utils/logging"
    13  )
    14  
    15  type Scheduler interface {
    16  	Dispatch(startTime time.Time)
    17  
    18  	// Client must guarantee that [SetBuildBlockTime]
    19  	// is never called after [Close]
    20  	SetBuildBlockTime(t time.Time)
    21  	Close()
    22  }
    23  
    24  // Scheduler receives notifications from a VM that it wants its engine to call
    25  // the VM's BuildBlock method, and delivers the notification to the engine only
    26  // when the engine should call BuildBlock. Namely, when this node is allowed to
    27  // propose a block under the congestion control mechanism.
    28  type scheduler struct {
    29  	log logging.Logger
    30  	// The VM sends a message on this channel when it wants to tell the engine
    31  	// that the engine should call the VM's BuildBlock method
    32  	fromVM <-chan common.Message
    33  	// The scheduler sends a message on this channel to notify the engine that
    34  	// it should call its VM's BuildBlock method
    35  	toEngine chan<- common.Message
    36  	// When we receive a message on this channel, it means that we must refrain
    37  	// from telling the engine to call its VM's BuildBlock method until the
    38  	// given time
    39  	newBuildBlockTime chan time.Time
    40  }
    41  
    42  func New(log logging.Logger, toEngine chan<- common.Message) (Scheduler, chan<- common.Message) {
    43  	vmToEngine := make(chan common.Message, cap(toEngine))
    44  	return &scheduler{
    45  		log:               log,
    46  		fromVM:            vmToEngine,
    47  		toEngine:          toEngine,
    48  		newBuildBlockTime: make(chan time.Time),
    49  	}, vmToEngine
    50  }
    51  
    52  func (s *scheduler) Dispatch(buildBlockTime time.Time) {
    53  	timer := time.NewTimer(time.Until(buildBlockTime))
    54  waitloop:
    55  	for {
    56  		select {
    57  		case <-timer.C: // It's time to tell the engine to try to build a block
    58  		case buildBlockTime, ok := <-s.newBuildBlockTime:
    59  			// Stop the timer and clear [timer.C] if needed
    60  			if !timer.Stop() {
    61  				<-timer.C
    62  			}
    63  
    64  			if !ok {
    65  				// s.Close() was called
    66  				return
    67  			}
    68  
    69  			// The time at which we should notify the engine that it should try
    70  			// to build a block has changed
    71  			timer.Reset(time.Until(buildBlockTime))
    72  			continue waitloop
    73  		}
    74  
    75  		for {
    76  			select {
    77  			case msg := <-s.fromVM:
    78  				// Give the engine the message from the VM asking the engine to
    79  				// build a block
    80  				select {
    81  				case s.toEngine <- msg:
    82  				default:
    83  					// If the channel to the engine is full, drop the message
    84  					// from the VM to avoid deadlock
    85  					s.log.Debug("dropping message from VM",
    86  						zap.String("reason", "channel to engine is full"),
    87  						zap.Stringer("messageString", msg),
    88  					)
    89  				}
    90  			case buildBlockTime, ok := <-s.newBuildBlockTime:
    91  				// The time at which we should notify the engine that it should
    92  				// try to build a block has changed
    93  				if !ok {
    94  					// s.Close() was called
    95  					return
    96  				}
    97  				// We know [timer.C] was drained in the first select statement
    98  				// so its safe to call [timer.Reset]
    99  				timer.Reset(time.Until(buildBlockTime))
   100  				continue waitloop
   101  			}
   102  		}
   103  	}
   104  }
   105  
   106  func (s *scheduler) SetBuildBlockTime(t time.Time) {
   107  	s.newBuildBlockTime <- t
   108  }
   109  
   110  func (s *scheduler) Close() {
   111  	close(s.newBuildBlockTime)
   112  }