daemon: Introduce "pulse mode" progress events.

This introduces a new form of progress event, where we don't know how
much of the operation has taken place, but we nevertheless want to
send back some indication of activity.  Some progress bar indicators
directly support this, eg. GtkProgressBar where it is known as "pulse
mode".

A pulse mode progress message is a special backwards-compatible form
of the ordinary progress message.  No change is required in callers,
unless they want to add support for pulse mode.

The daemon sends:

 - zero or more progress messages with position = 0, total = 1
 - a single final progress message with position = total = 1

Note that the final progress message may not be sent if the call fails
and returns an error.  This is consistent with the behaviour of
ordinary progress messages.

The daemon allows two types of implementation.  Either you can just
call notify_progress (0, 1); ...; notify_progress (1, 1) as usual.

Or you can call the functions pulse_mode_start, pulse_mode_end and/or
pulse_mode_cancel (see documentation in daemon/daemon.h).  For this
second form of call, the guarantee is very weak: it *just* says the
daemon is still capable of doing something, and it doesn't imply that
if there is a subprocess that it is doing anything.  However this does
make it very easy to add pulse mode progress messages to all sorts of
existing calls that depend on long-running external commands.

To do: add a third variant that monitors a subprocess and only sends
back progress messages if it's doing something, where "doing
something" might indicate it's using CPU time or it's printing output.
This commit is contained in:
Richard W.M. Jones
2011-04-01 15:50:33 +01:00
parent 6e5f640896
commit 40f7323134
6 changed files with 158 additions and 3 deletions

View File

@@ -1,5 +1,5 @@
# libguestfs-daemon
# Copyright (C) 2009-2010 Red Hat Inc.
# Copyright (C) 2009-2011 Red Hat Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -188,7 +188,9 @@ AC_CHECK_FUNCS([\
posix_fallocate \
realpath \
removexattr \
setitimer \
setxattr \
sigaction \
statvfs \
sync])

View File

@@ -174,6 +174,23 @@ extern void reply (xdrproc_t xdrp, char *ret);
*/
extern void notify_progress (uint64_t position, uint64_t total);
/* Pulse mode progress messages.
*
* Call pulse_mode_start to start sending progress messages.
*
* Call pulse_mode_end along the ordinary exit path (ie. before a
* reply message is sent).
*
* Call pulse_mode_cancel along all error paths *before* any reply is
* sent. pulse_mode_cancel does not modify errno, so it is safe to
* call it before reply_with_perror.
*
* Pulse mode and ordinary notify_progress must not be mixed.
*/
extern void pulse_mode_start (void);
extern void pulse_mode_end (void);
extern void pulse_mode_cancel (void);
/* Helper for functions that need a root filesystem mounted.
* NB. Cannot be used for FileIn functions.
*/

View File

