beacon/light: added comments, made event logic nicer

This commit is contained in:
Zsolt Felfoldi 2024-01-21 13:37:07 +01:00 committed by Felix Lange
parent 2ea0421599
commit f734747364
4 changed files with 120 additions and 108 deletions

View file

@ -60,6 +60,7 @@ func (s *ApiServer) Subscribe(eventCallback func(event request.Event)) {
func (s *ApiServer) SendRequest(req request.Request) request.ID { func (s *ApiServer) SendRequest(req request.Request) request.ID {
id := request.ID(atomic.AddUint64(&s.lastId, 1)) id := request.ID(atomic.AddUint64(&s.lastId, 1))
go func() { go func() {
s.eventCallback(request.Event{Type: request.EvRequest, Data: request.RequestResponse{ID: id, Request: req}})
var resp request.Response var resp request.Response
switch data := req.(type) { switch data := req.(type) {
case sync.ReqUpdates: case sync.ReqUpdates:

View file

@ -64,11 +64,13 @@ type Scheduler struct {
servers map[server]struct{} servers map[server]struct{}
targets map[targetData]uint64 targets map[targetData]uint64
pending map[ServerAndID]pendingRequest pending map[ServerAndID]pendingRequest
eventLock sync.Mutex // eventLock guards access to the events list. Note that eventLock can be
serverEvents []Event // locked either while lock is locked or unlocked but lock cannot be locked
requestEvents map[Module][]Event // while eventLock is locked.
stopCh chan chan struct{} eventLock sync.Mutex
events []Event
stopCh chan chan struct{}
triggerCh chan struct{} // restarts waiting sync loop triggerCh chan struct{} // restarts waiting sync loop
// testWaitCh chan struct{} // accepts sends when sync loop is waiting // testWaitCh chan struct{} // accepts sends when sync loop is waiting
@ -150,15 +152,10 @@ func (s *Scheduler) RegisterServer(rs requestServer) {
defer s.lock.Unlock() defer s.lock.Unlock()
server := newServer(rs, s.clock) server := newServer(rs, s.clock)
s.servers[server] = struct{}{}
s.eventLock.Lock()
s.addEvent(Event{Type: EvRegistered, Server: server}) s.addEvent(Event{Type: EvRegistered, Server: server})
s.eventLock.Unlock()
server.subscribe(func(event Event) { server.subscribe(func(event Event) {
event.Server = server event.Server = server
s.eventLock.Lock()
s.addEvent(event) s.addEvent(event)
s.eventLock.Unlock()
}) })
} }
@ -170,10 +167,7 @@ func (s *Scheduler) UnregisterServer(rs requestServer) {
for server := range s.servers { for server := range s.servers {
if sl, ok := server.(*serverWithLimits); ok && sl.parent == rs { if sl, ok := server.(*serverWithLimits); ok && sl.parent == rs {
server.unsubscribe() server.unsubscribe()
s.eventLock.Lock()
s.addEvent(Event{Type: EvUnregistered, Server: server}) s.addEvent(Event{Type: EvUnregistered, Server: server})
s.eventLock.Unlock()
delete(s.servers, server)
return return
} }
} }
@ -239,20 +233,16 @@ func (s *Scheduler) targetChanged() (changed bool) {
// processModules runs an entire processing round, calling the Process functions // processModules runs an entire processing round, calling the Process functions
// of all modules, passing all relevant events. // of all modules, passing all relevant events.
func (s *Scheduler) processModules() { func (s *Scheduler) processModules() {
s.eventLock.Lock() serverEvents, requestEvents := s.filterEvents()
serverEvents, requestEvents := s.serverEvents, s.requestEvents log.Debug("Processing modules", "server events", len(serverEvents))
s.serverEvents, s.requestEvents = nil, nil
s.eventLock.Unlock()
log.Debug("Processing modules", "server events", len(s.serverEvents))
for _, module := range s.modules { for _, module := range s.modules {
log.Debug("Processing module", "name", s.names[module], "request events", len(s.requestEvents[module])) log.Debug("Processing module", "name", s.names[module], "request events", len(requestEvents[module]))
module.Process(append(serverEvents, requestEvents[module]...)) module.Process(append(serverEvents, requestEvents[module]...))
} }
} }
func (s *Scheduler) sendRequests() { func (s *Scheduler) sendRequests() {
servers := make(serverSet) servers := make(map[server]struct{})
for server := range s.servers { for server := range s.servers {
if ok, _ := server.canRequestNow(); ok { if ok, _ := server.canRequestNow(); ok {
servers[server] = struct{}{} servers[server] = struct{}{}
@ -264,23 +254,25 @@ func (s *Scheduler) sendRequests() {
if len(servers) == 0 { if len(servers) == 0 {
return return
} }
s.eventLock.Lock() // ensure that EvRequest is added first if s.tryRequest(module, servers) {
if req, sent := s.tryRequest(module, servers); sent {
log.Debug("Sent request", "module", s.names[module]) log.Debug("Sent request", "module", s.names[module])
s.addEvent(Event{
Type: EvRequest,
Server: req.Server,
Data: RequestResponse{
ID: req.ID,
Request: req.Request,
},
})
} }
s.eventLock.Unlock()
} }
} }
func (s *Scheduler) tryRequest(module Module, servers serverSet) (RequestWithID, bool) { // tryRequest tries to generate request candidates for a given module and a given
// set of servers, then selects the best candidate if there is one and sends the
// request.
// The candidates are primarily ranked based on "request priority", a number that
// Module.MakeRequest has returned along with the request candidate. This ranking
// may or may not be used depending on the type of the request, identical requests
// typically have the same priority while multiple item requests may have a
// priority based on the number of items requested.
// If there are multiple candidates with identical request priority then they are
// ranked based on "server priority" which is determined by the server. This value
// is typically higher is the server is expected to respond quicker or with a
// higher chance (typically a lower number of pending requests).
func (s *Scheduler) tryRequest(module Module, servers map[server]struct{}) bool {
var ( var (
maxServerPriority, maxRequestPriority float32 maxServerPriority, maxRequestPriority float32
bestServer server bestServer server
@ -309,11 +301,11 @@ func (s *Scheduler) tryRequest(module Module, servers serverSet) (RequestWithID,
} }
log.Debug("Request attempt", "serverCount", serverCount, "removedServers", removed, "requestCandidates", candidates) log.Debug("Request attempt", "serverCount", serverCount, "removedServers", removed, "requestCandidates", candidates)
if bestServer == nil { if bestServer == nil {
return RequestWithID{}, false return false
} }
id := ServerAndID{Server: bestServer, ID: bestServer.sendRequest(bestRequest)} id := ServerAndID{Server: bestServer, ID: bestServer.sendRequest(bestRequest)}
s.pending[id] = pendingRequest{request: bestRequest, module: module} s.pending[id] = pendingRequest{request: bestRequest, module: module}
return RequestWithID{ServerAndID: id, Request: bestRequest}, true return true
} }
// Trigger starts a new processing round. If fired during processing, it ensures // Trigger starts a new processing round. If fired during processing, it ensures
@ -325,47 +317,70 @@ func (s *Scheduler) Trigger() {
} }
} }
// addEvent adds an Event either as a request event or a server event, depending // addEvent adds an event to be processed in the next round. Note that it can be
// on its type. In case of an EvUnregistered server event it also closes all // called regardless of the state of the lock mutex, making it safe for use in
// pending requests to the given server by adding a failed request event (EvFail), // the server event callback.
// ensuring that all requests get finalized and thereby allowing the module logic
// to be safe and simple.
func (s *Scheduler) addEvent(event Event) { func (s *Scheduler) addEvent(event Event) {
if _, ok := s.servers[event.Server.(server)]; !ok { s.eventLock.Lock()
return s.events = append(s.events, event)
} s.eventLock.Unlock()
s.Trigger() s.Trigger()
if event.IsRequestEvent() {
sid, _, _ := event.RequestInfo()
if pr, ok := s.pending[sid]; ok {
s.addRequestEvent(pr.module, event)
if event.Type == EvResponse || event.Type == EvFail {
delete(s.pending, sid)
}
}
return
}
if event.Type == EvUnregistered {
for id, pending := range s.pending {
if id.Server != event.Server {
continue
}
s.addRequestEvent(pending.module, Event{
Type: EvFail,
Server: event.Server,
Data: RequestResponse{
ID: id.ID,
Request: pending.request,
},
})
}
}
s.serverEvents = append(s.serverEvents, event)
} }
func (s *Scheduler) addRequestEvent(module Module, event Event) { // filterEvent sorts each Event either as a request event or a server event,
if s.requestEvents == nil { // depending on its type. Request events are also sorted in a map based on the
s.requestEvents = make(map[Module][]Event) // module that originally initiated the request. It also ensures that no events
// related to a server are returned before EvRegistered or after EvUnregistered.
// In case of an EvUnregistered server event it also closes all pending requests
// 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
// and simple.
func (s *Scheduler) filterEvents() (serverEvents []Event, requestEvents map[Module][]Event) {
s.eventLock.Lock()
events := s.events
s.events = nil
s.eventLock.Unlock()
requestEvents = make(map[Module][]Event)
for _, event := range events {
server, ok := event.Server.(server)
if !ok {
log.Error("Server interface type unknown for Scheduler")
continue
}
if event.Type == EvRegistered {
s.servers[server] = struct{}{}
}
if _, ok := s.servers[server]; !ok {
continue
}
if event.IsRequestEvent() {
sid, _, _ := event.RequestInfo()
if pr, ok := s.pending[sid]; ok {
requestEvents[pr.module] = append(requestEvents[pr.module], event)
if event.Type == EvResponse || event.Type == EvFail {
delete(s.pending, sid)
}
}
return
}
if event.Type == EvUnregistered {
delete(s.servers, server)
for id, pending := range s.pending {
if id.Server != event.Server {
continue
}
requestEvents[pending.module] = append(requestEvents[pending.module], Event{
Type: EvFail,
Server: event.Server,
Data: RequestResponse{
ID: id.ID,
Request: pending.request,
},
})
}
}
serverEvents = append(serverEvents, event)
} }
s.requestEvents[module] = append(s.requestEvents[module], event) return
} }

View file

@ -85,8 +85,6 @@ func newServer(rs requestServer, clock mclock.Clock) server {
return s return s
} }
type serverSet map[server]struct{}
// EventType identifies an event type, either related to a request or the server // EventType identifies an event type, either related to a request or the server
// in general. Server events can also be externally defined. // in general. Server events can also be externally defined.
type EventType struct { type EventType struct {
@ -158,6 +156,35 @@ func (s *serverWithTimeout) eventCallback(event Event) {
defer s.lock.Unlock() defer s.lock.Unlock()
switch event.Type { switch event.Type {
case EvRequest:
id := event.Data.(RequestResponse).ID
s.timeouts[id] = s.clock.AfterFunc(softRequestTimeout, func() {
/*if s.testTimerResults != nil {
s.testTimerResults = append(s.testTimerResults, true) // simulated timer finished
}*/
s.lock.Lock()
if _, ok := s.timeouts[id]; !ok {
s.lock.Unlock()
return
}
s.timeouts[id] = s.clock.AfterFunc(hardRequestTimeout-softRequestTimeout, func() {
/*if s.testTimerResults != nil {
s.testTimerResults = append(s.testTimerResults, true) // simulated timer finished
}*/
s.lock.Lock()
if _, ok := s.timeouts[id]; !ok {
s.lock.Unlock()
return
}
delete(s.timeouts, id)
childEventCb := s.childEventCb
s.lock.Unlock()
childEventCb(Event{Type: EvFail, Data: event.Data})
})
childEventCb := s.childEventCb
s.lock.Unlock()
childEventCb(Event{Type: EvTimeout, Data: event.Data})
})
case EvResponse, EvFail: case EvResponse, EvFail:
id := event.Data.(RequestResponse).ID id := event.Data.(RequestResponse).ID
if timer, ok := s.timeouts[id]; ok { if timer, ok := s.timeouts[id]; ok {
@ -175,38 +202,7 @@ func (s *serverWithTimeout) eventCallback(event Event) {
// sendRequest sends a request through the parent (requestServer) and starts a // sendRequest sends a request through the parent (requestServer) and starts a
// timer for request timeout. // timer for request timeout.
func (s *serverWithTimeout) sendRequest(request Request) (reqId ID) { func (s *serverWithTimeout) sendRequest(request Request) (reqId ID) {
s.lock.Lock() return s.parent.SendRequest(request)
defer s.lock.Unlock()
reqId = s.parent.SendRequest(request)
s.timeouts[reqId] = s.clock.AfterFunc(softRequestTimeout, func() {
/*if s.testTimerResults != nil {
s.testTimerResults = append(s.testTimerResults, true) // simulated timer finished
}*/
s.lock.Lock()
if _, ok := s.timeouts[reqId]; !ok {
s.lock.Unlock()
return
}
s.timeouts[reqId] = s.clock.AfterFunc(hardRequestTimeout-softRequestTimeout, func() {
/*if s.testTimerResults != nil {
s.testTimerResults = append(s.testTimerResults, true) // simulated timer finished
}*/
s.lock.Lock()
if _, ok := s.timeouts[reqId]; !ok {
s.lock.Unlock()
return
}
delete(s.timeouts, reqId)
childEventCb := s.childEventCb
s.lock.Unlock()
childEventCb(Event{Type: EvFail, Data: RequestResponse{ID: reqId, Request: request}})
})
childEventCb := s.childEventCb
s.lock.Unlock()
childEventCb(Event{Type: EvTimeout, Data: RequestResponse{ID: reqId, Request: request}})
})
return reqId
} }
// stop stops all goroutines associated with the server. // stop stops all goroutines associated with the server.

View file

@ -254,7 +254,7 @@ func (s *ForwardUpdateSync) Process(events []request.Event) {
} }
// try processing ordered list of available responses // try processing ordered list of available responses
sort.Sort(updateResponseList(s.processQueue)) //TODO sort.Sort(updateResponseList(s.processQueue))
for s.processQueue != nil { for s.processQueue != nil {
event := s.processQueue[0] event := s.processQueue[0]
if !s.processResponse(event) { if !s.processResponse(event) {