[pve-devel] [PATCH qemu-server 1/3] qmeventd: rework 'forced_cleanup' handling and set timeout to 60s

Fabian Grünbichler f.gruenbichler at proxmox.com
Thu Sep 22 10:24:55 CEST 2022


On September 21, 2022 2:49 pm, Dominik Csapak wrote:
> currently, the 'forced_cleanup' (sending SIGKILL to the qemu process),
> is intended to be triggered 5 seconds after sending the initial shutdown
> signal (SIGTERM) which is sadly not enough for some setups.
> 
> Accidentally, it could be triggered earlier than 5 seconds, if a
> SIGALRM triggers in the timespan directly before setting it again.
> 
> Also, this approach means that depending on when machines are shutdown
> their forced cleanup may happen after 5 seconds, or any time after, if
> new vms are shut off in the meantime.
> 
> To improve this situation, rework the way we deal with this cleanup, by
> saving the time incl. timeout in the CleanupData, and trigger a
> forced_cleanup every 10 seconds. If that happends, remove it from
> the forced_cleanup list.
> 
> To improve the shutdown behaviour, increase the default timeout to 60
> seconds, which should be enough, but add a commandline toggle where
> users can set it to a different value.
> 
> Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
> ---
>  qmeventd/qmeventd.c | 75 +++++++++++++++++++++++++--------------------
>  qmeventd/qmeventd.h |  2 ++
>  2 files changed, 44 insertions(+), 33 deletions(-)
> 
> diff --git a/qmeventd/qmeventd.c b/qmeventd/qmeventd.c
> index 8d32827..e9ff5b3 100644
> --- a/qmeventd/qmeventd.c
> +++ b/qmeventd/qmeventd.c
> @@ -29,6 +29,7 @@
>  #include <signal.h>
>  #include <stdbool.h>
>  #include <stdio.h>
> +#include <stdlib.h>
>  #include <string.h>
>  #include <sys/epoll.h>
>  #include <sys/socket.h>
> @@ -36,15 +37,18 @@
>  #include <sys/un.h>
>  #include <sys/wait.h>
>  #include <unistd.h>
> +#include <time.h>
>  
>  #include "qmeventd.h"
>  
> +#define DEFAULT_KILL_TIMEOUT 60
> +
>  static int verbose = 0;
> +static int kill_timeout = DEFAULT_KILL_TIMEOUT;
>  static int epoll_fd = 0;
>  static const char *progname;
>  GHashTable *vm_clients; // key=vmid (freed on remove), value=*Client (free manually)
>  GSList *forced_cleanups;
> -volatile sig_atomic_t alarm_triggered = 0;
>  
>  /*
>   * Helper functions
> @@ -54,9 +58,10 @@ static void
>  usage()
>  {
>      fprintf(stderr, "Usage: %s [-f] [-v] PATH\n", progname);
> -    fprintf(stderr, "  -f       run in foreground (default: false)\n");
> -    fprintf(stderr, "  -v       verbose (default: false)\n");
> -    fprintf(stderr, "  PATH     use PATH for socket\n");
> +    fprintf(stderr, "  -f           run in foreground (default: false)\n");
> +    fprintf(stderr, "  -v           verbose (default: false)\n");
> +    fprintf(stderr, "  -t timeout   kill timeout (default: %ds)\n", DEFAULT_KILL_TIMEOUT);
> +    fprintf(stderr, "  PATH         use PATH for socket\n");
>  }
>  
>  static pid_t
> @@ -469,16 +474,16 @@ terminate_client(struct Client *client)
>      int err = kill(client->pid, SIGTERM);
>      log_neg(err, "kill");
>  
> +    time_t timeout = time(NULL) + kill_timeout;
> +
>      struct CleanupData *data_ptr = malloc(sizeof(struct CleanupData));
>      struct CleanupData data = {
>  	.pid = client->pid,
> -	.pidfd = pidfd
> +	.pidfd = pidfd,
> +	.timeout = timeout,
>      };
>      *data_ptr = data;
>      forced_cleanups = g_slist_prepend(forced_cleanups, (void *)data_ptr);
> -
> -    // resets any other alarms, but will fire eventually and cleanup all
> -    alarm(5);
>  }
>  
>  void
> @@ -551,27 +556,16 @@ handle_client(struct Client *client)
>      json_tokener_free(tok);
>  }
>  
> -
> -/*
> - * SIGALRM and cleanup handling
> - *
> - * terminate_client will set an alarm for 5 seconds and add its client's PID to
> - * the forced_cleanups list - when the timer expires, we iterate the list and
> - * attempt to issue SIGKILL to all processes which haven't yet stopped.
> - */
> -
> -static void
> -alarm_handler(__attribute__((unused)) int signum)
> -{
> -    alarm_triggered = 1;
> -}
> -

