fix: ErrorAlreadyExists 1832->183, wrap syscall errors, signal handling, mutex cleanup, log sync
This commit is contained in:
@@ -1,2 +1,11 @@
|
|||||||
*.exe
|
*.exe
|
||||||
*.log
|
*.log
|
||||||
|
*.exe~
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.env
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
# autoPriority
|
# autoPriority
|
||||||
|
|
||||||
Automatically sets high CPU priority for memory-heavy processes.
|
Automatically sets high CPU priority for processes exceeding a memory threshold.
|
||||||
|
|
||||||
|
**Windows only.**
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
|
Requires Go 1.26+.
|
||||||
|
|
||||||
```
|
```
|
||||||
# Standard build (with console window)
|
# Standard build (with console window)
|
||||||
go build -o autopriority.exe .
|
go build -o autopriority.exe .
|
||||||
@@ -20,18 +24,25 @@ autopriority [flags]
|
|||||||
|
|
||||||
| Flag | Default | Description |
|
| Flag | Default | Description |
|
||||||
|-------------|---------------|----------------------------------|
|
|-------------|---------------|----------------------------------|
|
||||||
| `-mem` | 536870912 | Memory threshold in bytes |
|
| `-mem` | 536870912 | Memory threshold in bytes (512 MB) |
|
||||||
| `-interval` | 1m0s | Scan interval (min 10s) |
|
| `-interval` | 1 minute | Scan interval (min 10s) |
|
||||||
| `-dry-run` | false | Log only, don't change priority |
|
| `-dry-run` | false | Log only, don't change priority |
|
||||||
| `-log` | true | Write `autopriority.log` to `%TEMP%` |
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Run with 1 GB memory threshold, scanning every 30 seconds
|
||||||
|
autopriority -mem=1073741824 -interval=30s
|
||||||
|
|
||||||
|
# Dry run — log decisions without changing anything
|
||||||
|
autopriority -dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
Log is always written to `%TEMP%\autopriority.log`.
|
||||||
|
|
||||||
## Auto-start
|
## Auto-start
|
||||||
|
|
||||||
Place a shortcut to `autopriority.exe` in:
|
Open **Win+R**, type `shell:startup`, and press Enter. Then place a shortcut to `autopriority.exe` in the folder that opens.
|
||||||
|
|
||||||
```
|
|
||||||
shell:startup
|
|
||||||
```
|
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@@ -20,7 +21,7 @@ const (
|
|||||||
PriorityClassNormal = 0x00000020
|
PriorityClassNormal = 0x00000020
|
||||||
PriorityClassHigh = 0x00000080
|
PriorityClassHigh = 0x00000080
|
||||||
MessageBoxIconWarning = 0x00000030
|
MessageBoxIconWarning = 0x00000030
|
||||||
ErrorAlreadyExists = 1832
|
ErrorAlreadyExists = 183
|
||||||
)
|
)
|
||||||
|
|
||||||
type processEntry32 struct {
|
type processEntry32 struct {
|
||||||
@@ -78,7 +79,7 @@ func closeH(h syscall.Handle) {
|
|||||||
func openProc(pid uint32, acc uint32) (syscall.Handle, error) {
|
func openProc(pid uint32, acc uint32) (syscall.Handle, error) {
|
||||||
r, _, e := procOpenProcess.Call(uintptr(acc), 0, uintptr(pid))
|
r, _, e := procOpenProcess.Call(uintptr(acc), 0, uintptr(pid))
|
||||||
if r == 0 {
|
if r == 0 {
|
||||||
return 0, e
|
return 0, fmt.Errorf("OpenProcess(%d) failed: %w", pid, e)
|
||||||
}
|
}
|
||||||
return syscall.Handle(r), nil
|
return syscall.Handle(r), nil
|
||||||
}
|
}
|
||||||
@@ -91,7 +92,7 @@ func setPrio(pid uint32, cls uint32) error {
|
|||||||
defer closeH(h)
|
defer closeH(h)
|
||||||
r, _, e := procSetPriority.Call(uintptr(h), uintptr(cls))
|
r, _, e := procSetPriority.Call(uintptr(h), uintptr(cls))
|
||||||
if r == 0 {
|
if r == 0 {
|
||||||
return e
|
return fmt.Errorf("SetPriorityClass(%d) failed: %w", pid, e)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -104,7 +105,7 @@ func curPrio(pid uint32) (uint32, error) {
|
|||||||
defer closeH(h)
|
defer closeH(h)
|
||||||
r, _, e := procGetPriority.Call(uintptr(h))
|
r, _, e := procGetPriority.Call(uintptr(h))
|
||||||
if r == 0 {
|
if r == 0 {
|
||||||
return 0, e
|
return 0, fmt.Errorf("GetPriorityClass(%d) failed: %w", pid, e)
|
||||||
}
|
}
|
||||||
return uint32(r), nil
|
return uint32(r), nil
|
||||||
}
|
}
|
||||||
@@ -117,9 +118,10 @@ func rss(pid uint32) (uint64, error) {
|
|||||||
defer closeH(h)
|
defer closeH(h)
|
||||||
|
|
||||||
var m processMemoryCounters
|
var m processMemoryCounters
|
||||||
|
m.CBM = uint32(unsafe.Sizeof(m))
|
||||||
r, _, e := procGetMemInfo.Call(uintptr(h), uintptr(unsafe.Pointer(&m)), uintptr(unsafe.Sizeof(m)))
|
r, _, e := procGetMemInfo.Call(uintptr(h), uintptr(unsafe.Pointer(&m)), uintptr(unsafe.Sizeof(m)))
|
||||||
if r == 0 {
|
if r == 0 {
|
||||||
return 0, e
|
return 0, fmt.Errorf("GetProcessMemoryInfo(%d) failed: %w", pid, e)
|
||||||
}
|
}
|
||||||
return uint64(m.WorkingSetSize), nil
|
return uint64(m.WorkingSetSize), nil
|
||||||
}
|
}
|
||||||
@@ -136,14 +138,14 @@ func utf16Trim(p []uint16) string {
|
|||||||
func allProcs() ([]processInfo, error) {
|
func allProcs() ([]processInfo, error) {
|
||||||
snap, _, e := procCreateSnap.Call(CreateToolhelp32SnapshotProcess, 0)
|
snap, _, e := procCreateSnap.Call(CreateToolhelp32SnapshotProcess, 0)
|
||||||
if snap == 0 {
|
if snap == 0 {
|
||||||
return nil, e
|
return nil, fmt.Errorf("CreateToolhelp32Snapshot failed: %w", e)
|
||||||
}
|
}
|
||||||
defer closeH(syscall.Handle(snap))
|
defer closeH(syscall.Handle(snap))
|
||||||
|
|
||||||
pe := processEntry32{Size: uint32(unsafe.Sizeof(processEntry32{}))}
|
pe := processEntry32{Size: uint32(unsafe.Sizeof(processEntry32{}))}
|
||||||
r, _, e := procProcess32First.Call(snap, uintptr(unsafe.Pointer(&pe)))
|
r, _, e := procProcess32First.Call(snap, uintptr(unsafe.Pointer(&pe)))
|
||||||
if r == 0 {
|
if r == 0 {
|
||||||
return nil, e
|
return nil, fmt.Errorf("Process32First failed: %w", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
var out []processInfo
|
var out []processInfo
|
||||||
@@ -152,10 +154,15 @@ func allProcs() ([]processInfo, error) {
|
|||||||
if name != "" {
|
if name != "" {
|
||||||
out = append(out, processInfo{PID: pe.PID, Name: name})
|
out = append(out, processInfo{PID: pe.PID, Name: name})
|
||||||
}
|
}
|
||||||
|
pe.Size = uint32(unsafe.Sizeof(processEntry32{}))
|
||||||
r, _, e = procProcess32Next.Call(snap, uintptr(unsafe.Pointer(&pe)))
|
r, _, e = procProcess32Next.Call(snap, uintptr(unsafe.Pointer(&pe)))
|
||||||
if r == 0 {
|
if r == 0 {
|
||||||
|
le, _, _ := procGetLastError.Call()
|
||||||
|
if le == 18 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
return nil, fmt.Errorf("Process32Next failed: %w", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
@@ -174,19 +181,22 @@ func prioName(c uint32) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isAlive(pid uint32) bool {
|
func isAlive(pid uint32) bool {
|
||||||
h, _ := openProc(pid, ProcessQueryInformation)
|
h, err := openProc(pid, ProcessQueryInformation)
|
||||||
if h == 0 {
|
if err != nil || h == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
closeH(h)
|
closeH(h)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureSingleInstance() {
|
func ensureSingleInstance() syscall.Handle {
|
||||||
h, _, _ := procCreateMutex.Call(0, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("Global\\autopriority_v1"))))
|
h, _, err := procCreateMutex.Call(0, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("Global\\autopriority_v1"))))
|
||||||
|
if h == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "autoPriority: failed to create mutex: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
le, _, _ := procGetLastError.Call()
|
le, _, _ := procGetLastError.Call()
|
||||||
if uint32(le) == ErrorAlreadyExists {
|
if uint32(le) == ErrorAlreadyExists {
|
||||||
closeH(syscall.Handle(h))
|
|
||||||
procMessageBox.Call(0,
|
procMessageBox.Call(0,
|
||||||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("autoPriority is already running."))),
|
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("autoPriority is already running."))),
|
||||||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("autoPriority"))),
|
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("autoPriority"))),
|
||||||
@@ -194,6 +204,7 @@ func ensureSingleInstance() {
|
|||||||
)
|
)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
return syscall.Handle(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -213,6 +224,7 @@ func main() {
|
|||||||
os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
logFile = f
|
logFile = f
|
||||||
|
defer logFile.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,11 +238,13 @@ func main() {
|
|||||||
}
|
}
|
||||||
fmt.Fprintf(logFile, "[%s] ", ts())
|
fmt.Fprintf(logFile, "[%s] ", ts())
|
||||||
fmt.Fprintf(logFile, format+"\n", a...)
|
fmt.Fprintf(logFile, format+"\n", a...)
|
||||||
|
logFile.Sync()
|
||||||
}
|
}
|
||||||
|
|
||||||
logf("autoPriority started (mem=%d bytes, interval=%s, dry-run=%v)", *mem, *interval, *dryRun)
|
logf("autoPriority started (mem=%d bytes, interval=%s, dry-run=%v)", *mem, *interval, *dryRun)
|
||||||
|
|
||||||
ensureSingleInstance()
|
mutex := ensureSingleInstance()
|
||||||
|
defer closeH(mutex)
|
||||||
|
|
||||||
if err := setPrio(uint32(os.Getpid()), PriorityClassIdle); err != nil {
|
if err := setPrio(uint32(os.Getpid()), PriorityClassIdle); err != nil {
|
||||||
logf("warning: could not set own priority to IDLE: %v", err)
|
logf("warning: could not set own priority to IDLE: %v", err)
|
||||||
@@ -240,9 +254,6 @@ func main() {
|
|||||||
|
|
||||||
promoted := make(map[uint32]string)
|
promoted := make(map[uint32]string)
|
||||||
|
|
||||||
if logFile != nil {
|
|
||||||
defer logFile.Close()
|
|
||||||
}
|
|
||||||
defer func() {
|
defer func() {
|
||||||
for pid, name := range promoted {
|
for pid, name := range promoted {
|
||||||
if err := setPrio(pid, PriorityClassNormal); err == nil {
|
if err := setPrio(pid, PriorityClassNormal); err == nil {
|
||||||
@@ -256,6 +267,9 @@ func main() {
|
|||||||
ticker := time.NewTicker(*interval)
|
ticker := time.NewTicker(*interval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
stop := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
scan := func() {
|
scan := func() {
|
||||||
procs, err := allProcs()
|
procs, err := allProcs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -311,7 +325,18 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scan()
|
scan()
|
||||||
for range ticker.C {
|
select {
|
||||||
|
case <-stop:
|
||||||
|
logf("received shutdown signal")
|
||||||
|
case <-ticker.C:
|
||||||
|
for {
|
||||||
scan()
|
scan()
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
logf("received shutdown signal")
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user