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 }