You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

144 lines
5.0 KiB

  1. From ff81ac47267c4e0227d1e3fbc5b1dedfd81a2a2f Mon Sep 17 00:00:00 2001
  2. From: Willy Tarreau <w@1wt.eu>
  3. Date: Tue, 25 Oct 2016 17:20:24 +0200
  4. Subject: [PATCH 16/26] BUG/MEDIUM: systemd: let the wrapper know that haproxy
  5. has completed or failed
  6. Pierre Cheynier found that there's a persistent issue with the systemd
  7. wrapper. Too fast reloads can lead to certain old processes not being
  8. signaled at all and continuing to run. The problem was tracked down as
  9. a race between the startup and the signal processing : nothing prevents
  10. the wrapper from starting new processes while others are still starting,
  11. and the resulting pid file will only contain the latest pids in this
  12. case. This can happen with large configs and/or when a lot of SSL
  13. certificates are involved.
  14. In order to solve this we want the wrapper to wait for the new processes
  15. to complete their startup. But we also want to ensure it doesn't wait for
  16. nothing in case of error.
  17. The solution found here is to create a pipe between the wrapper and the
  18. sub-processes. The wrapper waits on the pipe and the sub-processes are
  19. expected to close this pipe once they completed their startup. That way
  20. we don't queue up new processes until the previous ones have registered
  21. their pids to the pid file. And if anything goes wrong, the wrapper is
  22. immediately released. The only thing is that we need the sub-processes
  23. to know the pipe's file descriptor. We pass it in an environment variable
  24. called HAPROXY_WRAPPER_FD.
  25. It was confirmed both by Pierre and myself that this completely solves
  26. the "zombie" process issue so that only the new processes continue to
  27. listen on the sockets.
  28. It seems that in the future this stuff could be moved to the haproxy
  29. master process, also getting rid of an environment variable.
  30. This fix needs to be backported to 1.6 and 1.5.
  31. (cherry picked from commit b957109727f7fed556c049d40bacf45f0df211db)
  32. ---
  33. src/haproxy-systemd-wrapper.c | 36 ++++++++++++++++++++++++++++++++++++
  34. src/haproxy.c | 10 ++++++++++
  35. 2 files changed, 46 insertions(+)
  36. diff --git a/src/haproxy-systemd-wrapper.c b/src/haproxy-systemd-wrapper.c
  37. index f4fcab1..b426f96 100644
  38. --- a/src/haproxy-systemd-wrapper.c
  39. +++ b/src/haproxy-systemd-wrapper.c
  40. @@ -65,16 +65,30 @@ static void locate_haproxy(char *buffer, size_t buffer_size)
  41. return;
  42. }
  43. +/* Note: this function must not exit in case of error (except in the child), as
  44. + * it is only dedicated the starting a new haproxy process. By keeping the
  45. + * process alive it will ensure that future signal delivery may get rid of
  46. + * the issue. If the first startup fails, the wrapper will notice it and
  47. + * return an error thanks to wait() returning ECHILD.
  48. + */
  49. static void spawn_haproxy(char **pid_strv, int nb_pid)
  50. {
  51. char haproxy_bin[512];
  52. pid_t pid;
  53. int main_argc;
  54. char **main_argv;
  55. + int pipefd[2];
  56. + char fdstr[20];
  57. + int ret;
  58. main_argc = wrapper_argc - 1;
  59. main_argv = wrapper_argv + 1;
  60. + if (pipe(pipefd) != 0) {
  61. + fprintf(stderr, SD_NOTICE "haproxy-systemd-wrapper: failed to create a pipe, please try again later.\n");
  62. + return;
  63. + }
  64. +
  65. pid = fork();
  66. if (!pid) {
  67. char **argv;
  68. @@ -89,6 +103,15 @@ static void spawn_haproxy(char **pid_strv, int nb_pid)
  69. }
  70. reset_signal_handler();
  71. +
  72. + close(pipefd[0]); /* close the read side */
  73. +
  74. + snprintf(fdstr, sizeof(fdstr), "%d", pipefd[1]);
  75. + if (setenv("HAPROXY_WRAPPER_FD", fdstr, 1) != 0) {
  76. + fprintf(stderr, SD_NOTICE "haproxy-systemd-wrapper: failed to setenv(), please try again later.\n");
  77. + exit(1);
  78. + }
  79. +
  80. locate_haproxy(haproxy_bin, 512);
  81. argv[argno++] = haproxy_bin;
  82. for (i = 0; i < main_argc; ++i)
  83. @@ -113,6 +136,19 @@ static void spawn_haproxy(char **pid_strv, int nb_pid)
  84. else if (pid == -1) {
  85. fprintf(stderr, SD_NOTICE "haproxy-systemd-wrapper: failed to fork(), please try again later.\n");
  86. }
  87. +
  88. + /* The parent closes the write side and waits for the child to close it
  89. + * as well. Also deal the case where the fd would unexpectedly be 1 or 2
  90. + * by silently draining all data.
  91. + */
  92. + close(pipefd[1]);
  93. +
  94. + do {
  95. + char c;
  96. + ret = read(pipefd[0], &c, sizeof(c));
  97. + } while ((ret > 0) || (ret == -1 && errno == EINTR));
  98. + /* the child has finished starting up */
  99. + close(pipefd[0]);
  100. }
  101. static int read_pids(char ***pid_strv)
  102. diff --git a/src/haproxy.c b/src/haproxy.c
  103. index a657dc4..2d476f2 100644
  104. --- a/src/haproxy.c
  105. +++ b/src/haproxy.c
  106. @@ -1843,6 +1843,7 @@ int main(int argc, char **argv)
  107. int ret = 0;
  108. int *children = calloc(global.nbproc, sizeof(int));
  109. int proc;
  110. + char *wrapper_fd;
  111. /* the father launches the required number of processes */
  112. for (proc = 0; proc < global.nbproc; proc++) {
  113. @@ -1879,6 +1880,15 @@ int main(int argc, char **argv)
  114. close(pidfd);
  115. }
  116. + /* each child must notify the wrapper that it's ready by closing the requested fd */
  117. + wrapper_fd = getenv("HAPROXY_WRAPPER_FD");
  118. + if (wrapper_fd) {
  119. + int pipe_fd = atoi(wrapper_fd);
  120. +
  121. + if (pipe_fd >= 0)
  122. + close(pipe_fd);
  123. + }
  124. +
  125. /* We won't ever use this anymore */
  126. free(oldpids); oldpids = NULL;
  127. free(global.chroot); global.chroot = NULL;
  128. --
  129. 2.7.3