esem-bridge manages a single Python subprocess for the lifetime of your Node.js process. The worker is spawned lazily on the firstDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/Crane04/esem/llms.txt
Use this file to discover all available pages before exploring further.
python() call and is then shared by every subsequent call in that process — there is no per-call startup cost after the first invocation. When no Python calls are active, the worker is automatically unreferenced so it does not prevent Node.js from exiting on its own.
Lifecycle stages
First python() call triggers worker startup
The first time Subsequent
python() is called, ensureWorker() reads ESEM_PYTHON
(defaulting to python3) and spawns worker.py as a child process with
stdio piped:python() calls skip this step and reuse the already-running
worker.Worker signals readiness
Once the Python process has initialised, The bridge receives this line, resolves the internal
worker.py writes a single
JSON message to stdout:readyPromise, and
allows the first RPC request to be sent. Until this message arrives, all
python() calls await the promise.RPC calls are dispatched over stdin/stdout
Every
python() call that follows sends a newline-delimited JSON-RPC
message to the worker’s stdin and waits for a matching response on stdout.
All calls share the same worker — there is no process-per-call overhead.Worker is unreferenced when idle
After each RPC response is received, the bridge checks whether there are
any remaining pending requests. If the queue is empty, the worker process
and all its stdio streams are unreferenced with An unreferenced process does not keep the Node.js event loop alive, so
short scripts exit naturally once all awaited calls complete — no explicit
shutdown call is needed.
unref():Shutdown closes the worker cleanly
When Node.js exits, or when
shutdown() is called explicitly, the bridge
closes the worker’s stdin. worker.py reads from stdin in a loop; a
closed stdin causes the loop to end and the Python process exits cleanly.Signal handlers registered in bridge.js ensure this happens automatically
for exit, SIGINT, and SIGTERM:Auto-shutdown for short scripts
For short-lived scripts you do not need to do anything. Once allpython() calls resolve and there are no more pending requests, the worker is unreferenced and Node.js exits on its own.
Explicit shutdown for long-running applications
In a long-running server or daemon, the worker stays alive and referenced whenever a Python call is in flight. If you know Python will not be needed again, you can release the worker early:What shutdown() does
shutdown() performs the following steps in order:
- Closes the worker’s stdin —
worker.pydetects end-of-file onsys.stdinand exits. - Clears the pending request map — any registered
{ resolve, reject }callbacks are discarded. - Resets the ready state —
isReadyis set tofalseandreadyPromiseis replaced with a fresh promise, so a new worker can be spawned transparently ifpython()is called again later.
Worker crash recovery
If the Python worker exits unexpectedly with a non-zero exit code, the bridge handles the failure gracefully:- All pending Promises are rejected with
Error("Python worker exited unexpectedly"). workerProcessis set tonullandisReadyis reset tofalse.- The next
python()call will triggerensureWorker()again, spawning a fresh worker automatically.
bridge.js:
A previous worker finishing after a new one has already started is handled
safely. The exit handler compares
workerProcess against the reference it
captured at spawn time, so a stale exit event cannot corrupt the state of a
newly started worker.Signal handling
bridge.js registers three process-level signal handlers to guarantee the Python subprocess is always cleaned up:
| Signal | Behaviour |
|---|---|
exit | Calls shutdown() synchronously before the Node process terminates |
SIGINT | Calls shutdown(), then process.exit(0) (handles Ctrl-C in terminal) |
SIGTERM | Calls shutdown(), then process.exit(0) (handles container/system stop) |
shutdown() function.
Long-running server pattern
The following example shows how to integrate esem-bridge into an HTTP server that starts a Python model once and serves predictions for as long as the server is running, then shuts everything down cleanly onSIGTERM.
The
python() call at the top level of the module runs once when the server
starts. Because predict is a proxied function, each request invokes it
directly without reloading the module — the Python model stays in memory for
the lifetime of the server.