Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

- Comments should explain non-obvious intent, invariants, or constraints in the current code. Do not mention the old implementation/state (for example, "preserve the behavior of the original query"); state the current rule directly.

## Tests

- Do not include customer names, tenant names, or production namespace names in test names, fixtures, comments, or logs. Describe the scenario by structural properties instead (for example, `dense_high_action_fanout`, not a customer or shard name).

## Docs MDX

- In MDX JSX component bodies, such as `<Callout>`, avoid Markdown link syntax (`[text](href)`). Prettier can wrap the label across lines and break MDX parsing. Use an explicit JSX link instead:
Expand Down
75 changes: 10 additions & 65 deletions pkg/scheduling/v1/action.go
Original file line number Diff line number Diff line change
@@ -1,83 +1,28 @@
package v1

import (
"slices"
"sync"
"time"

"github.com/google/uuid"
)

type action struct {
mu sync.RWMutex
actionId string

lastReplenishedSlotCount int
lastReplenishedWorkerCount int

// note that slots can be used across multiple actions, hence the pointer
slots []*slot

// slotsByTypeAndWorkerId indexes slots by slotType -> workerId -> slots.
//
// NOTE: this index contains pointers to the same slot objects as slots.
// It is built/replaced during replenish under a.mu and read during assignment
// under a.mu (RLock or Lock).
slotsByTypeAndWorkerId map[string]map[uuid.UUID][]*slot
// workerIds is the thin action index into Scheduler.pools.
workerIds []uuid.UUID
}

func (a *action) activeCount() int {
a.mu.RLock()
defer a.mu.RUnlock()

func (a *action) activeCountFromPools(poolsByWorker map[uuid.UUID]map[string]*slotPool, now time.Time) int {
count := 0

for _, slot := range a.slots {
if slot.active() {
count++
for _, workerId := range a.workerIds {
for _, pool := range poolsByWorker[workerId] {
if pool.staleAt(now) {
return 0
}
count += pool.unusedCount()
}
}

return count
}

// orderedLock acquires the locks in a stable order to prevent deadlocks
func orderedLock(actionsMap map[string]*action) {
actions := sortActions(actionsMap)

for _, action := range actions {
action.mu.Lock()
}
}

// orderedUnlock releases the locks in a stable order to prevent deadlocks. it returns
// a function that should be deferred to unlock the locks.
func orderedUnlock(actionsMap map[string]*action) func() {
actions := sortActions(actionsMap)

return func() {
for _, action := range actions {
action.mu.Unlock()
}
}
}

func sortActions(actionsMap map[string]*action) []*action {
actions := make([]*action, 0, len(actionsMap))

for _, action := range actionsMap {
actions = append(actions, action)
}

slices.SortStableFunc(actions, func(i, j *action) int {
switch {
case i.actionId < j.actionId:
return -1
case i.actionId > j.actionId:
return 1
default:
return 0
}
})

return actions
}
Loading
Loading