wasn't this intentionally decoupled like this?

alarm_handler just sets the flag
actual force cleanup is conditionalized on the alarm having triggered, 
but the cleanup happens outside of the signal handler..

is there a reason from switching away from these scheme? we don't need 
to do the cleanup in the signal handler (timing is already plenty fuzzy 
anyway ;))

>  static void
>  sigkill(void *ptr, __attribute__((unused)) void *unused)
>  {
>      struct CleanupData data = *((struct CleanupData *)ptr);
>      int err;
>  
> +    if (data.timeout > time(NULL)) {

nit: current time / cutoff could be passed in via the currently unused 
user_data parameter..

> +	return;
> +    }
> +
>      if (data.pidfd > 0) {
>  	err = pidfd_send_signal(data.pidfd, SIGKILL, NULL, 0);
>  	(void)close(data.pidfd);
> @@ -588,21 +582,29 @@ sigkill(void *ptr, __attribute__((unused)) void *unused)
>  	fprintf(stderr, "cleanup failed, terminating pid '%d' with SIGKILL\n",
>  		data.pid);
>      }
> +
> +    // remove ourselves from the list
> +    forced_cleanups = g_slist_remove(forced_cleanups, ptr);
> +    free(ptr);
>  }
>  
> +/*
> + * SIGALRM and cleanup handling
> + *
> + * handles the cleanup on non terminated qemu processes, will be called every
> + * 10 seconds by setting 'alarm(10)' at the end again
> + */
> +
>  static void
> -handle_forced_cleanup()
> +alarm_handler(__attribute__((unused)) int signum)
>  {
> -    if (alarm_triggered) {
> +    if (g_slist_length(forced_cleanups) > 0) {
>  	VERBOSE_PRINT("clearing forced cleanup backlog\n");
> -	alarm_triggered = 0;
>  	g_slist_foreach(forced_cleanups, sigkill, NULL);
> -	g_slist_free_full(forced_cleanups, free);
> -	forced_cleanups = NULL;
>      }
> +    alarm(10);
>  }
>  
> -
>  int
>  main(int argc, char *argv[])
>  {
> @@ -611,7 +613,7 @@ main(int argc, char *argv[])
>      char *socket_path = NULL;
>      progname = argv[0];
>  
> -    while ((opt = getopt(argc, argv, "hfv")) != -1) {
> +    while ((opt = getopt(argc, argv, "hfvt:")) != -1) {
>  	switch (opt) {
>  	    case 'f':
>  		daemonize = 0;
> @@ -619,6 +621,15 @@ main(int argc, char *argv[])
>  	    case 'v':
>  		verbose = 1;
>  		break;
> +	    case 't':
> +		errno = 0;
> +		char *endptr = NULL;
> +		kill_timeout = strtoul(optarg, &endptr, 10);
> +		if (errno != 0 || *endptr != '\0' || kill_timeout == 0) {
> +		    usage();
> +		    exit(EXIT_FAILURE);
> +		}
> +		break;
>  	    case 'h':
>  		usage();
>  		exit(EXIT_SUCCESS);
> @@ -668,10 +679,10 @@ main(int argc, char *argv[])
>  
>      int nevents;
>  
> +    alarm(10);
>      for(;;) {
>  	nevents = epoll_wait(epoll_fd, events, 1, -1);
>  	if (nevents < 0 && errno == EINTR) {
> -	    handle_forced_cleanup();
>  	    continue;
>  	}
>  	bail_neg(nevents, "epoll_wait");
> @@ -688,7 +699,5 @@ main(int argc, char *argv[])
>  		handle_client((struct Client *)events[n].data.ptr);
>  	    }
>  	}
> -
> -	handle_forced_cleanup();
>      }
>  }
> diff --git a/qmeventd/qmeventd.h b/qmeventd/qmeventd.h
> index 2cf1947..1b9cea1 100644
> --- a/qmeventd/qmeventd.h
> +++ b/qmeventd/qmeventd.h
> @@ -7,6 +7,7 @@
>  */
>  
>  #include <sys/syscall.h>
> +#include <time.h>
>  
>  #ifndef __NR_pidfd_open
>  #define __NR_pidfd_open 434
> @@ -86,6 +87,7 @@ struct Client {
>  struct CleanupData {
>      pid_t pid;
>      int pidfd;
> +    time_t timeout;
>  };
>  
>  void handle_qmp_handshake(struct Client *client);
> -- 
> 2.30.2
> 
> 
> 
> _______________________________________________
> pve-devel mailing list
> pve-devel at lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
> 
> 
> 





More information about the pve-devel mailing list