Files
StarPilot/openpilot/starpilot/galaxy/managed.py
T
firestar5683 80fbb4f6db Feature registration
Register vehicle controls, planner modes, UI actions and Galaxy services after their providers exist. Wire the shared runtime entrypoints and cover their combined behavior.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Daniel Koepping <elkoled@gmail.com>
Co-authored-by: Danny <dannyirwin31@yahoo.com>
Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: Kacper Rączy <gfw.kra@gmail.com>
Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
Co-authored-by: stef <19478336+stefpi@users.noreply.github.com>
Co-authored-by: whoisdomi <dcantu83@gmail.com>

Co-authored-by: Intelli <contact@intelli.software>
Co-authored-by: haraschax <6804392+haraschax@users.noreply.github.com>
Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

134 lines
4.4 KiB
Python

"""Manager-supervised local Galaxy service across ignition changes."""
import os
import signal
import threading
import time
from openpilot.starpilot.galaxy.server import make_server
from openpilot.starpilot.galaxy.server import make_remote_server
from openpilot.starpilot.galaxy.remote import default_remote_pairing, make_gateway_auth_server
from openpilot.starpilot.galaxy.remote_service import supervise_tunnel
def serve_managed(server, *, extra_servers=(), tunnel=None) -> None:
# The manual server retains its normal threading policy. A managed child has
# a five-second stop grace, so a slow request cannot keep the process alive.
server.daemon_threads = True
server.block_on_close = False
stop = threading.Event()
errors: list[BaseException] = []
def request_stop(signum, frame):
stop.set()
def serve():
try:
server.serve_forever(poll_interval=0.1)
except BaseException as exc:
errors.append(exc)
stop.set()
def serve_extra(extra):
try:
extra.serve_forever(poll_interval=0.1)
if not stop.is_set():
errors.append(RuntimeError('Galaxy auxiliary listener stopped'))
stop.set()
except BaseException as exc:
errors.append(exc)
stop.set()
def serve_tunnel():
try:
assert tunnel is not None
tunnel(stop)
if not stop.is_set():
errors.append(RuntimeError('Galaxy tunnel supervisor stopped'))
stop.set()
except BaseException as exc:
errors.append(exc)
stop.set()
old_handlers = {sig: signal.getsignal(sig) for sig in (signal.SIGINT, signal.SIGTERM)}
for sig in old_handlers:
signal.signal(sig, request_stop)
worker = threading.Thread(target=serve, name='galaxy-http', daemon=True)
extras = [threading.Thread(target=serve_extra, args=(extra,), daemon=True)
for extra in extra_servers]
tunnel_worker = threading.Thread(target=serve_tunnel, daemon=True) if tunnel is not None else None
started = False
try:
for extra, thread in zip(extra_servers, extras, strict=True):
extra.daemon_threads = True
thread.start()
worker.start()
started = True
if tunnel_worker is not None:
tunnel_worker.start()
while worker.is_alive() and not stop.wait(0.1):
pass
finally:
stop.set()
drained = False
try:
if tunnel_worker is not None and tunnel_worker.is_alive():
tunnel_worker.join(timeout=6)
for extra, thread in zip(extra_servers, extras, strict=True):
if thread.is_alive():
extra.shutdown()
thread.join(timeout=0.5)
if started:
server.shutdown()
worker.join(timeout=0.5)
# Both web listeners use the same owners. One deadline bounds their
# accepted requests before any shared reader can be closed.
deadline = time.monotonic() + 4.0
drained = not started or server.drain_requests(max(0.0, deadline - time.monotonic()))
for extra in extra_servers:
drain = getattr(extra, 'drain_requests', None)
if callable(drain):
extra_drained = drain(max(0.0, deadline - time.monotonic()))
drained = drained and extra_drained
finally:
try:
for extra in extra_servers:
extra.server_close()
finally:
try:
server.server_close(close_sources=drained)
finally:
for sig, handler in old_handlers.items():
signal.signal(sig, handler)
if errors:
raise RuntimeError('Galaxy HTTP service stopped unexpectedly') from errors[0]
def main() -> None:
if os.getenv('STARPILOT_GALAXY_DISABLE') == '1':
raise RuntimeError('Managed Galaxy disabled by STARPILOT_GALAXY_DISABLE=1')
server = make_server(port=8082, host='0.0.0.0')
try:
server.start_drive_history()
pairing = default_remote_pairing()
remote_server = make_remote_server(server)
try:
from openpilot.common.params import Params
params = Params()
from openpilot.starpilot.connect.provider import galaxy_device_id
auth_server = make_gateway_auth_server(pairing, lambda: galaxy_device_id(params))
except BaseException:
remote_server.server_close()
raise
except BaseException:
server.server_close()
raise
print(f'Galaxy local/LAN service listening on port {server.server_port}', flush=True)
serve_managed(server, extra_servers=(remote_server, auth_server), tunnel=lambda stop: supervise_tunnel(stop, pairing))
if __name__ == '__main__':
main()