github.com/safing/portbase@v0.19.5/modules/events.go (about) 1 package modules 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 8 "github.com/tevino/abool" 9 10 "github.com/safing/portbase/log" 11 ) 12 13 type eventHooks struct { 14 // hooks holds all registered hooks for the event. 15 hooks []*eventHook 16 17 // internal signifies that the event and it's data may not be exposed and may 18 // only be propagated internally. 19 internal bool 20 } 21 22 type eventHookFn func(context.Context, interface{}) error 23 24 type eventHook struct { 25 description string 26 hookingModule *Module 27 hookFn eventHookFn 28 } 29 30 // TriggerEvent executes all hook functions registered to the specified event. 31 func (m *Module) TriggerEvent(event string, data interface{}) { 32 if m.OnlineSoon() { 33 go m.processEventTrigger(event, data) 34 } 35 } 36 37 func (m *Module) processEventTrigger(event string, data interface{}) { 38 m.eventHooksLock.RLock() 39 defer m.eventHooksLock.RUnlock() 40 41 eventHooks, ok := m.eventHooks[event] 42 if !ok { 43 log.Warningf(`%s: tried to trigger non-existent event "%s"`, m.Name, event) 44 return 45 } 46 47 for _, hook := range eventHooks.hooks { 48 if hook.hookingModule.OnlineSoon() { 49 go m.runEventHook(hook, event, data) 50 } 51 } 52 53 // Call subscription function, if set. 54 if eventSubscriptionFuncReady.IsSet() { 55 m.StartWorker("event subscription", func(context.Context) error { 56 // Only use data in worker that won't change anymore. 57 eventSubscriptionFunc(m.Name, event, eventHooks.internal, data) 58 return nil 59 }) 60 } 61 } 62 63 // InjectEvent triggers an event from a foreign module and executes all hook functions registered to that event. 64 func (m *Module) InjectEvent(sourceEventName, targetModuleName, targetEventName string, data interface{}) error { 65 if !m.OnlineSoon() { 66 return errors.New("module not yet started") 67 } 68 69 if !modulesLocked.IsSet() { 70 return errors.New("module system not yet started") 71 } 72 73 targetModule, ok := modules[targetModuleName] 74 if !ok { 75 return fmt.Errorf(`module "%s" does not exist`, targetModuleName) 76 } 77 78 targetModule.eventHooksLock.RLock() 79 defer targetModule.eventHooksLock.RUnlock() 80 81 targetHooks, ok := targetModule.eventHooks[targetEventName] 82 if !ok { 83 return fmt.Errorf(`module "%s" has no event named "%s"`, targetModuleName, targetEventName) 84 } 85 86 for _, hook := range targetHooks.hooks { 87 if hook.hookingModule.OnlineSoon() { 88 go m.runEventHook(hook, sourceEventName, data) 89 } 90 } 91 92 // Call subscription function, if set. 93 if eventSubscriptionFuncReady.IsSet() { 94 m.StartWorker("event subscription", func(context.Context) error { 95 // Only use data in worker that won't change anymore. 96 eventSubscriptionFunc(targetModule.Name, targetEventName, targetHooks.internal, data) 97 return nil 98 }) 99 } 100 101 return nil 102 } 103 104 func (m *Module) runEventHook(hook *eventHook, event string, data interface{}) { 105 // check if source module is ready for handling 106 if m.Status() != StatusOnline { 107 // source module has not yet fully started, wait until start is complete 108 select { 109 case <-m.StartCompleted(): 110 // continue with hook execution 111 case <-hook.hookingModule.Stopping(): 112 return 113 case <-m.Stopping(): 114 return 115 } 116 } 117 118 // check if destionation module is ready for handling 119 if hook.hookingModule.Status() != StatusOnline { 120 // target module has not yet fully started, wait until start is complete 121 select { 122 case <-hook.hookingModule.StartCompleted(): 123 // continue with hook execution 124 case <-hook.hookingModule.Stopping(): 125 return 126 case <-m.Stopping(): 127 return 128 } 129 } 130 131 err := hook.hookingModule.RunWorker( 132 fmt.Sprintf("event hook %s/%s -> %s/%s", m.Name, event, hook.hookingModule.Name, hook.description), 133 func(ctx context.Context) error { 134 return hook.hookFn(ctx, data) 135 }, 136 ) 137 if err != nil { 138 log.Warningf("%s: failed to execute event hook %s/%s -> %s/%s: %s", hook.hookingModule.Name, m.Name, event, hook.hookingModule.Name, hook.description, err) 139 } 140 } 141 142 // RegisterEvent registers a new event to allow for registering hooks. 143 // The expose argument controls whether these events and the attached data may 144 // be received by external components via APIs. If not exposed, the database 145 // record that carries the event and it's data will be marked as secret and as 146 // a crown jewel. Enforcement is left to the database layer. 147 func (m *Module) RegisterEvent(event string, expose bool) { 148 m.eventHooksLock.Lock() 149 defer m.eventHooksLock.Unlock() 150 151 _, ok := m.eventHooks[event] 152 if !ok { 153 m.eventHooks[event] = &eventHooks{ 154 hooks: make([]*eventHook, 0, 1), 155 internal: !expose, 156 } 157 } 158 } 159 160 // RegisterEventHook registers a hook function with (another) modules' event. Whenever a hook is triggered and the receiving module has not yet fully started, hook execution will be delayed until the modules completed starting. 161 func (m *Module) RegisterEventHook(module string, event string, description string, fn func(context.Context, interface{}) error) error { 162 // get target module 163 var eventModule *Module 164 if module == m.Name { 165 eventModule = m 166 } else { 167 var ok bool 168 eventModule, ok = modules[module] 169 if !ok { 170 return fmt.Errorf(`module "%s" does not exist`, module) 171 } 172 } 173 174 // get target event 175 eventModule.eventHooksLock.Lock() 176 defer eventModule.eventHooksLock.Unlock() 177 eventHooks, ok := eventModule.eventHooks[event] 178 if !ok { 179 return fmt.Errorf(`event "%s/%s" does not exist`, eventModule.Name, event) 180 } 181 182 // add hook 183 eventHooks.hooks = append(eventHooks.hooks, &eventHook{ 184 description: description, 185 hookingModule: m, 186 hookFn: fn, 187 }) 188 return nil 189 } 190 191 // Subscribe to all events 192 193 var ( 194 eventSubscriptionFunc func(moduleName, eventName string, internal bool, data interface{}) 195 eventSubscriptionFuncEnabled = abool.NewBool(false) 196 eventSubscriptionFuncReady = abool.NewBool(false) 197 ) 198 199 // SetEventSubscriptionFunc sets a function that is called for every event. 200 // This enabled the runtime package to expose events. 201 func SetEventSubscriptionFunc(fn func(moduleName, eventName string, internal bool, data interface{})) bool { 202 if eventSubscriptionFuncEnabled.SetToIf(false, true) { 203 eventSubscriptionFunc = fn 204 eventSubscriptionFuncReady.Set() 205 return true 206 } 207 return false 208 }