mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
beacon/light/request: add Scheduler unit test
This commit is contained in:
parent
5795351236
commit
245d166d45
5 changed files with 163 additions and 40 deletions
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -68,7 +67,6 @@ type Module interface {
|
||||||
// allow new operations.
|
// allow new operations.
|
||||||
type Scheduler struct {
|
type Scheduler struct {
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
clock mclock.Clock
|
|
||||||
modules []Module // first has highest priority
|
modules []Module // first has highest priority
|
||||||
names map[Module]string
|
names map[Module]string
|
||||||
servers map[server]struct{}
|
servers map[server]struct{}
|
||||||
|
|
@ -83,6 +81,9 @@ type Scheduler struct {
|
||||||
stopCh chan chan struct{}
|
stopCh chan chan struct{}
|
||||||
|
|
||||||
triggerCh chan struct{} // restarts waiting sync loop
|
triggerCh chan struct{} // restarts waiting sync loop
|
||||||
|
// if trigger has already been fired then send to testWaitCh blocks until
|
||||||
|
// the triggered processing round is finished
|
||||||
|
testWaitCh chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|
@ -118,9 +119,8 @@ type pendingRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewScheduler creates a new Scheduler.
|
// NewScheduler creates a new Scheduler.
|
||||||
func NewScheduler(clock mclock.Clock) *Scheduler {
|
func NewScheduler() *Scheduler {
|
||||||
s := &Scheduler{
|
s := &Scheduler{
|
||||||
clock: clock,
|
|
||||||
servers: make(map[server]struct{}),
|
servers: make(map[server]struct{}),
|
||||||
names: make(map[Module]string),
|
names: make(map[Module]string),
|
||||||
pending: make(map[ServerAndID]pendingRequest),
|
pending: make(map[ServerAndID]pendingRequest),
|
||||||
|
|
@ -130,7 +130,7 @@ func NewScheduler(clock mclock.Clock) *Scheduler {
|
||||||
// that after a trigger happens testWaitCh will block until the resulting
|
// that after a trigger happens testWaitCh will block until the resulting
|
||||||
// processing round has been finished
|
// processing round has been finished
|
||||||
triggerCh: make(chan struct{}, 1),
|
triggerCh: make(chan struct{}, 1),
|
||||||
//testWaitCh: make(chan struct{}),
|
testWaitCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
@ -157,11 +157,10 @@ func (s *Scheduler) RegisterModule(m Module, name string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterServer registers a new server.
|
// RegisterServer registers a new server.
|
||||||
func (s *Scheduler) RegisterServer(rs requestServer) {
|
func (s *Scheduler) RegisterServer(server server) {
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
server := newServer(rs, s.clock)
|
|
||||||
s.addEvent(Event{Type: EvRegistered, Server: server})
|
s.addEvent(Event{Type: EvRegistered, Server: server})
|
||||||
server.subscribe(func(event Event) {
|
server.subscribe(func(event Event) {
|
||||||
event.Server = server
|
event.Server = server
|
||||||
|
|
@ -170,17 +169,12 @@ func (s *Scheduler) RegisterServer(rs requestServer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnregisterServer removes a registered server.
|
// UnregisterServer removes a registered server.
|
||||||
func (s *Scheduler) UnregisterServer(rs requestServer) {
|
func (s *Scheduler) UnregisterServer(server server) {
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
for server := range s.servers {
|
|
||||||
if sl, ok := server.(*serverWithLimits); ok && sl.parent == rs {
|
|
||||||
server.unsubscribe()
|
server.unsubscribe()
|
||||||
s.addEvent(Event{Type: EvUnregistered, Server: server})
|
s.addEvent(Event{Type: EvUnregistered, Server: server})
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts the scheduler. It should be called after registering all modules
|
// Start starts the scheduler. It should be called after registering all modules
|
||||||
|
|
@ -191,15 +185,15 @@ func (s *Scheduler) Start() {
|
||||||
|
|
||||||
// Stop stops the scheduler.
|
// Stop stops the scheduler.
|
||||||
func (s *Scheduler) Stop() {
|
func (s *Scheduler) Stop() {
|
||||||
|
stop := make(chan struct{})
|
||||||
|
s.stopCh <- stop
|
||||||
|
<-stop
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
for server := range s.servers {
|
for server := range s.servers {
|
||||||
server.unsubscribe()
|
server.unsubscribe()
|
||||||
}
|
}
|
||||||
s.servers = nil
|
s.servers = nil
|
||||||
s.lock.Unlock()
|
s.lock.Unlock()
|
||||||
stop := make(chan struct{})
|
|
||||||
s.stopCh <- stop
|
|
||||||
<-stop
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// syncLoop is the main event loop responsible for event/data processing and
|
// syncLoop is the main event loop responsible for event/data processing and
|
||||||
|
|
@ -219,7 +213,7 @@ func (s *Scheduler) syncLoop() {
|
||||||
return
|
return
|
||||||
case <-s.triggerCh:
|
case <-s.triggerCh:
|
||||||
break loop
|
break loop
|
||||||
//case <-s.testWaitCh:
|
case <-s.testWaitCh:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -244,11 +238,11 @@ func (s *Scheduler) targetChanged() (changed bool) {
|
||||||
// requests are generated and sent if necessary and possible.
|
// requests are generated and sent if necessary and possible.
|
||||||
func (s *Scheduler) processRound() {
|
func (s *Scheduler) processRound() {
|
||||||
for {
|
for {
|
||||||
serverEvents, requestEvents := s.filterEvents()
|
filteredEvents := s.filterEvents()
|
||||||
log.Debug("Processing modules", "server events", len(serverEvents))
|
log.Debug("Processing modules")
|
||||||
for _, module := range s.modules {
|
for _, module := range s.modules {
|
||||||
log.Debug("Processing module", "name", s.names[module], "request events", len(requestEvents[module]))
|
log.Debug("Processing module", "name", s.names[module], "events", len(filteredEvents[module]))
|
||||||
module.Process(append(serverEvents, requestEvents[module]...))
|
module.Process(filteredEvents[module])
|
||||||
}
|
}
|
||||||
if !s.targetChanged() {
|
if !s.targetChanged() {
|
||||||
break
|
break
|
||||||
|
|
@ -358,13 +352,13 @@ func (s *Scheduler) addEvent(event Event) {
|
||||||
// to the given server by adding a failed request event (EvFail), ensuring that
|
// to the given server by adding a failed request event (EvFail), ensuring that
|
||||||
// all requests get finalized and thereby allowing the module logic to be safe
|
// all requests get finalized and thereby allowing the module logic to be safe
|
||||||
// and simple.
|
// and simple.
|
||||||
func (s *Scheduler) filterEvents() (serverEvents []Event, requestEvents map[Module][]Event) {
|
func (s *Scheduler) filterEvents() map[Module][]Event {
|
||||||
s.eventLock.Lock()
|
s.eventLock.Lock()
|
||||||
events := s.events
|
events := s.events
|
||||||
s.events = nil
|
s.events = nil
|
||||||
s.eventLock.Unlock()
|
s.eventLock.Unlock()
|
||||||
|
|
||||||
requestEvents = make(map[Module][]Event)
|
filteredEvents := make(map[Module][]Event)
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
server, ok := event.Server.(server)
|
server, ok := event.Server.(server)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -384,27 +378,29 @@ func (s *Scheduler) filterEvents() (serverEvents []Event, requestEvents map[Modu
|
||||||
if event.Type == EvResponse || event.Type == EvFail {
|
if event.Type == EvResponse || event.Type == EvFail {
|
||||||
delete(s.pending, sid) // final event, close pending request
|
delete(s.pending, sid) // final event, close pending request
|
||||||
}
|
}
|
||||||
requestEvents[pending.module] = append(requestEvents[pending.module], event)
|
filteredEvents[pending.module] = append(filteredEvents[pending.module], event)
|
||||||
} else {
|
} else {
|
||||||
switch event.Type {
|
switch event.Type {
|
||||||
case EvRegistered:
|
case EvRegistered:
|
||||||
s.servers[server] = struct{}{}
|
s.servers[server] = struct{}{}
|
||||||
case EvUnregistered:
|
case EvUnregistered:
|
||||||
s.closePending(event.Server, requestEvents)
|
s.closePending(event.Server, filteredEvents)
|
||||||
delete(s.servers, server)
|
delete(s.servers, server)
|
||||||
}
|
}
|
||||||
serverEvents = append(serverEvents, event)
|
for _, module := range s.modules {
|
||||||
|
filteredEvents[module] = append(filteredEvents[module], event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
}
|
||||||
|
return filteredEvents
|
||||||
}
|
}
|
||||||
|
|
||||||
// closePending closes all pending requests to the given server and adds an EvFail
|
// closePending closes all pending requests to the given server and adds an EvFail
|
||||||
// event to properly finalize them
|
// event to properly finalize them
|
||||||
func (s *Scheduler) closePending(server Server, requestEvents map[Module][]Event) {
|
func (s *Scheduler) closePending(server Server, filteredEvents map[Module][]Event) {
|
||||||
for sid, pending := range s.pending {
|
for sid, pending := range s.pending {
|
||||||
if sid.Server == server {
|
if sid.Server == server {
|
||||||
requestEvents[pending.module] = append(requestEvents[pending.module], Event{
|
filteredEvents[pending.module] = append(filteredEvents[pending.module], Event{
|
||||||
Type: EvFail,
|
Type: EvFail,
|
||||||
Server: server,
|
Server: server,
|
||||||
Data: RequestResponse{
|
Data: RequestResponse{
|
||||||
|
|
|
||||||
128
beacon/light/request/scheduler_test.go
Normal file
128
beacon/light/request/scheduler_test.go
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEventFilter(t *testing.T) {
|
||||||
|
s := NewScheduler()
|
||||||
|
module1 := &testModule{name: "module1"}
|
||||||
|
module2 := &testModule{name: "module2"}
|
||||||
|
s.RegisterModule(module1, "module1")
|
||||||
|
s.RegisterModule(module2, "module2")
|
||||||
|
s.Start()
|
||||||
|
// startup process round without events
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expProcess(t, nil)
|
||||||
|
module2.expProcess(t, nil)
|
||||||
|
srv := &testServer{}
|
||||||
|
// register server; both modules should receive server event
|
||||||
|
s.RegisterServer(srv)
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expProcess(t, []Event{
|
||||||
|
Event{Type: EvRegistered, Server: srv},
|
||||||
|
})
|
||||||
|
module2.expProcess(t, []Event{
|
||||||
|
Event{Type: EvRegistered, Server: srv},
|
||||||
|
})
|
||||||
|
// let module1 send a request
|
||||||
|
srv.canRequest = 1
|
||||||
|
module1.reqc = testRequest
|
||||||
|
s.Trigger()
|
||||||
|
// first triggered round sends the request, no events yet
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expProcess(t, nil)
|
||||||
|
module2.expProcess(t, nil)
|
||||||
|
// next round triggered by EvRequest; only module1 should receive it
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expProcess(t, []Event{
|
||||||
|
Event{Type: EvRequest, Server: srv, Data: RequestResponse{ID: 1, Request: testRequest}},
|
||||||
|
})
|
||||||
|
module2.expProcess(t, nil)
|
||||||
|
// server emits EvTimeout; only module1 should receive it
|
||||||
|
srv.eventCb(Event{Type: EvTimeout, Data: RequestResponse{ID: 1, Request: testRequest}})
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expProcess(t, []Event{
|
||||||
|
Event{Type: EvTimeout, Server: srv, Data: RequestResponse{ID: 1, Request: testRequest}},
|
||||||
|
})
|
||||||
|
module2.expProcess(t, nil)
|
||||||
|
// unregister server; both modules should receive server event
|
||||||
|
s.UnregisterServer(srv)
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expProcess(t, []Event{
|
||||||
|
// module1 should also receive EvFail on its pending request
|
||||||
|
Event{Type: EvFail, Server: srv, Data: RequestResponse{ID: 1, Request: testRequest}},
|
||||||
|
Event{Type: EvUnregistered, Server: srv},
|
||||||
|
})
|
||||||
|
module2.expProcess(t, []Event{
|
||||||
|
Event{Type: EvUnregistered, Server: srv},
|
||||||
|
})
|
||||||
|
// response after server unregistered; should be discarded
|
||||||
|
srv.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 1, Request: testRequest, Response: testResponse}})
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expProcess(t, nil)
|
||||||
|
module2.expProcess(t, nil)
|
||||||
|
// no more process rounds expected; shut down
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expNoMoreProcess(t)
|
||||||
|
module2.expNoMoreProcess(t)
|
||||||
|
s.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
type testServer struct {
|
||||||
|
eventCb func(Event)
|
||||||
|
lastID ID
|
||||||
|
canRequest int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testServer) subscribe(eventCb func(Event)) {
|
||||||
|
s.eventCb = eventCb
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testServer) canRequestNow() (bool, float32) {
|
||||||
|
return s.canRequest > 0, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testServer) sendRequest(req Request) ID {
|
||||||
|
s.canRequest--
|
||||||
|
s.lastID++
|
||||||
|
s.eventCb(Event{Type: EvRequest, Data: RequestResponse{ID: s.lastID, Request: req}})
|
||||||
|
return s.lastID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testServer) Fail(string) {}
|
||||||
|
func (s *testServer) unsubscribe() {}
|
||||||
|
|
||||||
|
type testModule struct {
|
||||||
|
name string
|
||||||
|
processed [][]Event
|
||||||
|
reqc Request // request candidate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testModule) Process(events []Event) {
|
||||||
|
m.processed = append(m.processed, events)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testModule) MakeRequest(Server) (Request, float32) {
|
||||||
|
return m.reqc, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testModule) expProcess(t *testing.T, expEvents []Event) {
|
||||||
|
if len(m.processed) == 0 {
|
||||||
|
t.Errorf("Missing call to %s.Process", m.name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
events := m.processed[0]
|
||||||
|
m.processed = m.processed[1:]
|
||||||
|
if !reflect.DeepEqual(events, expEvents) {
|
||||||
|
t.Errorf("Call to %s.Process with wrong events (expected %v, got %v)", m.name, expEvents, events)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testModule) expNoMoreProcess(t *testing.T) {
|
||||||
|
for len(m.processed) > 0 {
|
||||||
|
t.Errorf("Unexpected call to %s.Process with events %v", m.name, m.processed[0])
|
||||||
|
m.processed = m.processed[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -76,8 +76,8 @@ type server interface {
|
||||||
unsubscribe()
|
unsubscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
// newServer wraps a requestServer and returns a server
|
// NewServer wraps a requestServer and returns a server
|
||||||
func newServer(rs requestServer, clock mclock.Clock) server {
|
func NewServer(rs requestServer, clock mclock.Clock) server {
|
||||||
s := &serverWithLimits{}
|
s := &serverWithLimits{}
|
||||||
s.parent = rs
|
s.parent = rs
|
||||||
s.serverWithTimeout.init(clock)
|
s.serverWithTimeout.init(clock)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ var testEventType = &EventType{Name: "testEvent"}
|
||||||
func TestServerEvents(t *testing.T) {
|
func TestServerEvents(t *testing.T) {
|
||||||
rs := &testRequestServer{}
|
rs := &testRequestServer{}
|
||||||
clock := &mclock.Simulated{}
|
clock := &mclock.Simulated{}
|
||||||
srv := newServer(rs, clock)
|
srv := NewServer(rs, clock)
|
||||||
var lastEventType *EventType
|
var lastEventType *EventType
|
||||||
srv.subscribe(func(event Event) { lastEventType = event.Type })
|
srv.subscribe(func(event Event) { lastEventType = event.Type })
|
||||||
evTypeName := func(evType *EventType) string {
|
evTypeName := func(evType *EventType) string {
|
||||||
|
|
@ -57,7 +57,7 @@ func TestServerEvents(t *testing.T) {
|
||||||
|
|
||||||
func TestServerParallel(t *testing.T) {
|
func TestServerParallel(t *testing.T) {
|
||||||
rs := &testRequestServer{}
|
rs := &testRequestServer{}
|
||||||
srv := newServer(rs, &mclock.Simulated{})
|
srv := NewServer(rs, &mclock.Simulated{})
|
||||||
srv.subscribe(func(event Event) {})
|
srv.subscribe(func(event Event) {})
|
||||||
|
|
||||||
expSend := func(expSent int) {
|
expSend := func(expSent int) {
|
||||||
|
|
@ -91,7 +91,7 @@ func TestServerParallel(t *testing.T) {
|
||||||
func TestServerFail(t *testing.T) {
|
func TestServerFail(t *testing.T) {
|
||||||
rs := &testRequestServer{}
|
rs := &testRequestServer{}
|
||||||
clock := &mclock.Simulated{}
|
clock := &mclock.Simulated{}
|
||||||
srv := newServer(rs, clock)
|
srv := NewServer(rs, clock)
|
||||||
srv.subscribe(func(event Event) {})
|
srv.subscribe(func(event Event) {})
|
||||||
expCanRequest := func(expCanRequest bool) {
|
expCanRequest := func(expCanRequest bool) {
|
||||||
if canRequest, _ := srv.canRequestNow(); canRequest != expCanRequest {
|
if canRequest, _ := srv.canRequestNow(); canRequest != expCanRequest {
|
||||||
|
|
|
||||||
|
|
@ -115,8 +115,7 @@ func blsync(ctx *cli.Context) error {
|
||||||
headSync := sync.NewHeadSync(headTracker, committeeChain)
|
headSync := sync.NewHeadSync(headTracker, committeeChain)
|
||||||
|
|
||||||
// set up scheduler and sync modules
|
// set up scheduler and sync modules
|
||||||
scheduler := request.NewScheduler(&mclock.System{})
|
scheduler := request.NewScheduler()
|
||||||
|
|
||||||
checkpointInit := sync.NewCheckpointInit(committeeChain, chainConfig.Checkpoint)
|
checkpointInit := sync.NewCheckpointInit(committeeChain, chainConfig.Checkpoint)
|
||||||
forwardSync := sync.NewForwardUpdateSync(committeeChain)
|
forwardSync := sync.NewForwardUpdateSync(committeeChain)
|
||||||
beaconBlockSync := newBeaconBlockSync(headTracker)
|
beaconBlockSync := newBeaconBlockSync(headTracker)
|
||||||
|
|
@ -132,7 +131,7 @@ func blsync(ctx *cli.Context) error {
|
||||||
// register server(s)
|
// register server(s)
|
||||||
for _, url := range ctx.StringSlice(utils.BeaconApiFlag.Name) {
|
for _, url := range ctx.StringSlice(utils.BeaconApiFlag.Name) {
|
||||||
beaconApi := api.NewBeaconLightApi(url, customHeader)
|
beaconApi := api.NewBeaconLightApi(url, customHeader)
|
||||||
scheduler.RegisterServer(api.NewApiServer(beaconApi))
|
scheduler.RegisterServer(request.NewServer(api.NewApiServer(beaconApi), &mclock.System{}))
|
||||||
}
|
}
|
||||||
// run until stopped
|
// run until stopped
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue