forked from Keelrleek/Harbor
Background compiles give way to the lander: the Logbook statistics and the results refresher wait while [logbook] lander_lock is fresh (under 60 min); the statistics tools run below normal priority and a timeout ends the whole tree (an orphaned cl.exe went on compiling main at normal priority); a retried statistics run no longer relogs the same commits
This commit is contained in:
1 parent
21b7fedb6f
commit
56308e3aab
5 files changed
+154
-14
No files matched your search
@@ -0,0 +1,69 @@
|
||||
"""The server's background compiles (the Logbook's statistics, the whole-program check, the emulator run) give way to
|
||||
the lander: its gates (a full readable check of every file) are what land work on main, and a second compile of main
|
||||
beside them roughly quadruples their time.
|
||||
|
||||
Two parts:
|
||||
lander_busy(project) while [logbook] lander_lock names a file that exists and was written in the last
|
||||
STALE_MINUTES, the lander is running its gates: background jobs do not start. The lander
|
||||
creates the file before its gates and removes it after the push; an older file is a lander
|
||||
that died, and is ignored.
|
||||
run_low(...) subprocess.run at BELOW_NORMAL priority (children such as cl.exe inherit it) that, on a
|
||||
timeout, ends the whole process tree instead of leaving the compiler running orphaned.
|
||||
|
||||
Configured per project in the [logbook] table:
|
||||
lander_lock = 'R:/Workspace/Data/lander-busy.lock' unset or "": never wait"""
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
STALE_MINUTES = 60
|
||||
BELOW_NORMAL = getattr(subprocess, "BELOW_NORMAL_PRIORITY_CLASS", 0)
|
||||
IDLE = getattr(subprocess, "IDLE_PRIORITY_CLASS", 0)
|
||||
NO_WINDOW = getattr(subprocess, "CREATE_NO_WINDOW", 0)
|
||||
|
||||
|
||||
def lock_path(project):
|
||||
path = ((getattr(project, "logbook", None) or {}).get("lander_lock") or "").strip()
|
||||
return Path(path) if path else None
|
||||
|
||||
|
||||
def lander_busy(project, now=None):
|
||||
"""Why background work should wait (the lander's lock is fresh), or None."""
|
||||
path = lock_path(project)
|
||||
if path is None:
|
||||
return None
|
||||
try:
|
||||
written = path.stat().st_mtime
|
||||
except OSError:
|
||||
return None
|
||||
age = (time.time() if now is None else now) - written
|
||||
if age >= STALE_MINUTES * 60:
|
||||
return None
|
||||
return f"the lander is running its gates ({path.name}, {max(0, int(age // 60))} min old)"
|
||||
|
||||
|
||||
def kill_tree(pid):
|
||||
if os.name == "nt":
|
||||
subprocess.run(["taskkill", "/PID", str(pid), "/T", "/F"], capture_output=True, creationflags=NO_WINDOW)
|
||||
else:
|
||||
try:
|
||||
os.kill(pid, 9)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def run_low(argv, timeout, priority=BELOW_NORMAL, **kwargs):
|
||||
"""subprocess.run(argv, capture_output=True, text=True, ...) at a lower priority class; on a timeout the whole
|
||||
tree (cmd.exe, cl.exe) is ended before TimeoutExpired is raised."""
|
||||
flags = kwargs.pop("creationflags", 0) | (priority | NO_WINDOW if os.name == "nt" else 0)
|
||||
with subprocess.Popen(argv, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
text=True, creationflags=flags, **kwargs) as proc:
|
||||
try:
|
||||
stdout, stderr = proc.communicate(timeout=timeout)
|
||||
except subprocess.TimeoutExpired:
|
||||
kill_tree(proc.pid)
|
||||
proc.kill()
|
||||
proc.communicate()
|
||||
raise subprocess.TimeoutExpired(argv, timeout) from None
|
||||
return subprocess.CompletedProcess(argv, proc.returncode, stdout, stderr)
|
||||
Reference in new issue
Block a user