forked from Keelrleek/Harbor
hooks: any agent's hook restarts the project's web server when it is down ([server] port and start; at most once a minute, detached), so the board and chat no longer depend on the coordinator's session
This commit is contained in:
1 parent
940cacc2d2
commit
9b6ea3bc57
2 files changed
+40
-1
No files matched your search
@@ -200,6 +200,42 @@ def _context(host, event, text):
|
||||
print(text)
|
||||
|
||||
|
||||
def ensure_server(project, every=60):
|
||||
"""Restart the project's web server when it is down: [server] port and start (a command). Any agent's hook
|
||||
does it, at most once a minute, so the board and chat come back without the coordinator's session. Detached,
|
||||
so the server outlives the hook (and the app) that started it."""
|
||||
port, start = project.server.get("port"), project.server.get("start")
|
||||
if not port or not start:
|
||||
return
|
||||
stamp = project.db.parent / "server-check.stamp"
|
||||
try:
|
||||
import time
|
||||
if stamp.exists() and time.time() - stamp.stat().st_mtime < every:
|
||||
return
|
||||
stamp.touch()
|
||||
import socket
|
||||
with socket.socket() as connection:
|
||||
connection.settimeout(0.3)
|
||||
if connection.connect_ex(("127.0.0.1", int(port))) == 0:
|
||||
return
|
||||
import subprocess
|
||||
flags = 0
|
||||
if os.name == "nt":
|
||||
flags = (subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
|
||||
| subprocess.CREATE_BREAKAWAY_FROM_JOB | subprocess.CREATE_NO_WINDOW)
|
||||
command = start if isinstance(start, list) else [start]
|
||||
try:
|
||||
subprocess.Popen(command, creationflags=flags, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL, close_fds=True)
|
||||
except OSError: # the job may refuse breakaway: start it without
|
||||
subprocess.Popen(command, creationflags=flags & ~getattr(subprocess, "CREATE_BREAKAWAY_FROM_JOB", 0),
|
||||
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
close_fds=True)
|
||||
print(f"harbor: the web server on port {port} was down; restarting it", file=sys.stderr)
|
||||
except Exception as error: # never break the host because of this
|
||||
print(f"harbor: server check failed: {error}", file=sys.stderr)
|
||||
|
||||
|
||||
def run(event, project_spec, agent, host="generic"):
|
||||
payload = _payload()
|
||||
try:
|
||||
@@ -216,6 +252,7 @@ def run(event, project_spec, agent, host="generic"):
|
||||
agent = project.session_identity(agent or os.environ.get("HARBOR_AGENT"), cwd) # the session's own folder
|
||||
if not agent or agent not in project.ai_names:
|
||||
return 0
|
||||
ensure_server(project)
|
||||
harbor = Harbor(project, busy_ms=1500, write_attempts=2)
|
||||
try:
|
||||
return _run(event, harbor, agent, host, payload)
|
||||
|
||||
+3
-1
@@ -96,6 +96,7 @@ class Project:
|
||||
shared_files: tuple = () # glob patterns (repo-relative) every agent edits and the integrator merges: not collisions
|
||||
logbook: dict = field(default_factory=dict) # [logbook]: dir, auto, decomp, stats_repo (harbor.logbook_auto)
|
||||
reverse: dict = field(default_factory=dict) # [reverse]: image, base, facts, imports, annotations, src (harbor.reverse)
|
||||
server: dict = field(default_factory=dict) # [server]: port, start (a command that starts the web server)
|
||||
|
||||
@property
|
||||
def names(self):
|
||||
@@ -223,7 +224,8 @@ def project_from_dict(data, source=None, home=None):
|
||||
autopilot=bool(data.get("autopilot", False)),
|
||||
shared_files=tuple(str(p).replace("\\", "/") for p in data.get("shared_files", [])),
|
||||
logbook=dict(data.get("logbook", {})),
|
||||
reverse=dict(data.get("reverse", {})))
|
||||
reverse=dict(data.get("reverse", {})),
|
||||
server=dict(data.get("server", {})))
|
||||
|
||||
|
||||
def load_file(path, home=None):
|
||||
|
||||
Reference in new issue
Block a user