@@ -679,6 +679,7 @@ commandrvf (char **stdoutput, char **stderror, int flags,
}
if (pid == 0) { /* Child process running the command. */
signal (SIGALRM, SIG_DFL);
signal (SIGPIPE, SIG_DFL);
close (0);
if (flag_copy_stdin) {

View File

@@ -22,6 +22,7 @@
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include <inttypes.h>
#include <unistd.h>
#include <errno.h>
@@ -661,3 +662,117 @@ notify_progress (uint64_t position, uint64_t total)
exit (EXIT_FAILURE);
}
}
/* "Pulse mode" progress messages. */
#if defined(HAVE_SETITIMER) && defined(HAVE_SIGACTION)
static void async_safe_send_pulse (int sig);
void
pulse_mode_start (void)
{
struct sigaction act;
struct itimerval it;
memset (&act, 0, sizeof act);
act.sa_handler = async_safe_send_pulse;
act.sa_flags = SA_RESTART;
if (sigaction (SIGALRM, &act, NULL) == -1) {
perror ("pulse_mode_start: sigaction");
return;
}
it.it_value.tv_sec = NOTIFICATION_INITIAL_DELAY / 1000000;
it.it_value.tv_usec = NOTIFICATION_INITIAL_DELAY % 1000000;
it.it_interval.tv_sec = NOTIFICATION_PERIOD / 1000000;
it.it_interval.tv_usec = NOTIFICATION_PERIOD % 1000000;
if (setitimer (ITIMER_REAL, &it, NULL) == -1)
perror ("pulse_mode_start: setitimer");
}
void
pulse_mode_end (void)
{
pulse_mode_cancel (); /* Cancel the itimer. */
notify_progress (1, 1);
}
void
pulse_mode_cancel (void)
{
int err = errno; /* Function must preserve errno. */
struct itimerval it;
struct sigaction act;
/* Setting it_value to zero cancels the itimer. */
it.it_value.tv_sec = 0;
it.it_value.tv_usec = 0;
it.it_interval.tv_sec = 0;
it.it_interval.tv_usec = 0;
if (setitimer (ITIMER_REAL, &it, NULL) == -1)
perror ("pulse_mode_cancel: setitimer");
memset (&act, 0, sizeof act);
act.sa_handler = SIG_DFL;
if (sigaction (SIGALRM, &act, NULL) == -1)
perror ("pulse_mode_cancel: sigaction");
errno = err;
}
/* Send a position = 0, total = 1 (pulse mode) message. The tricky
* part is we have to do it without invoking any non-async-safe
* functions (see signal(7) for a list). Therefore, KISS.
*/
static void
async_safe_send_pulse (int sig)
{
/* XDR is a RFC ... */
unsigned char msg[] = {
(GUESTFS_PROGRESS_FLAG & 0xff000000) >> 24,
(GUESTFS_PROGRESS_FLAG & 0x00ff0000) >> 16,
(GUESTFS_PROGRESS_FLAG & 0x0000ff00) >> 8,
GUESTFS_PROGRESS_FLAG & 0x000000ff,
(proc_nr & 0xff000000) >> 24,
(proc_nr & 0x00ff0000) >> 16,
(proc_nr & 0x0000ff00) >> 8,
proc_nr & 0x000000ff,
(serial & 0xff000000) >> 24,
(serial & 0x00ff0000) >> 16,
(serial & 0x0000ff00) >> 8,
serial & 0x000000ff,
/* 64 bit position = 0 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 64 bit total = 1 */ 0, 0, 0, 0, 0, 0, 0, 1
};
if (xwrite (sock, msg, sizeof msg) == -1)
exit (EXIT_FAILURE);
}
#else /* !HAVE_SETITIMER || !HAVE_SIGACTION */
void
pulse_mode_start (void)
{
/* empty */
}
void
pulse_mode_end (void)
{
/* empty */
}
void
pulse_mode_cancel (void)
{
/* empty */
}
#endif /* !HAVE_SETITIMER || !HAVE_SIGACTION */

View File

@@ -1,5 +1,5 @@
(* libguestfs
* Copyright (C) 2009-2010 Red Hat Inc.
* Copyright (C) 2009-2011 Red Hat Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -214,8 +214,14 @@ struct guestfs_chunk {
* 'position' and 'total' have undefined units; however they may
* have meaning for some calls.
*
* NB. guestfs___recv_from_daemon assumes the XDR-encoded
* Notes:
*
* (1) guestfs___recv_from_daemon assumes the XDR-encoded
* structure is 24 bytes long.
*
* (2) daemon/proto.c:async_safe_send_pulse assumes the progress
* message is laid out precisely in this way. So if you change
* this then you'd better change that function as well.
*/
struct guestfs_progress {
guestfs_procedure proc; /* @0: GUESTFS_PROC_x */

View File

@@ -1786,6 +1786,20 @@ This is to simplify caller code, so callers can easily set the
progress indicator to "100%" at the end of the operation, without
requiring special code to detect this case.
=item *
For some calls we are unable to estimate the progress of the call, but
we can still generate progress messages to indicate activity. This is
known as "pulse mode", and is directly supported by certain progress
bar implementations (eg. GtkProgressBar).
For these calls, zero or more progress messages are generated with
C<position = 0> and C<total = 1>, followed by a final message with
C<position = total = 1>.
As noted above, if the call fails with an error then the final message
may not be generated.
=back
The callback also receives the procedure number (C<proc_nr>) and