Sync branch magefile with main #104308
@ -10,17 +10,25 @@ type (
|
|||||||
EventTopic string
|
EventTopic string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Listener is the interface for internal components that want to respond to events.
|
||||||
|
type Listener interface {
|
||||||
|
OnEvent(topic EventTopic, payload interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forwarder is the interface for components that forward events to external systems.
|
||||||
type Forwarder interface {
|
type Forwarder interface {
|
||||||
Broadcast(topic EventTopic, payload interface{})
|
Broadcast(topic EventTopic, payload interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
type Broker struct {
|
type Broker struct {
|
||||||
|
listeners []Listener
|
||||||
forwarders []Forwarder
|
forwarders []Forwarder
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBroker() *Broker {
|
func NewBroker() *Broker {
|
||||||
return &Broker{
|
return &Broker{
|
||||||
|
listeners: []Listener{},
|
||||||
forwarders: []Forwarder{},
|
forwarders: []Forwarder{},
|
||||||
mutex: sync.Mutex{},
|
mutex: sync.Mutex{},
|
||||||
}
|
}
|
||||||
@ -32,10 +40,20 @@ func (b *Broker) AddForwarder(forwarder Forwarder) {
|
|||||||
b.forwarders = append(b.forwarders, forwarder)
|
b.forwarders = append(b.forwarders, forwarder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Broker) AddListener(listener Listener) {
|
||||||
|
b.mutex.Lock()
|
||||||
|
defer b.mutex.Unlock()
|
||||||
|
b.listeners = append(b.listeners, listener)
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Broker) broadcast(topic EventTopic, payload interface{}) {
|
func (b *Broker) broadcast(topic EventTopic, payload interface{}) {
|
||||||
b.mutex.Lock()
|
b.mutex.Lock()
|
||||||
defer b.mutex.Unlock()
|
defer b.mutex.Unlock()
|
||||||
|
|
||||||
|
for _, listener := range b.listeners {
|
||||||
|
listener.OnEvent(topic, payload)
|
||||||
|
}
|
||||||
|
|
||||||
for _, forwarder := range b.forwarders {
|
for _, forwarder := range b.forwarders {
|
||||||
forwarder.Broadcast(topic, payload)
|
forwarder.Broadcast(topic, payload)
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var socketIOEventTypes = map[string]string{
|
var socketIOEventTypes = map[string]string{
|
||||||
|
reflect.TypeOf(api.EventLifeCycle{}).Name(): "/lifecycle",
|
||||||
|
reflect.TypeOf(api.EventFarmStatus{}).Name(): "/status",
|
||||||
reflect.TypeOf(api.EventJobUpdate{}).Name(): "/jobs",
|
reflect.TypeOf(api.EventJobUpdate{}).Name(): "/jobs",
|
||||||
reflect.TypeOf(api.EventTaskUpdate{}).Name(): "/task",
|
reflect.TypeOf(api.EventTaskUpdate{}).Name(): "/task",
|
||||||
reflect.TypeOf(api.EventLastRenderedUpdate{}).Name(): "/last-rendered",
|
reflect.TypeOf(api.EventLastRenderedUpdate{}).Name(): "/last-rendered",
|
||||||
@ -91,6 +93,10 @@ func (s *SocketIOForwarder) registerSIOEventHandlers() {
|
|||||||
_ = sio.On(gosocketio.OnConnection, func(c *gosocketio.Channel) {
|
_ = sio.On(gosocketio.OnConnection, func(c *gosocketio.Channel) {
|
||||||
logger := sioLogger(c)
|
logger := sioLogger(c)
|
||||||
logger.Debug().Msg("socketIO: connected")
|
logger.Debug().Msg("socketIO: connected")
|
||||||
|
|
||||||
|
// All SocketIO connections get these events, regardless of their subscription.
|
||||||
|
_ = c.Join(string(TopicLifeCycle))
|
||||||
|
_ = c.Join(string(TopicFarmStatus))
|
||||||
})
|
})
|
||||||
|
|
||||||
// socket disconnection
|
// socket disconnection
|
||||||
|
@ -20,7 +20,7 @@ const (
|
|||||||
//
|
//
|
||||||
// Note that this indicates the time between polls, so between a poll
|
// Note that this indicates the time between polls, so between a poll
|
||||||
// operation being done, and the next one starting.
|
// operation being done, and the next one starting.
|
||||||
pollWait = 5 * time.Second
|
pollWait = 30 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
// Service keeps track of the overall farm status.
|
// Service keeps track of the overall farm status.
|
||||||
@ -30,17 +30,24 @@ type Service struct {
|
|||||||
|
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
lastReport api.FarmStatusReport
|
lastReport api.FarmStatusReport
|
||||||
|
forcePoll chan struct{} // Send anything here to force a poll, if none is running yet.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewService returns a 'farm status' service. Run its Run() function in a
|
||||||
|
// goroutine to make it actually do something.
|
||||||
func NewService(persist PersistenceService, eventbus EventBus) *Service {
|
func NewService(persist PersistenceService, eventbus EventBus) *Service {
|
||||||
return &Service{
|
service := Service{
|
||||||
persist: persist,
|
persist: persist,
|
||||||
eventbus: eventbus,
|
eventbus: eventbus,
|
||||||
mutex: sync.Mutex{},
|
mutex: sync.Mutex{},
|
||||||
|
forcePoll: make(chan struct{}, 1),
|
||||||
lastReport: api.FarmStatusReport{
|
lastReport: api.FarmStatusReport{
|
||||||
Status: api.FarmStatusStarting,
|
Status: api.FarmStatusStarting,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eventbus.AddListener(&service)
|
||||||
|
return &service
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the farm status polling loop.
|
// Run the farm status polling loop.
|
||||||
@ -54,10 +61,43 @@ func (s *Service) Run(ctx context.Context) {
|
|||||||
return
|
return
|
||||||
case <-time.After(pollWait):
|
case <-time.After(pollWait):
|
||||||
s.poll(ctx)
|
s.poll(ctx)
|
||||||
|
case <-s.forcePoll:
|
||||||
|
s.poll(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) OnEvent(topic eventbus.EventTopic, payload interface{}) {
|
||||||
|
forcePoll := false
|
||||||
|
eventSubject := ""
|
||||||
|
|
||||||
|
switch event := payload.(type) {
|
||||||
|
case api.EventJobUpdate:
|
||||||
|
forcePoll = event.PreviousStatus != nil && *event.PreviousStatus != event.Status
|
||||||
|
eventSubject = "job"
|
||||||
|
case api.EventWorkerUpdate:
|
||||||
|
forcePoll = event.PreviousStatus != nil && *event.PreviousStatus != event.Status
|
||||||
|
eventSubject = "worker"
|
||||||
|
}
|
||||||
|
|
||||||
|
if !forcePoll {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Str("event", string(topic)).
|
||||||
|
Msgf("farm status: investigating after %s status change", eventSubject)
|
||||||
|
|
||||||
|
// Polling queries the database, and thus can have a non-trivial duration.
|
||||||
|
// Better to run in the Run() goroutine.
|
||||||
|
select {
|
||||||
|
case s.forcePoll <- struct{}{}:
|
||||||
|
default:
|
||||||
|
// If sending to the channel fails, there is already a struct{}{} in
|
||||||
|
// there, and thus a poll will be triggered ASAP anyway.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Report returns the last-known farm status report.
|
// Report returns the last-known farm status report.
|
||||||
//
|
//
|
||||||
// It is updated every few seconds, from the Run() function.
|
// It is updated every few seconds, from the Run() function.
|
||||||
|
@ -225,6 +225,8 @@ func fixtures(t *testing.T) *Fixtures {
|
|||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// calling NewService() immediate registers as a listener with the event bus.
|
||||||
|
f.eventbus.EXPECT().AddListener(gomock.Any())
|
||||||
f.service = NewService(f.persist, f.eventbus)
|
f.service = NewService(f.persist, f.eventbus)
|
||||||
|
|
||||||
return &f
|
return &f
|
||||||
|
@ -19,6 +19,7 @@ type PersistenceService interface {
|
|||||||
var _ PersistenceService = (*persistence.DB)(nil)
|
var _ PersistenceService = (*persistence.DB)(nil)
|
||||||
|
|
||||||
type EventBus interface {
|
type EventBus interface {
|
||||||
|
AddListener(listener eventbus.Listener)
|
||||||
BroadcastFarmStatusEvent(event api.EventFarmStatus)
|
BroadcastFarmStatusEvent(event api.EventFarmStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
eventbus "projects.blender.org/studio/flamenco/internal/manager/eventbus"
|
||||||
persistence "projects.blender.org/studio/flamenco/internal/manager/persistence"
|
persistence "projects.blender.org/studio/flamenco/internal/manager/persistence"
|
||||||
api "projects.blender.org/studio/flamenco/pkg/api"
|
api "projects.blender.org/studio/flamenco/pkg/api"
|
||||||
)
|
)
|
||||||
@ -89,6 +90,18 @@ func (m *MockEventBus) EXPECT() *MockEventBusMockRecorder {
|
|||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddListener mocks base method.
|
||||||
|
func (m *MockEventBus) AddListener(arg0 eventbus.Listener) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "AddListener", arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddListener indicates an expected call of AddListener.
|
||||||
|
func (mr *MockEventBusMockRecorder) AddListener(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddListener", reflect.TypeOf((*MockEventBus)(nil).AddListener), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
// BroadcastFarmStatusEvent mocks base method.
|
// BroadcastFarmStatusEvent mocks base method.
|
||||||
func (m *MockEventBus) BroadcastFarmStatusEvent(arg0 api.EventFarmStatus) {
|
func (m *MockEventBus) BroadcastFarmStatusEvent(arg0 api.EventFarmStatus) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
Loading…
Reference in New Issue
Block a user