discopt.gams.daemon#

Warm solver daemon for the discopt GAMS link.

GAMS launches a solver as a fresh process per solve, so each solve otherwise re-pays Python + JAX import and first-JIT warmup (seconds), even though a warm solve is ~10 ms. This module keeps a single long-lived process warm and turns the per-solve launch into a thin socket round-trip.

Pieces#

  • DaemonServer – listens on a per-user unix socket, services one request at a time (solves are serialized: one warm interpreter, GIL + JAX thread-safety), and self-terminates on idle timeout / max lifetime / max solves / version mismatch.

  • solve_via_daemon() – the client used by the GAMS link: connect (lazily spawning a detached daemon if needed), relay the control-file path, return the exit code. Returns None to signal the caller should fall back to an in-process solve, so correctness never depends on the daemon being up.

The lifecycle (idle timeout + lazy auto-respawn + direct-solve fallback) is exercised in tests with a fake solve function, so it needs no GAMS install.

Classes#

DaemonServer

A warm, single-request-at-a-time solver server over a unix socket.

Functions#

default_socket_path(→ pathlib.Path)

Per-user socket path (honours DISCOPT_GAMS_SOCKET / XDG_RUNTIME_DIR).

ping(→ dict | None)

Return the running daemon's handshake, or None if not reachable.

stop_daemon(→ bool)

Ask a running daemon to shut down. Returns True if it acknowledged.

spawn_daemon(→ bool)

Start a detached daemon and wait until its socket answers.

solve_via_daemon(→ int | None)

Solve control_file through the warm daemon, spawning it if needed.

main(→ int)

python -m discopt.gams.daemon {serve,stop,status}.

Module Contents#

discopt.gams.daemon.default_socket_path() pathlib.Path#

Per-user socket path (honours DISCOPT_GAMS_SOCKET / XDG_RUNTIME_DIR).

class discopt.gams.daemon.DaemonServer(socket_path: pathlib.Path | None = None, solve_fn: collections.abc.Callable[[str, str | None], int] | None = None, idle_timeout: float | None = None, max_lifetime: float | None = None, max_solves: int | None = None, max_rss_mb: int | None = None, jax_clear_every: int | None = None, version: str = __version__)#

A warm, single-request-at-a-time solver server over a unix socket.

socket_path#
idle_timeout#
max_lifetime#
max_solves#
max_rss_mb#
jax_clear_every#
version = '0.4.1.dev0'#
solves = 0#
serve_forever() None#

Accept and service requests until a shutdown condition fires.

discopt.gams.daemon.ping(socket_path: pathlib.Path | None = None) dict | None#

Return the running daemon’s handshake, or None if not reachable.

discopt.gams.daemon.stop_daemon(socket_path: pathlib.Path | None = None) bool#

Ask a running daemon to shut down. Returns True if it acknowledged.

discopt.gams.daemon.spawn_daemon(socket_path: pathlib.Path | None = None, wait: float = _SPAWN_WAIT) bool#

Start a detached daemon and wait until its socket answers.

discopt.gams.daemon.solve_via_daemon(control_file: str, sysdir: str | None = None, socket_path: pathlib.Path | None = None) int | None#

Solve control_file through the warm daemon, spawning it if needed.

Returns the solver exit code, or None if the daemon is unreachable and could not be started – the caller should then solve in-process.

discopt.gams.daemon.main(argv: list[str] | None = None) int#

python -m discopt.gams.daemon {serve,stop,status}.