Expand description
Graceful restart management inspired by tableflip, but more basic.
To implement restarts, the simplest thing to do is to generate a RestartConfig
from
command-line values or hardcoded defaults, then call RestartConfig::try_into_restart_task
. If
you implement a restart command using unix sockets for interactive error reporting, call
RestartConfig::request_restart
and return the Result in your main() function.
The process is automatically placed into the ready state the first time the restart task is
polled. This should be put into a select statement with other futures your app may await on.
The restart task will resolve with Ok(())
if a restart signal was sent and the new process
spawned successfully. If the task is unable to handle future restart signals for any reason,
it will resolve to an Err
.
The process can also be restarted by sending it SIGUSR1. After any kind of restart request, the old process will terminate if the new process starts up successfully, otherwise it will continue if possible.
For coordinating graceful shutdown of the old process, see ShutdownCoordinator
in the
shutdown
module.
§Restart thread
Process restarts are handled by a dedicated thread which is spawned when calling either
RestartConfig::try_into_restart_task
or spawn_restart_task
. If you are dropping privileges,
capabilities or using seccomp policies to limit the syscalls that can execute, it is a good
idea to call the aforementioned functions before locking down the main & future child threads.
You likely don’t want the restart thread to have the same restrictions and limitations that may
otherwise prevent you from calling execve() or doing certain I/O operations.
§Transferring state to the new process
It is possible for the old process to serialise state and send it to the new process to
continue processing. Your code must set an implementation of LifecycleHandler
on the
RestartConfig
. After a new process is spawned, shellflip will call
LifecycleHandler::send_to_new_process
which gives you a unidirectional pipe to write data to
the new process, which receives this data by calling the receive_from_old_process
function.
The data should be received and validated by the new process before it signals readiness by
polling the restart task, in case the data is unusable e.g. if the data format changed slightly
between versions causing serialisation to fail. If the new process fails to signal readiness,
LifecycleHandler::new_process_failed
is called and you can undo any changes you made in
preparation for handover. If the new process succeeds, however, the restart task will resolve
and you may terminate the process as usual.
Re-exports§
pub use shutdown::ShutdownCoordinator;
pub use shutdown::ShutdownHandle;
pub use shutdown::ShutdownSignal;
Modules§
- lifecycle
- restart_
coordination_ socket - Communication with a running process over a unix domain socket.
- shutdown
Structs§
- Restart
Config - Settings for graceful restarts
Enums§
- Child
Spawn Error - Indicates an error that happened during child forking.
Functions§
- fixup_
systemd_ env - When the proxy restarts itself, it sets the child’s LISTEN_PID env to a special value so that the child can replace it with the real child PID. Doing this is easier than reimplementing rust’s process spawn code just so we can call execvpe to replace the environment in the forked process.
- spawn_
restart_ task - Spawns a thread that can be used to restart the process. Returns a future that resolves when a restart succeeds, or if restart becomes impossible. The child spawner thread needs to be created before seccomp locks down fork/exec.
- startup_
complete - Notify systemd and the parent process (if any) that the proxy has started successfully. Returns an error if there was a parent process and we failed to notify it.