Skip to content

Commit 4931d1c

Browse files
committed
BUG/MEIDUM: mworker: fix fd leak from master to worker
During re-execution master keeps always opened "reload" sockpair FDs and shared sockpair ipc_fd[0], the latter is using to transfert listeners sockets from the previously forked worker to the new one. So, these master's FDs are inherited in the newly forked worker and must be closed in its context. "reload" sockpair inherited FDs and shared sockpair FD (ipc_fd[0]) are closed separately, becase master doesn't recreate "reload" sockpair each time after its re-exec. It always keeps the same FDs for this "reload" sockpair. So in worker context it can be closed immediately after the fork. At contrast, shared sockpair is created each time after reload, when the new worker will be forked. So, if N previous workers are still exist at this moment, the new worker will inherit N ipc_fd[0] from master. So, it's more save to close all these FDs after get_listeners_fd() and bind_listeners() calls. Otherwise, early closed FDs in the worker context will be immediately bound to listeners and we could potentially have some bugs.
1 parent 745a4c5 commit 4931d1c

File tree

2 files changed

+45
-0
lines changed

2 files changed

+45
-0
lines changed

src/haproxy.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2162,6 +2162,18 @@ static void apply_master_worker_mode()
21622162

21632163
break;
21642164
}
2165+
2166+
/* need to close reload sockpair fds, inherited after master's execvp and fork(),
2167+
* we can't close these fds in master before the fork(), as ipc_fd[1] serves after
2168+
* the mworker_reexec to obtain the MCLI client connection fd, like this we can
2169+
* write to this connection fd the content of the startup_logs ring.
2170+
*/
2171+
if (child->options & PROC_O_TYPE_MASTER) {
2172+
if (child->ipc_fd[0] > 0)
2173+
close(child->ipc_fd[0]);
2174+
if (child->ipc_fd[1] > 0)
2175+
close(child->ipc_fd[1]);
2176+
}
21652177
}
21662178
break;
21672179
default:
@@ -3672,6 +3684,7 @@ int main(int argc, char **argv)
36723684
int intovf = (unsigned char)argc + 1; /* let the compiler know it's strictly positive */
36733685
struct cfgfile *cfg, *cfg_tmp;
36743686
struct ring *tmp_startup_logs = NULL;
3687+
struct mworker_proc *proc;
36753688

36763689
/* Catch broken toolchains */
36773690
if (sizeof(long) != sizeof(void *) || (intovf + 0x7FFFFFFF >= intovf)) {
@@ -3861,6 +3874,27 @@ int main(int argc, char **argv)
38613874

38623875
bind_listeners();
38633876

3877+
/* worker context: now listeners fds were transferred from the previous
3878+
* worker, all listeners fd are bound. So we can close ipc_fd[0]s of all
3879+
* previous workers, which are still referenced in the proc_list, i.e.
3880+
* they are not exited yet at the moment, when this current worker was
3881+
* forked. Thus the current worker inherits ipc_fd[0]s from the previous
3882+
* ones by it's parent, master, because we have to keep shared sockpair
3883+
* ipc_fd[0] always opened in master (master CLI server is listening on
3884+
* this fd). It's safe to call close() at this point on these inhereted
3885+
* ipc_fd[0]s, as they are inhereted after master re-exec unbound, we
3886+
* keep them like this during bind_listeners() call. So, these fds were
3887+
* never referenced in the current worker's fdtab.
3888+
*/
3889+
if ((global.mode & MODE_MWORKER) && !master) {
3890+
list_for_each_entry(proc, &proc_list, list) {
3891+
if ((proc->options & PROC_O_TYPE_WORKER) && (proc->options & PROC_O_LEAVING)) {
3892+
close(proc->ipc_fd[0]);
3893+
proc->ipc_fd[0] = -1;
3894+
}
3895+
}
3896+
}
3897+
38643898
/* Exit in standalone mode, if no listeners found */
38653899
if (!(global.mode & MODE_MWORKER) && listeners == 0) {
38663900
ha_alert("[%s.main()] No enabled listener found (check for 'bind' directives) ! Exiting.\n", argv[0]);

src/sock.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,17 @@ int sock_get_old_sockets(const char *unixsocket)
440440
int sv[2];
441441
int dst_fd;
442442

443+
/* dst_fd is always open in the worker process context because
444+
* it's inherited from the master via -x cmd option. It's closed
445+
* futher in main (after bind_listeners()) and not here for the
446+
* simplicity. In main(), after bind_listeners(), it's safe just
447+
* to loop over all workers list, launched before this reload and
448+
* to close its ipc_fd[0], thus we also close this fd. If we
449+
* would close dst_fd here, it might be potentially "reused" in
450+
* bind_listeners() followed this call, thus it would be difficult
451+
* to exclude it, in the case if it was bound again when we will
452+
* filter the previous workers list.
453+
*/
443454
dst_fd = strtoll(unixsocket + strlen("sockpair@"), NULL, 0);
444455

445456
if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) {

0 commit comments

Comments
 (0)