Skip to content

Commit

Permalink
WIP: Forward parent death to descendant processes (unix)
Browse files Browse the repository at this point in the history
If the forker's connection to the parent BEAM is broken or closed, react
by killing all spawned children.  When a spawned port is closed, kill
the associated OS process.

A concise demonstration of the problem being solved is to run the
following command with and without the patch, then kill the BEAM.
Without the patch, the "sleep" process will continue:

    erl -noshell -eval 'os:cmd("sleep 60")'

TODO:
* Needs a decision made between killing the process or process group.
* Separate patch for win32
  • Loading branch information
adamwight committed Feb 27, 2025
1 parent 68cf02a commit f7c336f
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 0 deletions.
22 changes: 22 additions & 0 deletions erts/emulator/sys/unix/erl_child_setup.c
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ static ssize_t write_all(int fd, const char *buff, size_t size) {
return pos;
}

static void kill_child(pid_t os_pid);
static void kill_all_children(void);
static int forker_hash_init(void);

static int max_files = -1;
Expand Down Expand Up @@ -571,6 +573,7 @@ main(int argc, char *argv[])
tcsetattr(0,TCSANOW,&initial_tty_mode);
}
DEBUG_PRINT("erl_child_setup failed to read from uds: %d, %d", res, errno);
kill_all_children();
_exit(0);
}

Expand All @@ -579,6 +582,7 @@ main(int argc, char *argv[])
if (isatty(0) && isatty(1)) {
tcsetattr(0,TCSANOW,&initial_tty_mode);
}
kill_all_children();
_exit(0);
}
/* Since we use unix domain sockets and send the entire data in
Expand Down Expand Up @@ -621,6 +625,7 @@ main(int argc, char *argv[])
est.os_pid = proto.u.stop.os_pid;
es = hash_remove(forker_hash, &est);
if (es) {
kill_child(es->os_pid);
free(es);
}
} else {
Expand Down Expand Up @@ -660,6 +665,23 @@ main(int argc, char *argv[])
return 1;
}

/* Kill child process groups on VM termination so they don't become orphaned. */

static void kill_child(pid_t os_pid) {
if (os_pid > 0 && kill(os_pid, SIGTERM) != 0) {
DEBUG_PRINT("error killing process %d: %d", os_pid, errno);
}
}

static void fun_kill_foreach(ErtsSysExitStatus *es, void *unused) {
kill_child(es->os_pid);
}

static void kill_all_children(void) {
DEBUG_PRINT("cleaning up by killing all %d child processes", forker_hash->nobjs);
hash_foreach(forker_hash, (HFOREACH_FUN)fun_kill_foreach, NULL);
}

static int fcmp(void *a, void *b)
{
ErtsSysExitStatus *sa = a;
Expand Down
5 changes: 5 additions & 0 deletions erts/preloaded/src/erlang.erl
Original file line number Diff line number Diff line change
Expand Up @@ -7475,6 +7475,11 @@ reported to the owning process using signals of the form
The maximum number of ports that can be open at the same time can be configured
by passing command-line flag [`+Q`](erl_cmd.md#max_ports) to [erl](erl_cmd.md).
When a port is closed or the VM shuts down, spawned executables are sent a
`SIGTERM` on unix. The child may still outlive the VM if it traps the signal.
Note that any processes started under a shell using `spawn` will not terminate
unless they respond to stdin or stdout being closed.
""".
-doc #{ category => ports }.
-spec open_port(PortName, PortSettings) -> port() when
Expand Down

0 comments on commit f7c336f

Please sign in to comment.