Skip to content

Commit 80f301a

Browse files
committed
Forward parent death to descendant processes
If the uds_fd connection to the parent BEAM is broken or closed, react by killing all children and any descendants in the same process group. A concise demonstration of the problem being solved is to run this 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")' To intentionally start a child process which can outlive BEAM termination, give it a new process group for example by using `setsid`: erl -noshell -eval 'os:cmd("setsid sleep 60")' TODO: Needs to be tested on win32
1 parent 99b7bde commit 80f301a

File tree

2 files changed

+24
-0
lines changed

2 files changed

+24
-0
lines changed

erts/emulator/sys/unix/erl_child_setup.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ static ssize_t write_all(int fd, const char *buff, size_t size) {
174174
return pos;
175175
}
176176

177+
static void kill_all_children(void);
177178
static void add_os_pid_to_port_id_mapping(Eterm, pid_t);
178179
static Eterm get_port_id(pid_t);
179180
static int forker_hash_init(void);
@@ -564,6 +565,7 @@ main(int argc, char *argv[])
564565
tcsetattr(0,TCSANOW,&initial_tty_mode);
565566
}
566567
DEBUG_PRINT("erl_child_setup failed to read from uds: %d, %d", res, errno);
568+
kill_all_children();
567569
_exit(0);
568570
}
569571

@@ -572,6 +574,7 @@ main(int argc, char *argv[])
572574
if (isatty(0) && isatty(1)) {
573575
tcsetattr(0,TCSANOW,&initial_tty_mode);
574576
}
577+
kill_all_children();
575578
_exit(0);
576579
}
577580
/* Since we use unix domain sockets and send the entire data in
@@ -642,6 +645,23 @@ typedef struct exit_status {
642645

643646
static Hash *forker_hash;
644647

648+
/* Kill child process groups on VM termination, so they don't become orphaned.
649+
* If the child should continue running after the VM stops, the child will need
650+
* to either trap the TERM signal, or change its process group eg. by calling
651+
* `setsid` */
652+
653+
static void fun_kill_foreach(ErtsSysExitStatus *es, void *unused) {
654+
DEBUG_PRINT("killing process group %d", es->os_pid);
655+
if (kill(-es->os_pid, SIGTERM) != 0) {
656+
DEBUG_PRINT("error killing process group %d: %d", es->os_pid, errno);
657+
}
658+
}
659+
660+
static void kill_all_children(void) {
661+
DEBUG_PRINT("cleaning up by killing all %d child process groups", forker_hash->nobjs);
662+
hash_foreach(forker_hash, (HFOREACH_FUN)fun_kill_foreach, NULL);
663+
}
664+
645665
static void add_os_pid_to_port_id_mapping(Eterm port_id, pid_t os_pid)
646666
{
647667
ErtsSysExitStatus es;

erts/preloaded/src/erlang.erl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7324,6 +7324,10 @@ reported to the owning process using signals of the form
73247324

73257325
The maximum number of ports that can be open at the same time can be configured
73267326
by passing command-line flag [`+Q`](erl_cmd.md#max_ports) to [erl](erl_cmd.md).
7327+
7328+
When the VM shuts down, it tries to terminate all spawned executables by sending
7329+
a `SIGTERM` to each child's process group. The child may still outlive the VM if
7330+
it traps the signal or if it has changed its process group since starting.
73277331
""".
73287332
-doc #{ category => ports }.
73297333
-spec open_port(PortName, PortSettings) -> port() when

0 commit comments

Comments
 (0)