//go:build windows package main import ( "fmt" "sort" "time" ) type processError struct { Name string PID uint32 Description string } type processSnapshot struct { Processes []procInfo // Complete PID membership, including processes whose metadata was inaccessible. PIDs map[uint32]bool Errors []processError } type processBackend interface { Snapshot() (processSnapshot, error) Alive(*procInfo) (bool, error) Priority(*procInfo) (uint32, error) SetPriority(*procInfo, uint32) error Close(*procInfo) } type priorityChange struct { Name string PID uint32 RSS uint64 Before, After uint32 Heavy, Unchanged, Blocked bool } type scanReport struct { Title string Rows []priorityChange Errors []processError DryRun bool Unrestored int } type processState struct { info procInfo original, last uint32 idleSince uint64 changed, game, overlay bool gameBase uint32 } type changeKind uint8 const ( normalChange changeKind = iota gameChange restoringChange ) type controller struct { api processBackend mem, gameMem uint64 dryRun, gameMode bool states map[processKey]*processState now func() time.Time blocked map[processKey]bool out scanReport } func newController(api processBackend, mem, gameMem uint64, dryRun bool, _ time.Duration) *controller { return &controller{api: api, mem: mem, gameMem: gameMem, dryRun: dryRun, states: make(map[processKey]*processState), blocked: make(map[processKey]bool), now: time.Now} } func (c *controller) ordered() []*processState { list := make([]*processState, 0, len(c.states)) for _, s := range c.states { list = append(list, s) } sort.Slice(list, func(i, j int) bool { a, b := list[i], list[j] if a.game != b.game { return a.game } if a.info.PID != b.info.PID { return a.info.PID < b.info.PID } return a.info.Key.Created < b.info.Key.Created }) return list } func (c *controller) alive(s *processState, snapshot *processSnapshot) (bool, error) { if snapshot != nil { if !snapshot.PIDs[s.info.PID] { return false, nil } for _, p := range snapshot.Processes { if p.PID == s.info.PID && p.Key != s.info.Key { return false, nil } } } if s.info.Waitable { return c.api.Alive(&s.info) } // Without SYNCHRONIZE, only absence from a complete snapshot proves exit. // Retained handles still bind all restoration writes to the original instance. return true, nil } func (s *processState) observe(current uint32) { if (s.changed || s.overlay) && current != s.last { // Do not undo another actor's change. The next own write starts a new baseline. s.changed = false s.overlay = false s.idleSince = 0 } s.info.Prio = current if !s.changed && !s.overlay { s.original = current } } func (c *controller) reconcile(snapshot processSnapshot) map[processKey]bool { fresh := make(map[processKey]bool, len(snapshot.Processes)) instances := make(map[uint32]processKey, len(snapshot.Processes)) for _, p := range snapshot.Processes { instances[p.PID] = p.Key } for key := range c.blocked { current, known := instances[key.PID] if !snapshot.PIDs[key.PID] || (known && current != key) { delete(c.blocked, key) } } for key, s := range c.states { alive, err := c.alive(s, &snapshot) if err != nil { c.failed(s, s.info.Prio, fmt.Errorf("статус процесса: %w", err)) } if err == nil && !alive { c.api.Close(&s.info) delete(c.states, key) } } for i := range snapshot.Processes { p := &snapshot.Processes[i] fresh[p.Key] = true if s := c.states[p.Key]; s != nil { s.info.Name = p.Name s.info.RSS = p.RSS s.info.ParentPID = p.ParentPID s.observe(p.Prio) c.api.Close(p) } else { c.states[p.Key] = &processState{info: *p, original: p.Prio} } } c.out.Errors = append(c.out.Errors, snapshot.Errors...) return fresh } func (c *controller) failed(s *processState, target uint32, err error) { if c.blocked[s.info.Key] { return } c.blocked[s.info.Key] = true c.out.Errors = append(c.out.Errors, processError{Name: s.info.Name, PID: s.info.PID, Description: fmt.Sprintf("%s: %v; внесён в блоклист", prioName(target), err)}) } func (c *controller) change(s *processState, target uint32, kind changeKind) bool { if c.blocked[s.info.Key] { return false } current, err := c.api.Priority(&s.info) if err != nil { c.failed(s, target, err) return false } s.observe(current) if kind == normalChange && desiredPriority(s.info.RSS, c.mem, current, false, false) == 0 { return true } if kind == restoringChange && !s.changed && !s.overlay { return true } if current == target { return true } row := priorityChange{Name: s.info.Name, PID: s.info.PID, RSS: s.info.RSS, Before: current, After: target, Heavy: s.game} if c.dryRun { c.out.Rows = append(c.out.Rows, row) return true } stamp := filetimeValue(c.now()) if err = c.api.SetPriority(&s.info, target); err != nil { c.failed(s, target, err) return false } // Save ownership immediately after a successful write, even if verification fails. if !s.changed { s.original = current } if kind == gameChange && !s.overlay { s.overlay = true s.gameBase = current } s.changed = true s.last = target s.info.Prio = target if target == PriorityClassIdle { s.idleSince = stamp } else { s.idleSince = 0 } actual, err := c.api.Priority(&s.info) if err != nil { c.failed(s, target, fmt.Errorf("изменение выполнено, проверка не удалась: %w", err)) return false } s.last = actual s.info.Prio = actual s.changed = actual != s.original if actual != PriorityClassIdle { s.idleSince = 0 } if actual != current { row.After = actual c.out.Rows = append(c.out.Rows, row) } if actual != target { c.failed(s, target, fmt.Errorf("Windows установила %s вместо %s", prioName(actual), prioName(target))) return false } return true } func (c *controller) restoreOverlay(s *processState) bool { if c.blocked[s.info.Key] { return false } current, err := c.api.Priority(&s.info) if err != nil { c.failed(s, s.gameBase, err) return false } s.observe(current) if !s.overlay { return true } if !c.change(s, s.gameBase, restoringChange) { return false } s.overlay = false return true } func filetimeValue(t time.Time) uint64 { return uint64(t.Unix())*10000000 + uint64(t.Nanosecond()/100) + 116444736000000000 } func inheritedClass(parent uint32) uint32 { if parent == PriorityClassIdle || parent == PriorityClassBelowNormal { return parent } return PriorityClassNormal } func (c *controller) adoptInherited(fresh map[processKey]bool) { if c.dryRun { return } byPID := make(map[uint32]*processState, len(c.states)) for _, s := range c.states { byPID[s.info.PID] = s } // Each successful pass adopts at least one previously unowned child. Cycles, // reused parent PIDs and pre-existing IDLE processes cannot establish ownership. for pass := 0; pass < len(c.states); pass++ { adopted := false for _, s := range c.states { if !fresh[s.info.Key] || s.changed || s.overlay || s.game || s.info.Prio != PriorityClassIdle { continue } parent := byPID[s.info.ParentPID] if parent == nil || parent == s || !fresh[parent.info.Key] || (!parent.changed && !parent.overlay) || parent.last != PriorityClassIdle || parent.info.Prio != PriorityClassIdle || parent.idleSince == 0 { continue } if s.info.Key.Created < parent.idleSince || s.info.Key.Created <= parent.info.Key.Created { continue } s.original = inheritedClass(parent.original) s.last = PriorityClassIdle s.changed = s.original != s.last s.overlay = parent.overlay s.gameBase = inheritedClass(parent.gameBase) s.idleSince = s.info.Key.Created // Explicit child IDLE cannot be distinguished from inherited IDLE by polling. adopted = adopted || s.changed || s.overlay } if !adopted { break } } } func (c *controller) releaseUnused() { for key, s := range c.states { if !s.game && !s.changed && !s.overlay { c.api.Close(&s.info) delete(c.states, key) } } } func (c *controller) Step() scanReport { c.out = scanReport{DryRun: c.dryRun} snapshot, err := c.api.Snapshot() if err != nil { c.out.Errors = append(c.out.Errors, processError{Description: fmt.Sprintf("Снимок процессов: %v", err)}) return c.out } fresh := c.reconcile(snapshot) c.adoptInherited(fresh) newGames := make(map[processKey]bool) for key, s := range c.states { if fresh[key] && c.gameMem > 0 && s.info.RSS >= c.gameMem && !s.game { s.game = true newGames[key] = true } } hasGame := false for _, s := range c.states { hasGame = hasGame || s.game } if hasGame && !c.gameMode { c.out.Title = "ИГРОВОЙ РЕЖИМ ВКЛЮЧЁН" } if !hasGame && c.gameMode { c.out.Title = "ВОССТАНОВЛЕНИЕ ПРИОРИТЕТОВ" } c.gameMode = hasGame for _, s := range c.ordered() { if !hasGame && s.overlay { if !c.restoreOverlay(s) { continue } if c.out.Title == "" { c.out.Title = "ВОССТАНОВЛЕНИЕ ПРИОРИТЕТОВ" } } if !fresh[s.info.Key] { continue } target := desiredPriority(s.info.RSS, c.mem, s.info.Prio, hasGame, s.game) if target == 0 { continue } if target == s.info.Prio { if newGames[s.info.Key] { c.out.Rows = append(c.out.Rows, priorityChange{Name: s.info.Name, PID: s.info.PID, RSS: s.info.RSS, Before: target, After: target, Heavy: true, Unchanged: true}) } continue } kind := normalChange if hasGame { kind = gameChange } if !c.change(s, target, kind) && newGames[s.info.Key] { c.out.Rows = append(c.out.Rows, priorityChange{Name: s.info.Name, PID: s.info.PID, RSS: s.info.RSS, Before: s.info.Prio, After: s.info.Prio, Heavy: true, Blocked: true}) } } c.releaseUnused() if c.out.Title == "" && len(c.out.Rows) > 0 { if hasGame { c.out.Title = "ИГРОВОЙ РЕЖИМ" } else { c.out.Title = "ИЗМЕНЕНИЕ ПРИОРИТЕТОВ" } } return c.out } func (c *controller) Shutdown() scanReport { c.out = scanReport{Title: "ЗАВЕРШЕНИЕ: ВОССТАНОВЛЕНИЕ ПРИОРИТЕТОВ", DryRun: c.dryRun} if !c.dryRun && len(c.states) > 0 { snapshot, err := c.api.Snapshot() if err == nil { fresh := c.reconcile(snapshot) c.adoptInherited(fresh) } else { c.out.Errors = append(c.out.Errors, processError{Description: fmt.Sprintf("Последний снимок процессов: %v", err)}) } for _, s := range c.ordered() { if !s.changed || c.blocked[s.info.Key] { continue } alive, err := c.alive(s, nil) if err == nil && !alive { s.changed = false continue } if err != nil { c.failed(s, s.original, err) continue } current, err := c.api.Priority(&s.info) if err != nil { c.failed(s, s.original, err) continue } s.observe(current) if !s.changed { continue } if c.change(s, s.original, restoringChange) { s.changed = false s.overlay = false } } } for key, s := range c.states { if s.changed { c.out.Unrestored++ } c.api.Close(&s.info) delete(c.states, key) } if c.out.Unrestored > 0 { c.out.Errors = append(c.out.Errors, processError{Description: fmt.Sprintf("Не восстановлены приоритеты %d процессов; подробности в журнале", c.out.Unrestored)}) } c.gameMode = false clear(c.blocked) return c.out }