github.com/ethereum/go-ethereum@v1.16.1/beacon/light/request/server.go (about)

     1  // Copyright 2023 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package request
    18  
    19  import (
    20  	"math"
    21  	"sync"
    22  	"time"
    23  
    24  	"github.com/ethereum/go-ethereum/common/mclock"
    25  	"github.com/ethereum/go-ethereum/log"
    26  )
    27  
    28  var (
    29  	// request events
    30  	EvResponse = &EventType{Name: "response", requestEvent: true} // data: RequestResponse; sent by requestServer
    31  	EvFail     = &EventType{Name: "fail", requestEvent: true}     // data: RequestResponse; sent by requestServer
    32  	EvTimeout  = &EventType{Name: "timeout", requestEvent: true}  // data: RequestResponse; sent by serverWithTimeout
    33  	// server events
    34  	EvRegistered      = &EventType{Name: "registered"}      // data: nil; sent by Scheduler
    35  	EvUnregistered    = &EventType{Name: "unregistered"}    // data: nil; sent by Scheduler
    36  	EvCanRequestAgain = &EventType{Name: "canRequestAgain"} // data: nil; sent by serverWithLimits
    37  )
    38  
    39  const (
    40  	softRequestTimeout = time.Second      // allow resending request to a different server but do not cancel yet
    41  	hardRequestTimeout = time.Second * 10 // cancel request
    42  )
    43  
    44  const (
    45  	// serverWithLimits parameters
    46  	parallelAdjustUp     = 0.1                    // adjust parallelLimit up in case of success under full load
    47  	parallelAdjustDown   = 1                      // adjust parallelLimit down in case of timeout/failure
    48  	minParallelLimit     = 1                      // parallelLimit lower bound
    49  	defaultParallelLimit = 3                      // parallelLimit initial value
    50  	minFailureDelay      = time.Millisecond * 100 // minimum disable time in case of request failure
    51  	maxFailureDelay      = time.Minute            // maximum disable time in case of request failure
    52  	maxServerEventBuffer = 5                      // server event allowance buffer limit
    53  	maxServerEventRate   = time.Second            // server event allowance buffer recharge rate
    54  )
    55  
    56  // requestServer can send requests in a non-blocking way and feed back events
    57  // through the event callback. After each request it should send back either
    58  // EvResponse or EvFail. Additionally, it may also send application-defined
    59  // events that the Modules can interpret.
    60  type requestServer interface {
    61  	Name() string
    62  	Subscribe(eventCallback func(Event))
    63  	SendRequest(ID, Request)
    64  	Unsubscribe()
    65  }
    66  
    67  // server is implemented by a requestServer wrapped into serverWithTimeout and
    68  // serverWithLimits and is used by Scheduler.
    69  // In addition to requestServer functionality, server can also handle timeouts,
    70  // limit the number of parallel in-flight requests and temporarily disable
    71  // new requests based on timeouts and response failures.
    72  type server interface {
    73  	Server
    74  	subscribe(eventCallback func(Event))
    75  	canRequestNow() bool
    76  	sendRequest(Request) ID
    77  	fail(string)
    78  	unsubscribe()
    79  }
    80  
    81  // NewServer wraps a requestServer and returns a server
    82  func NewServer(rs requestServer, clock mclock.Clock) server {
    83  	s := &serverWithLimits{}
    84  	s.parent = rs
    85  	s.serverWithTimeout.init(clock)
    86  	s.init()
    87  	return s
    88  }
    89  
    90  // EventType identifies an event type, either related to a request or the server
    91  // in general. Server events can also be externally defined.
    92  type EventType struct {
    93  	Name         string
    94  	requestEvent bool // all request events are pre-defined in request package
    95  }
    96  
    97  // Event describes an event where the type of Data depends on Type.
    98  // Server field is not required when sent through the event callback; it is filled
    99  // out when processed by the Scheduler. Note that the Scheduler can also create
   100  // and send events (EvRegistered, EvUnregistered) directly.
   101  type Event struct {
   102  	Type   *EventType
   103  	Server Server // filled by Scheduler
   104  	Data   any
   105  }
   106  
   107  // IsRequestEvent returns true if the event is a request event
   108  func (e *Event) IsRequestEvent() bool {
   109  	return e.Type.requestEvent
   110  }
   111  
   112  // RequestInfo assumes that the event is a request event and returns its contents
   113  // in a convenient form.
   114  func (e *Event) RequestInfo() (ServerAndID, Request, Response) {
   115  	data := e.Data.(RequestResponse)
   116  	return ServerAndID{Server: e.Server, ID: data.ID}, data.Request, data.Response
   117  }
   118  
   119  // RequestResponse is the Data type of request events.
   120  type RequestResponse struct {
   121  	ID       ID
   122  	Request  Request
   123  	Response Response
   124  }
   125  
   126  // serverWithTimeout wraps a requestServer and introduces timeouts.
   127  // The request's lifecycle is concluded if EvResponse or EvFail emitted by the
   128  // parent requestServer. If this does not happen until softRequestTimeout then
   129  // EvTimeout is emitted, after which the final EvResponse or EvFail is still
   130  // guaranteed to follow.
   131  // If the parent fails to send this final event for hardRequestTimeout then
   132  // serverWithTimeout emits EvFail and discards any further events from the
   133  // parent related to the given request.
   134  type serverWithTimeout struct {
   135  	parent       requestServer
   136  	lock         sync.Mutex
   137  	clock        mclock.Clock
   138  	childEventCb func(event Event)
   139  	timeouts     map[ID]mclock.Timer
   140  	lastID       ID
   141  }
   142  
   143  // Name implements request.Server
   144  func (s *serverWithTimeout) Name() string {
   145  	return s.parent.Name()
   146  }
   147  
   148  // init initializes serverWithTimeout
   149  func (s *serverWithTimeout) init(clock mclock.Clock) {
   150  	s.clock = clock
   151  	s.timeouts = make(map[ID]mclock.Timer)
   152  }
   153  
   154  // subscribe subscribes to events which include parent (requestServer) events
   155  // plus EvTimeout.
   156  func (s *serverWithTimeout) subscribe(eventCallback func(event Event)) {
   157  	s.lock.Lock()
   158  	defer s.lock.Unlock()
   159  
   160  	s.childEventCb = eventCallback
   161  	s.parent.Subscribe(s.eventCallback)
   162  }
   163  
   164  // sendRequest generated a new request ID, emits EvRequest, sets up the timeout
   165  // timer, then sends the request through the parent (requestServer).
   166  func (s *serverWithTimeout) sendRequest(request Request) (reqId ID) {
   167  	s.lock.Lock()
   168  	s.lastID++
   169  	id := s.lastID
   170  	s.startTimeout(RequestResponse{ID: id, Request: request})
   171  	s.lock.Unlock()
   172  	s.parent.SendRequest(id, request)
   173  	return id
   174  }
   175  
   176  // eventCallback is called by parent (requestServer) event subscription.
   177  func (s *serverWithTimeout) eventCallback(event Event) {
   178  	s.lock.Lock()
   179  	defer s.lock.Unlock()
   180  
   181  	switch event.Type {
   182  	case EvResponse, EvFail:
   183  		id := event.Data.(RequestResponse).ID
   184  		if timer, ok := s.timeouts[id]; ok {
   185  			// Note: if stopping the timer is unsuccessful then the resulting AfterFunc
   186  			// call will just do nothing
   187  			timer.Stop()
   188  			delete(s.timeouts, id)
   189  			if s.childEventCb != nil {
   190  				s.childEventCb(event)
   191  			}
   192  		}
   193  	default:
   194  		if s.childEventCb != nil {
   195  			s.childEventCb(event)
   196  		}
   197  	}
   198  }
   199  
   200  // startTimeout starts a timeout timer for the given request.
   201  func (s *serverWithTimeout) startTimeout(reqData RequestResponse) {
   202  	id := reqData.ID
   203  	s.timeouts[id] = s.clock.AfterFunc(softRequestTimeout, func() {
   204  		s.lock.Lock()
   205  		if _, ok := s.timeouts[id]; !ok {
   206  			s.lock.Unlock()
   207  			return
   208  		}
   209  		s.timeouts[id] = s.clock.AfterFunc(hardRequestTimeout-softRequestTimeout, func() {
   210  			s.lock.Lock()
   211  			if _, ok := s.timeouts[id]; !ok {
   212  				s.lock.Unlock()
   213  				return
   214  			}
   215  			delete(s.timeouts, id)
   216  			childEventCb := s.childEventCb
   217  			s.lock.Unlock()
   218  			if childEventCb != nil {
   219  				childEventCb(Event{Type: EvFail, Data: reqData})
   220  			}
   221  		})
   222  		childEventCb := s.childEventCb
   223  		s.lock.Unlock()
   224  		if childEventCb != nil {
   225  			childEventCb(Event{Type: EvTimeout, Data: reqData})
   226  		}
   227  	})
   228  }
   229  
   230  // unsubscribe stops all goroutines associated with the server.
   231  func (s *serverWithTimeout) unsubscribe() {
   232  	s.lock.Lock()
   233  	for _, timer := range s.timeouts {
   234  		if timer != nil {
   235  			timer.Stop()
   236  		}
   237  	}
   238  	s.lock.Unlock()
   239  	s.parent.Unsubscribe()
   240  }
   241  
   242  // serverWithLimits wraps serverWithTimeout and implements server. It limits the
   243  // number of parallel in-flight requests and prevents sending new requests when a
   244  // pending one has already timed out. Server events are also rate limited.
   245  // It also implements a failure delay mechanism that adds an exponentially growing
   246  // delay each time a request fails (wrong answer or hard timeout). This makes the
   247  // syncing mechanism less brittle as temporary failures of the server might happen
   248  // sometimes, but still avoids hammering a non-functional server with requests.
   249  type serverWithLimits struct {
   250  	serverWithTimeout
   251  	lock                       sync.Mutex
   252  	childEventCb               func(event Event)
   253  	softTimeouts               map[ID]struct{}
   254  	pendingCount, timeoutCount int
   255  	parallelLimit              float32
   256  	sendEvent                  bool
   257  	delayTimer                 mclock.Timer
   258  	delayCounter               int
   259  	failureDelayEnd            mclock.AbsTime
   260  	failureDelay               float64
   261  	serverEventBuffer          int
   262  	eventBufferUpdated         mclock.AbsTime
   263  }
   264  
   265  // init initializes serverWithLimits
   266  func (s *serverWithLimits) init() {
   267  	s.softTimeouts = make(map[ID]struct{})
   268  	s.parallelLimit = defaultParallelLimit
   269  	s.serverEventBuffer = maxServerEventBuffer
   270  }
   271  
   272  // subscribe subscribes to events which include parent (serverWithTimeout) events
   273  // plus EvCanRequestAgain.
   274  func (s *serverWithLimits) subscribe(eventCallback func(event Event)) {
   275  	s.lock.Lock()
   276  	defer s.lock.Unlock()
   277  
   278  	s.childEventCb = eventCallback
   279  	s.serverWithTimeout.subscribe(s.eventCallback)
   280  }
   281  
   282  // eventCallback is called by parent (serverWithTimeout) event subscription.
   283  func (s *serverWithLimits) eventCallback(event Event) {
   284  	s.lock.Lock()
   285  	var sendCanRequestAgain bool
   286  	passEvent := true
   287  	switch event.Type {
   288  	case EvTimeout:
   289  		id := event.Data.(RequestResponse).ID
   290  		s.softTimeouts[id] = struct{}{}
   291  		s.timeoutCount++
   292  		s.parallelLimit -= parallelAdjustDown
   293  		if s.parallelLimit < minParallelLimit {
   294  			s.parallelLimit = minParallelLimit
   295  		}
   296  		log.Debug("Server timeout", "count", s.timeoutCount, "parallelLimit", s.parallelLimit)
   297  	case EvResponse, EvFail:
   298  		id := event.Data.(RequestResponse).ID
   299  		if _, ok := s.softTimeouts[id]; ok {
   300  			delete(s.softTimeouts, id)
   301  			s.timeoutCount--
   302  			log.Debug("Server timeout finalized", "count", s.timeoutCount, "parallelLimit", s.parallelLimit)
   303  		}
   304  		if event.Type == EvResponse && s.pendingCount >= int(s.parallelLimit) {
   305  			s.parallelLimit += parallelAdjustUp
   306  		}
   307  		s.pendingCount--
   308  		if s.canRequest() {
   309  			sendCanRequestAgain = s.sendEvent
   310  			s.sendEvent = false
   311  		}
   312  		if event.Type == EvFail {
   313  			s.failLocked("failed request")
   314  		}
   315  	default:
   316  		// server event; check rate limit
   317  		if s.serverEventBuffer < maxServerEventBuffer {
   318  			now := s.clock.Now()
   319  			sinceUpdate := time.Duration(now - s.eventBufferUpdated)
   320  			if sinceUpdate >= maxServerEventRate*time.Duration(maxServerEventBuffer-s.serverEventBuffer) {
   321  				s.serverEventBuffer = maxServerEventBuffer
   322  				s.eventBufferUpdated = now
   323  			} else {
   324  				addBuffer := int(sinceUpdate / maxServerEventRate)
   325  				s.serverEventBuffer += addBuffer
   326  				s.eventBufferUpdated += mclock.AbsTime(maxServerEventRate * time.Duration(addBuffer))
   327  			}
   328  		}
   329  		if s.serverEventBuffer > 0 {
   330  			s.serverEventBuffer--
   331  		} else {
   332  			passEvent = false
   333  		}
   334  	}
   335  	childEventCb := s.childEventCb
   336  	s.lock.Unlock()
   337  	if passEvent && childEventCb != nil {
   338  		childEventCb(event)
   339  	}
   340  	if sendCanRequestAgain && childEventCb != nil {
   341  		childEventCb(Event{Type: EvCanRequestAgain})
   342  	}
   343  }
   344  
   345  // sendRequest sends a request through the parent (serverWithTimeout).
   346  func (s *serverWithLimits) sendRequest(request Request) (reqId ID) {
   347  	s.lock.Lock()
   348  	s.pendingCount++
   349  	s.lock.Unlock()
   350  	return s.serverWithTimeout.sendRequest(request)
   351  }
   352  
   353  // unsubscribe stops all goroutines associated with the server.
   354  func (s *serverWithLimits) unsubscribe() {
   355  	s.lock.Lock()
   356  	if s.delayTimer != nil {
   357  		s.delayTimer.Stop()
   358  		s.delayTimer = nil
   359  	}
   360  	s.childEventCb = nil
   361  	s.lock.Unlock()
   362  	s.serverWithTimeout.unsubscribe()
   363  }
   364  
   365  // canRequest checks whether a new request can be started.
   366  func (s *serverWithLimits) canRequest() bool {
   367  	if s.delayTimer != nil || s.pendingCount >= int(s.parallelLimit) || s.timeoutCount > 0 {
   368  		return false
   369  	}
   370  	if s.parallelLimit < minParallelLimit {
   371  		s.parallelLimit = minParallelLimit
   372  	}
   373  	return true
   374  }
   375  
   376  // canRequestNow checks whether a new request can be started, according to the
   377  // current in-flight request count and parallelLimit, and also the failure delay
   378  // timer.
   379  // If it returns false then it is guaranteed that an EvCanRequestAgain will be
   380  // sent whenever the server becomes available for requesting again.
   381  func (s *serverWithLimits) canRequestNow() bool {
   382  	var sendCanRequestAgain bool
   383  	s.lock.Lock()
   384  	canRequest := s.canRequest()
   385  	if canRequest {
   386  		sendCanRequestAgain = s.sendEvent
   387  		s.sendEvent = false
   388  	}
   389  	childEventCb := s.childEventCb
   390  	s.lock.Unlock()
   391  	if sendCanRequestAgain && childEventCb != nil {
   392  		childEventCb(Event{Type: EvCanRequestAgain})
   393  	}
   394  	return canRequest
   395  }
   396  
   397  // delay sets the delay timer to the given duration, disabling new requests for
   398  // the given period.
   399  func (s *serverWithLimits) delay(delay time.Duration) {
   400  	if s.delayTimer != nil {
   401  		// Note: if stopping the timer is unsuccessful then the resulting AfterFunc
   402  		// call will just do nothing
   403  		s.delayTimer.Stop()
   404  		s.delayTimer = nil
   405  	}
   406  
   407  	s.delayCounter++
   408  	delayCounter := s.delayCounter
   409  	log.Debug("Server delay started", "length", delay)
   410  	s.delayTimer = s.clock.AfterFunc(delay, func() {
   411  		log.Debug("Server delay ended", "length", delay)
   412  		var sendCanRequestAgain bool
   413  		s.lock.Lock()
   414  		if s.delayTimer != nil && s.delayCounter == delayCounter { // do nothing if there is a new timer now
   415  			s.delayTimer = nil
   416  			if s.canRequest() {
   417  				sendCanRequestAgain = s.sendEvent
   418  				s.sendEvent = false
   419  			}
   420  		}
   421  		childEventCb := s.childEventCb
   422  		s.lock.Unlock()
   423  		if sendCanRequestAgain && childEventCb != nil {
   424  			childEventCb(Event{Type: EvCanRequestAgain})
   425  		}
   426  	})
   427  }
   428  
   429  // fail reports that a response from the server was found invalid by the processing
   430  // Module, disabling new requests for a dynamically adjusted time period.
   431  func (s *serverWithLimits) fail(desc string) {
   432  	s.lock.Lock()
   433  	defer s.lock.Unlock()
   434  
   435  	s.failLocked(desc)
   436  }
   437  
   438  // failLocked calculates the dynamic failure delay and applies it.
   439  func (s *serverWithLimits) failLocked(desc string) {
   440  	log.Debug("Server error", "description", desc)
   441  	s.failureDelay *= 2
   442  	now := s.clock.Now()
   443  	if now > s.failureDelayEnd {
   444  		s.failureDelay *= math.Pow(2, -float64(now-s.failureDelayEnd)/float64(maxFailureDelay))
   445  	}
   446  	if s.failureDelay < float64(minFailureDelay) {
   447  		s.failureDelay = float64(minFailureDelay)
   448  	}
   449  	s.failureDelayEnd = now + mclock.AbsTime(s.failureDelay)
   450  	s.delay(time.Duration(s.failureDelay))
   451  }