blackbull.server.reload¶
blackbull.server.reload
¶
Auto-reload support for the BlackBull multi-worker master.
The reload model is master re-exec:
- Master binds and listens on the configured sockets.
- Master forks worker processes; they inherit the listening sockets via fd inheritance.
- A background
watchfileswatcher signals the master when any watched*.pyfile changes. - Master sends SIGTERM to all workers and waits up to
shutdown_timeoutfor them to drain in-flight requests. - Master marks the listening sockets inheritable, exports their fds
via
BB_INHERIT_FDS, andos.execvp\ ssys.executablewith the original argv. - The fresh master process adopts the inherited sockets
(see :func:
blackbull.protocol.rsock.adopt_inherited_sockets) and re-forks workers — now running the new code.
Picking up new code requires the master itself to re-import, which is
why we re-exec the whole process rather than importlib.reload. The
listening sockets do not close at any point: kernel multiplexes the
same fd across master+workers; only python state churns.
The watcher runs in a daemon thread so it can not block the master's synchronous supervision loop. It debounces filesystem events itself (watchfiles default ~50 ms) so a single editor save does not trigger multiple reloads.
FileChangeWatcher
¶
Daemon-thread wrapper around watchfiles.watch.
Parameters¶
paths:
Iterable of filesystem paths to watch. Each path can be a file
or a directory; directories are watched recursively.
on_change:
Zero-argument callable invoked exactly once per debounced batch
of file events. Runs on the watcher thread, so must be cheap
and threadsafe — typical use is to set a threading.Event
the master's main loop polls.
watch_filter:
Optional watchfiles watch_filter callable. Defaults to
*.py-only.
start()
¶
Start the watcher thread. No-op if already running.
exec_self_with_sockets(sockets, argv=None)
¶
Re-execute the current Python process, preserving listening sockets.
Marks each socket's fd inheritable, sets BB_INHERIT_FDS to a
comma-separated list of those fds, and calls os.execvp —
which replaces the current process image while keeping fds open
(unless FD_CLOEXEC is set, which we clear here).
Does not return on success: the current process image is gone.
Parameters¶
sockets:
Listening sockets the new process should adopt. Caller is
responsible for having already terminated any subprocesses
that hold copies of these fds.
argv:
Argv to exec. Defaults to sys.argv (re-runs the same
command line). sys.executable is always used as argv[0].