]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: logs: startup-logs can use a shm for logging the reload
authorWilliam Lallemand <wlallemand@haproxy.org>
Mon, 26 Sep 2022 10:54:39 +0000 (12:54 +0200)
committerWilliam Lallemand <wlallemand@haproxy.org>
Thu, 13 Oct 2022 14:50:22 +0000 (16:50 +0200)
When compiled with USE_SHM_OPEN=1 the startup-logs are now able to use
an shm which is used to keep the logs when switching to mworker wait
mode. This allows to keep the failed reload logs.

When allocating the startup-logs at first start of the process, haproxy
will do a shm_open with a unique path using the PID of the process, the
file is unlink immediatly so we don't let unwelcomed files be. The fd
resulting from this shm is stored in the HAPROXY_STARTUPLOGS_FD
environment variable so it can be mmap again when switching to wait
mode.

When forking children, the process is copying the mmap to a a mallocated
ring so we never share the same memory section between the master and
the workers. When switching to wait mode, the shm is not used anymore as
it is also copied to a mallocated structure.

This allow to use the "show startup-logs" command over the master CLI,
to get the logs of the latest startup or reload. This way the logs of
the latest failed reload are also kept.

This is only activated on the linux-glibc target for now.

Makefile
include/haproxy/defaults.h
include/haproxy/errors.h
src/errors.c
src/haproxy.c

index 65d8d0c1c60dff25f3827081f2dbfae9475a814a..9123e0261178446a74884b4ac92e56ad4b5ce832 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -57,6 +57,7 @@
 #   USE_MEMORY_PROFILING : enable the memory profiler. Linux-glibc only.
 #   USE_LIBATOMIC        : force to link with/without libatomic. Automatic.
 #   USE_PTHREAD_EMULATION: replace pthread's rwlocks with ours
+#   USE_SHM_OPEN         : use shm_open() for the startup-logs
 #
 # Options can be forced by specifying "USE_xxx=1" or can be disabled by using
 # "USE_xxx=" (empty string). The list of enabled and disabled options for a
@@ -345,7 +346,8 @@ use_opts = USE_EPOLL USE_KQUEUE USE_NETFILTER                                 \
            USE_CLOSEFROM USE_ZLIB USE_SLZ USE_CPU_AFFINITY USE_TFO USE_NS     \
            USE_DL USE_RT USE_DEVICEATLAS USE_51DEGREES USE_WURFL USE_SYSTEMD  \
            USE_OBSOLETE_LINKER USE_PRCTL USE_PROCCTL USE_THREAD_DUMP          \
-           USE_EVPORTS USE_OT USE_QUIC USE_PROMEX USE_MEMORY_PROFILING
+           USE_EVPORTS USE_OT USE_QUIC USE_PROMEX USE_MEMORY_PROFILING        \
+           USE_SHM_OPEN
 
 #### Target system options
 # Depending on the target platform, some options are set, as well as some
@@ -382,7 +384,7 @@ ifeq ($(TARGET),linux-glibc)
     USE_POLL USE_TPROXY USE_LIBCRYPT USE_DL USE_RT USE_CRYPT_H USE_NETFILTER  \
     USE_CPU_AFFINITY USE_THREAD USE_EPOLL USE_LINUX_TPROXY                    \
     USE_ACCEPT4 USE_LINUX_SPLICE USE_PRCTL USE_THREAD_DUMP USE_NS USE_TFO     \
-    USE_GETADDRINFO USE_BACKTRACE)
+    USE_GETADDRINFO USE_BACKTRACE USE_SHM_OPEN)
   INSTALL = install -v
 endif
 
index a8e5e578023752a9a70787a425c6ec885940c0b4..7b4992822e1a415f4c24bdeae5742b500791db9c 100644 (file)
 #define MAX_SYSLOG_LEN          1024
 #endif
 
-/* 64kB to archive startup-logs seems way more than enough */
+/* 64kB to archive startup-logs seems way more than enough
+ * /!\ Careful when changing this size, it is used in a shm when exec() from
+ * mworker to wait mode.
+ */
 #ifndef STARTUP_LOG_SIZE
 #define STARTUP_LOG_SIZE        65536
 #endif
index e9ad588357a697fe73f96fde4f0dabc8cf13111b..81a4b2f1134775bae37462da1126d333431cbb05 100644 (file)
@@ -43,6 +43,7 @@
 
 #define ERR_CODE       (ERR_RETRYABLE|ERR_FATAL|ERR_ABORT)     /* mask */
 
