External processes spawned via Elixir Ports continue running after the BEAM terminates them, creating orphaned processes that consume resources indefinitely
Use a C shim wrapper that intercepts parent death signals using prctl(PR_SET_PDEATHSIG, SIGHUP) to properly terminate child processes. Example:
prctl(PR_SET_PDEATHSIG, SIGHUP);
void handle_signal(int signum) {
if (signum == SIGHUP && child_pid > 0) {
kill(child_pid, SIGTERM);
}
}
This detects when the parent BEAM process dies and forwards a SIGTERM to the child process, ensuring proper cleanup. The issue occurs because the BEAM's Port API was optimized for pipeline programs that terminate when stdin closes, but daemon-like programs (e.g., rsync) ignore closed pipes. An alternative approach is to use Port.open() with :spawn_executable and wrap ports in a gen_server for state management under supervision.