| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- package executor
- import (
- "fmt"
- "slices"
- "sync"
- config "github.com/OliveTin/OliveTin/internal/config"
- log "github.com/sirupsen/logrus"
- )
- type groupLimit struct {
- name string
- maxConcurrent int
- }
- type queuedExecution struct {
- req *ExecutionRequest
- wg *sync.WaitGroup
- }
- func actionGroupLimits(req *ExecutionRequest) []groupLimit {
- if !hasActionGroupContext(req) {
- return nil
- }
- limits := make([]groupLimit, 0, len(req.Binding.Action.Groups))
- for _, groupName := range req.Binding.Action.Groups {
- if limit, ok := groupLimitFromConfig(req.Cfg, groupName); ok {
- limits = append(limits, limit)
- }
- }
- return limits
- }
- func hasActionGroupContext(req *ExecutionRequest) bool {
- return req != nil && req.Binding != nil && req.Binding.Action != nil && req.Cfg != nil
- }
- func groupLimitFromConfig(cfg *config.Config, groupName string) (groupLimit, bool) {
- group, found := cfg.ActionGroups[groupName]
- if !found || group == nil || group.MaxConcurrent < 1 {
- return groupLimit{}, false
- }
- return groupLimit{name: groupName, maxConcurrent: group.MaxConcurrent}, true
- }
- func actionNeedsGroupLimit(req *ExecutionRequest) bool {
- return len(actionGroupLimits(req)) > 0
- }
- func actionInGroup(action *config.Action, groupName string) bool {
- if action == nil {
- return false
- }
- return slices.Contains(action.Groups, groupName)
- }
- func (e *Executor) countActiveInGroup(groupName string) int {
- count := 0
- e.logmutex.RLock()
- defer e.logmutex.RUnlock()
- for _, logEntry := range e.logs {
- if logEntryIsActiveInGroup(logEntry, groupName) {
- count++
- }
- }
- return count
- }
- func logEntryIsActiveInGroup(logEntry *InternalLogEntry, groupName string) bool {
- if inactiveLogEntry(logEntry) {
- return false
- }
- return actionInGroup(logEntry.Binding.Action, groupName)
- }
- func inactiveLogEntry(logEntry *InternalLogEntry) bool {
- if logEntry == nil {
- return true
- }
- return logEntryIsInactive(logEntry)
- }
- func logEntryIsInactive(logEntry *InternalLogEntry) bool {
- if logEntry.ExecutionFinished || logEntry.Queued {
- return true
- }
- return logEntry.Binding == nil || logEntry.Binding.Action == nil
- }
- func (e *Executor) groupsHaveCapacityForActive(req *ExecutionRequest) bool {
- for _, limit := range actionGroupLimits(req) {
- if e.countActiveInGroup(limit.name) >= (limit.maxConcurrent + 1) {
- return false
- }
- }
- return true
- }
- func (e *Executor) groupsHaveCapacityForQueued(req *ExecutionRequest) bool {
- for _, limit := range actionGroupLimits(req) {
- if e.countActiveInGroup(limit.name) >= limit.maxConcurrent {
- return false
- }
- }
- return true
- }
- func firstFullGroupName(e *Executor, req *ExecutionRequest) string {
- for _, limit := range actionGroupLimits(req) {
- if e.countActiveInGroup(limit.name) >= (limit.maxConcurrent + 1) {
- return limit.name
- }
- }
- return ""
- }
- func (e *Executor) queueRequest(req *ExecutionRequest, wg *sync.WaitGroup) {
- groupName := firstFullGroupName(e, req)
- req.logEntry.Queued = true
- req.logEntry.QueuedForGroup = groupName
- req.logEntry.Output = fmt.Sprintf("Queued waiting for action group %q", groupName)
- log.WithFields(log.Fields{
- "actionTitle": req.logEntry.ActionTitle,
- "groupName": groupName,
- }).Infof("Action queued due to action group concurrency limit")
- e.groupQueueMu.Lock()
- e.groupQueue = append(e.groupQueue, &queuedExecution{req: req, wg: wg})
- e.groupQueueMu.Unlock()
- }
- func (e *Executor) drainGroupQueue() {
- e.groupQueueMu.Lock()
- if len(e.groupQueue) == 0 {
- e.groupQueueMu.Unlock()
- return
- }
- next := e.groupQueue[0]
- if !e.groupsHaveCapacityForQueued(next.req) {
- e.groupQueueMu.Unlock()
- return
- }
- e.groupQueue = e.groupQueue[1:]
- e.groupQueueMu.Unlock()
- go e.runDequeuedExecution(next)
- }
- func (e *Executor) runDequeuedExecution(queued *queuedExecution) {
- req := queued.req
- e.logmutex.Lock()
- req.logEntry.Queued = false
- req.logEntry.QueuedForGroup = ""
- e.logmutex.Unlock()
- req.skipRequestRegistration = true
- e.runExecutionSteps(req)
- e.finishExecChain(req)
- queued.wg.Done()
- }
|