+extern struct ring *startup_logs;
 
 /* These codes may be used by config parsing functions which detect errors and
  * which need to inform the upper layer about them. They are all prefixed with
@@ -123,6 +124,10 @@ void ha_notice(const char *fmt, ...)
 void qfprintf(FILE *out, const char *fmt, ...)
        __attribute__ ((format(printf, 2, 3)));
 
+void startup_logs_init();
+struct ring *startup_logs_dup(struct ring *src);
+void startup_logs_free(struct ring *r);
+
 #endif /* _HAPROXY_ERRORS_H */
 
 /*
index bd3c271f42c33d159612f490871383b7fd6bc1a1..96ed60d2a72a5cdfe12917b6e80f322cb47b3d08 100644 (file)
@@ -1,5 +1,9 @@
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
 #include <stdarg.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <syslog.h>
 
 #include <haproxy/api.h>
 
 /* A global buffer used to store all startup alerts/warnings. It will then be
  * retrieve on the CLI. */
-static struct ring *startup_logs = NULL;
+struct ring *startup_logs = NULL;
+#ifdef USE_SHM_OPEN
+static struct ring *shm_startup_logs = NULL;
+#endif
 
 /* A thread local buffer used to store all alerts/warnings. It can be used to
  * retrieve them for CLI commands after startup.
@@ -28,6 +35,185 @@ static THREAD_LOCAL struct buffer usermsgs_buf = BUF_NULL;
 #define USERMSGS_CTX_BUFSIZE   PATH_MAX
 static THREAD_LOCAL struct usermsgs_ctx usermsgs_ctx = { .str = BUF_NULL, };
 
+#ifdef USE_SHM_OPEN
+
+/* initialise an SHM for the startup logs and return its fd */
+static int startup_logs_new_shm()
+{
+       char *path = NULL;
+       int fd = -1;
+       int flags;
+
+       /* create a unique path per PID so we don't collide with another
+          process */
+       memprintf(&path, "/haproxy_startup_logs_%d", getpid());
+       fd = shm_open(path, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
+       if (fd == -1)
+               goto error;
+       shm_unlink(path);
+       ha_free(&path);
+
+       if (ftruncate(fd, STARTUP_LOG_SIZE) == -1)
+               goto error;
+
+       flags = fcntl(fd, F_GETFD);
+       if (flags == -1)
+               goto error;
+       flags &= ~FD_CLOEXEC;
+       flags = fcntl(fd, F_SETFD, flags);
+       if (flags == -1)
+               goto error;
+
+       return fd;
+error:
+       if (fd != -1) {
+               close(fd);
+               fd = -1;
+       }
+       return fd;
+}
+
+/* mmap a startup-logs from a <fd>.
+ * if <new> is set to one, initialize the buffer.
+ * Returns the ring.
+ */
+static struct ring *startup_logs_from_fd(int fd, int new)
+{
+       char *area;
+       struct ring *r = NULL;
+
+       if (fd == -1)
+               goto error;
+
+       area = mmap(NULL, STARTUP_LOG_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+       if (area == MAP_FAILED || area == NULL)
+               goto error;
+
+       if (new)
+               r = ring_make_from_area(area, STARTUP_LOG_SIZE);
+       else
+               r = ring_cast_from_area(area);
+
+       if (r == NULL)
+               goto error;
+
+       shm_startup_logs = r; /* save the ptr so we can unmap later */
+
+       return r;
+error:
+       return NULL;
+}
+
+/*
+ * Use a shm accross reexec of the master.
+ *
+ * During the startup of the master, a shm_open must be done and the FD saved
+ * into the HAPROXY_STARTUPLOGS_FD environment variable.
+ *
+ * When forking workers, the child must use a copy of the shm, not the shm itself.
+ *
+ * Once in wait mode, the shm must be copied and closed.
+ *
+ */
+void startup_logs_init()
+{
+       struct ring *r = NULL;
+       char *str_fd, *endptr;
+       int fd = -1;
+
+       str_fd = getenv("HAPROXY_STARTUPLOGS_FD");
+       if (str_fd) {
+               fd = strtol(str_fd, &endptr, 10);
+               if (*endptr != '\0')
+                       goto error;
+               unsetenv("HAPROXY_STARTUPLOGS_FD");
+       }
+
+       /* during startup, or just after a reload.
+        * Note: the WAIT_ONLY env variable must be
+        * check in case of an early call  */
+       if (!(global.mode & MODE_MWORKER_WAIT) &&
+           getenv("HAPROXY_MWORKER_WAIT_ONLY") == NULL) {
+               if (fd != -1)
+                       close(fd);
+
+               fd = startup_logs_new_shm();
+               if (fd == -1)
+                       goto error;
+
+               r = startup_logs_from_fd(fd, 1);
+               if (!r)
+                       goto error;
+
+               memprintf(&str_fd, "%d", fd);
+               setenv("HAPROXY_STARTUPLOGS_FD", str_fd, 1);
+               ha_free(&str_fd);
+
+       } else {
+               /* in wait mode, copy the shm to an allocated buffer */
+               struct ring *prev = NULL;
+
+               if (fd == -1)
+                       goto error;
+
+               prev = startup_logs_from_fd(fd, 0);
+               if (!prev)
+                       goto error;
+
+               r = startup_logs_dup(prev);
+               if (!r)
+                       goto error;
+               startup_logs_free(prev);
+               close(fd);
+       }
+
+       startup_logs = r;
+
+       return;
+error:
+       if (fd != -1)
+               close(fd);
+       /* couldn't get a mmap to work */
+       startup_logs = ring_new(STARTUP_LOG_SIZE);
+
+}
+
+#else /* ! USE_SHM_OPEN */
+
+void startup_logs_init()
+{
+       startup_logs = ring_new(STARTUP_LOG_SIZE);
+}
+
+#endif
+
+/* free the startup logs, unmap if it was an shm */
+void startup_logs_free(struct ring *r)
+{
+#ifdef USE_SHM_OPEN
+       if (r == shm_startup_logs)
+               munmap(r, STARTUP_LOG_SIZE);
+       else
+#endif /* ! USE_SHM_OPEN */
+               ring_free(r);
+}
+
+/* duplicate a startup logs which was previously allocated in a shm */
+struct ring *startup_logs_dup(struct ring *src)
+{
+       struct ring *dst = NULL;
+
+       /* must use the size of the previous buffer */
+       dst = ring_new(b_size(&src->buf));
+       if (!dst)
+               goto error;
+
+       b_reset(&dst->buf);
+       b_ncat(&dst->buf, &src->buf, b_data(&src->buf));
+error:
+       return dst;
+}
+
 /* Put msg in usermsgs_buf.
  *
  * The message should not be terminated by a newline because this function
@@ -200,7 +386,7 @@ static void print_message(int use_usermsgs_ctx, const char *label, const char *f
 
        if (global.mode & MODE_STARTING) {
                if (unlikely(!startup_logs))
-                       startup_logs = ring_new(STARTUP_LOG_SIZE);
+                       startup_logs_init();
 
                if (likely(startup_logs)) {
                        struct ist m[3];
@@ -361,7 +547,7 @@ static int cli_parse_show_startup_logs(char **args, char *payload, struct appctx
 
 /* register cli keywords */
 static struct cli_kw_list cli_kws = {{ },{
-       { { "show", "startup-logs",  NULL }, "show startup-logs                       : report logs emitted during HAProxy startup", cli_parse_show_startup_logs, NULL, NULL },
+       { { "show", "startup-logs",  NULL }, "show startup-logs                       : report logs emitted during HAProxy startup", cli_parse_show_startup_logs, NULL, NULL, NULL, ACCESS_MASTER },
        {{},}
 }};
 
index a95fc21e451b68aff8b1c03b4ae2758e5b0c8087..8064970622c75ed7109143812dddb49c6310d95c 100644 (file)
@@ -1909,6 +1909,8 @@ static void init(int argc, char **argv)
        struct pre_check_fct *prcf;
        int ideal_maxconn;
 
+       startup_logs_init();
+
        if (!init_trash_buffers(1)) {
                ha_alert("failed to initialize trash buffers.\n");
                exit(1);
@@ -3431,9 +3433,13 @@ int main(int argc, char **argv)
 
                /* the father launches the required number of processes */
                if (!(global.mode & MODE_MWORKER_WAIT)) {
+                       struct ring *tmp_startup_logs = NULL;
+
                        if (global.mode & MODE_MWORKER)
                                mworker_ext_launch_all();
 
+                       /* at this point the worker must have his own startup_logs buffer */
+                       tmp_startup_logs = startup_logs_dup(startup_logs);
                        ret = fork();
                        if (ret < 0) {
                                ha_alert("[%s.main()] Cannot fork.\n", argv[0]);
@@ -3441,6 +3447,8 @@ int main(int argc, char **argv)
                                exit(1); /* there has been an error */
                        }
                        else if (ret == 0) { /* child breaks here */
+                               startup_logs_free(startup_logs);
+                               startup_logs = tmp_startup_logs;
                                /* This one must not be exported, it's internal! */
                                unsetenv("HAPROXY_MWORKER_REEXEC");
                                ha_random_jump96(1);