From: "Richard W.M. Jones" <rjones(a)redhat.com>
These limit the number of concurrent handles which can be used
at once.
---
fish/guestfish.pod | 8 ++
generator/actions.ml | 87 +++++++++++++++++
generator/events.ml | 4 +
ocaml/t/guestfs_400_events.ml | 5 +-
po/POTFILES | 1 +
src/Makefile.am | 1 +
src/guestfs-internal.h | 11 +++
src/guestfs.pod | 120 ++++++++++++++++++++++++
src/handle.c | 20 +++-
src/launch.c | 4 +
src/mutex.c | 213 ++++++++++++++++++++++++++++++++++++++++++
src/proto.c | 1 +
test-tool/test-tool.c | 3 +
13 files changed, 476 insertions(+), 2 deletions(-)
create mode 100644 src/mutex.c
diff --git a/fish/guestfish.pod b/fish/guestfish.pod
index 5fec2f2..28dde6c 100644
--- a/fish/guestfish.pod
+++ b/fish/guestfish.pod
@@ -1279,6 +1279,14 @@ example:
LIBGUESTFS_MEMSIZE=700
+=item LIBGUESTFS_MUTEX_FILE
+
+=item LIBGUESTFS_MUTEX_LIMIT
+
+Set these in order to limit the number of concurrent guestfish
+sessions launched at once. See L<guestfs(3)/MUTEX> and
+L</set-mutex-limit>.
+
=item LIBGUESTFS_PATH
Set the path that guestfish uses to search for kernel and initrd.img.
diff --git a/generator/actions.ml b/generator/actions.ml
index 4f18f41..d664a7a 100644
--- a/generator/actions.ml
+++ b/generator/actions.ml
@@ -2694,6 +2694,93 @@ the default. Else C</var/tmp> is the default." };
longdesc = "\
Get the directory used by the handle to store the appliance cache." };
+ { defaults with
+ name = "set_mutex_file";
+ style = RErr, [OptString "mutexfile"], [];
+ config_only = true; blocking = false;
+ shortdesc = "set mutex filename to limit concurrent handles";
+ longdesc = "\
+Set the name of the semaphore file used to limit concurrent handles.
+The name must start with a C</> character, and contain no other
+C</> characters (eg. C</guestfs.mutex>). See L<sem_overview(7)>
+for an explanation.
+
+See L<guestfs(3)/MUTEX>." };
+
+ { defaults with
+ name = "get_mutex_file";
+ style = RConstOptString "mutexfile", [], [];
+ blocking = false;
+ shortdesc = "get mutex filename";
+ longdesc = "\
+Get the name of the semaphore file.
+
+See C<guestfs_set_mutex_file> and L<guestfs(3)/MUTEX>." };
+
+ { defaults with
+ name = "set_mutex_limit";
+ style = RErr, [Int "limit"], [];
+ config_only = true; blocking = false;
+ shortdesc = "set mutex to limit concurrent handles";
+ longdesc = "\
+This sets the limit of the number of concurrent handles that
+may be launched at the same time. The intention is to limit
+the overall load that libguestfs places on the host.
+
+Note that in the current implementation which uses a POSIX
+semaphore, you have to delete the semaphore file (or reboot the
+host) in order to change this limit. See L<sem_overview(7)> for
+the location of the semaphore file. This may be fixed in future.
+
+As well as ordinary positive integers, meaning to limit
+the number of launched handles to C<limit>, various magic
+values are possible too:
+
+=over 4
+
+=item C<limit> E<ge> 1
+
+Limit the number of launched handles to at most C<limit> concurrently.
+
+=item C<0>
+
+Disable limits.
+
+=item C<-2>
+
+Let libguestfs choose a suitable limit based on the free memory
+available on the host when the semaphore is first created.
+
+If there is less memory available than libguestfs thinks is necessary,
+libguestfs will still allow one handle to run (otherwise that handle
+would be waiting forever).
+
+This is probably the best choice for most users.
+
+=item C<limit> E<le> -128
+
+Calculate the maximum number of concurrent handles by dividing
+the free memory by C<-limit>. This calculation is done when the
+semaphore is first created.
+
+Libguestfs will always allow at least one handle to run
+(otherwise that handle would be waiting forever).
+
+=back
+
+For more information see L<guestfs(3)/MUTEX>." };
+
+ { defaults with
+ name = "get_mutex_limit";
+ style = RInt "limit", [], [];
+ blocking = false;
+ shortdesc = "get mutex limit";
+ longdesc = "\
+This returns the mutex limit. If not previously set, the
+default is C<0> (meaning the mutex feature is disabled).
+
+See C<guestfs_set_mutex_limit> and L<guestfs(3)/MUTEX>." };
+
]
(* daemon_functions are any functions which cause some action
diff --git a/generator/events.ml b/generator/events.ml
index 58c0c54..9c69705 100644
--- a/generator/events.ml
+++ b/generator/events.ml
@@ -39,6 +39,10 @@ let events = [
"enter"; (* enter a function *)
"libvirt_auth"; (* libvirt authentication request *)
+
+ "mutex_blocked"; (* mutex events *)
+ "mutex_entered";
+ "mutex_released";
]
let events = mapi (fun i name -> name, 1 lsl i) events
diff --git a/ocaml/t/guestfs_400_events.ml b/ocaml/t/guestfs_400_events.ml
index be40608..76d27a5 100644
--- a/ocaml/t/guestfs_400_events.ml
+++ b/ocaml/t/guestfs_400_events.ml
@@ -29,7 +29,10 @@ let log g ev eh buf array =
| Guestfs.EVENT_LIBRARY -> "library"
| Guestfs.EVENT_TRACE -> "trace"
| Guestfs.EVENT_ENTER -> "enter"
- | Guestfs.EVENT_LIBVIRT_AUTH -> "libvirt_auth" in
+ | Guestfs.EVENT_LIBVIRT_AUTH -> "libvirt_auth"
+ | Guestfs.EVENT_MUTEX_BLOCKED -> "mutex_blocked"
+ | Guestfs.EVENT_MUTEX_ENTERED -> "mutex_entered"
+ | Guestfs.EVENT_MUTEX_RELEASED -> "mutex_released" in
let eh : int = Obj.magic eh in
diff --git a/po/POTFILES b/po/POTFILES
index 0686966..e3430f2 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -263,6 +263,7 @@ src/libvirt-domain.c
src/listfs.c
src/lpj.c
src/match.c
+src/mutex.c
src/osinfo.c
src/private-data.c
src/proto.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 2c3af05..3410803 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -171,6 +171,7 @@ libguestfs_la_SOURCES = \
listfs.c \
lpj.c \
match.c \
+ mutex.c \
osinfo.c \
private-data.c \
proto.c \
diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h
index 48ab745..00c029e 100644
--- a/src/guestfs-internal.h
+++ b/src/guestfs-internal.h
@@ -23,6 +23,8 @@
#include <libintl.h>
+#include <semaphore.h>
+
#include <rpc/types.h>
#include <rpc/xdr.h>
@@ -292,6 +294,11 @@ struct guestfs_h
virConnectCredentialPtr requested_credentials;
#endif
+ /* Used by the handle mutex (see src/mutex.c). */
+ char *mutex_file;
+ int mutex_limit;
+ sem_t *semaphore;
+
/**** Private data for attach-methods. ****/
/* NB: This cannot be a union because of a pathological case where
* the user changes attach-method while reusing the handle to launch
@@ -519,6 +526,10 @@ extern char *guestfs___appliance_command_line (guestfs_h *g, const
char *applian
/* launch-appliance.c */
extern char *guestfs___drive_name (size_t index, char *ret);
+/* mutex.c */
+extern int guestfs___acquire_mutex (guestfs_h *g);
+extern void guestfs___release_mutex (guestfs_h *g);
+
/* inspect.c */
extern void guestfs___free_inspect_info (guestfs_h *g);
extern int guestfs___feature_available (guestfs_h *g, const char *feature);
diff --git a/src/guestfs.pod b/src/guestfs.pod
index d21ac8c..bec1249 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -718,6 +718,100 @@ available from the L</guestfs_inspect_is_live>,
L</guestfs_inspect_is_netinst> and L</guestfs_inspect_is_multipart>
calls.
+=head2 MUTEX
+
+You can limit the number of launched handles (per host) to avoid
+having too many libguestfs instances running at once. This is known
+as the handle "mutex". This works by having a shared semaphore. If
+the limit is reached, handles wait on this semaphore until other
+instances finish.
+
+The default is no lock file and no limit on the number of concurrent
+handles.
+
+=head3 SETTING THE MUTEX LOCK FILE AND LIMIT
+
+To use this feature, after you open each handle but before calling
+L</guestfs_launch>, set the semaphore filename and limit by calling
+L</guestfs_set_mutex_file> and L</guestfs_set_mutex_limit>.
+
+The semaphore filename is a special type of filename that starts with
+a C</> character and contains no other C</> characters
+(eg. C</guestfs.mutex>). See L<sem_overview(7)> for an explanation.
+
+The same filename should be passed to every handle.
+
+The limit is simply an integer (the number of handles). The limit can
+have various magic values too, read the documentation for
+L</guestfs_set_mutex_limit>.
+
+Alternatively, ensure that the following environment variables are set
+globally for every libguestfs user / process: C<LIBGUESTFS_MUTEX_FILE>
+and C<LIBGUESTFS_MUTEX_LIMIT>.
+
+The example below shows how to use the environment variables. The
+special limit of C<-2> causes libguestfs to choose a suitable limit
+based on the amount of free memory on the host:
+
+ LIBGUESTFS_MUTEX_FILE=/guestfs.mutex
+ LIBGUESTFS_MUTEX_LIMIT=-2
+ export LIBGUESTFS_MUTEX_FILE LIBGUESTFS_MUTEX_LIMIT
+
+=head3 MUTEX CHANGES TO LAUNCH AND CLOSE
+
+When the mutex has been configured, L</guestfs_launch> may block if
+there are too many other handles running at the same time. Similarly,
+L</guestfs_shutdown> or L</guestfs_close> may release the lock causing
+another handle that was waiting to start launching.
+
+You can see if handles are blocked by enabling debugging or by
+registering for events (see next section).
+
+=head3 MUTEX EVENTS
+
+The API allows callers to receive an event when a handle is blocked
+acquiring the lock, has acquired the lock, or has released the lock.
+Events only let you monitor this; they don't let you change the mutex
+behaviour on the fly.
+
+The three events are C<GUESTFS_EVENT_MUTEX_BLOCKED> (blocked when
+trying to acquire the mutex), C<GUESTFS_EVENT_MUTEX_ENTERED>
+(successfully acquired the mutex), and C<GUESTFS_EVENT_MUTEX_RELEASED>
+(have just released the mutex). All events have a string payload
+which is the name of the semaphore file (ie. the same string passed to
+L</guestfs_set_mutex_file>).
+
+Note that the C<BLOCKED> event is only generated if the handle
+actually waits, not if the handle goes straight into the mutex.
+
+If mutexes are not configured, then none of these events will be
+generated.
+
+For further information about events, see L</EVENTS>.
+
+=head3 MUTEX IMPLEMENTATION NOTES
+
+Currently the mutex is implemented using POSIX semaphores (this
+implementation detail may change in future).
+
+The mutex is only enabled if the mutex semaphore file is set and the
+limit is set to a non-zero value.
+
+The mutex only affects when handles can be launched. You can still
+create as many handles as you like, which should be safe because
+handles that are not launched just use a little memory in the current
+process.
+
+The mutex must be enabled on all handles. If you don't enable it on a
+particular handle, then that handle will ignore the mutex (even if it
+is set on other handles).
+
+Currently, if you want to change the limit then you have to delete the
+semaphore file.
+
+For the magic negative values of the limit, libguestfs uses the output
+of the L<free(1)> command to determine free memory on the host.
+
=head2 SPECIAL CONSIDERATIONS FOR WINDOWS GUESTS
Libguestfs can mount NTFS partitions. It does this using the
@@ -2486,6 +2580,24 @@ authentication information. See L</LIBVIRT AUTHENTICATION>
below.
If no callback is registered: C<virConnectAuthPtrDefault> is
used (suitable for command-line programs only).
+=item GUESTFS_EVENT_MUTEX_BLOCKED
+(payload type: lock file name)
+
+During launch, the handle is blocked waiting for the mutex.
+See L</MUTEX>.
+
+=item GUESTFS_EVENT_MUTEX_ENTERED
+(payload type: lock file name)
+
+During launch, the handle acquired the mutex lock.
+See L</MUTEX>.
+
+=item GUESTFS_EVENT_MUTEX_RELEASED
+(payload type: lock file name)
+
+During shutdown, the handle released the mutex lock.
+See L</MUTEX>.
+
=back
=head2 EVENT API
@@ -4131,6 +4243,14 @@ example:
LIBGUESTFS_MEMSIZE=700
+=item LIBGUESTFS_MUTEX_FILE
+
+=item LIBGUESTFS_MUTEX_LIMIT
+
+Set these in order to limit the number of concurrent libguestfs
+handles running at once. See L</MUTEX> and
+L</guestfs_set_mutex_limit>.
+
=item LIBGUESTFS_PATH
Set the path that libguestfs uses to search for a supermin appliance.
diff --git a/src/handle.c b/src/handle.c
index c630daf..6c9b311 100644
--- a/src/handle.c
+++ b/src/handle.c
@@ -144,6 +144,7 @@ guestfs_create_flags (unsigned flags, ...)
return g;
error:
+ free (g->mutex_file);
free (g->attach_method_arg);
free (g->path);
free (g->qemu);
@@ -157,7 +158,7 @@ parse_environment (guestfs_h *g,
char *(*do_getenv) (const void *data, const char *),
const void *data)
{
- int memsize;
+ int memsize, limit;
char *str;
/* Don't bother checking the return values of functions
@@ -215,6 +216,20 @@ parse_environment (guestfs_h *g,
return -1;
}
+ str = do_getenv (data, "LIBGUESTFS_MUTEX_FILE");
+ if (str)
+ guestfs_set_mutex_file (g, str);
+
+ str = do_getenv (data, "LIBGUESTFS_MUTEX_LIMIT");
+ if (str) {
+ if (sscanf (str, "%d", &limit) != 1) {
+ error (g, _("non-numeric value for LIBGUESTFS_MUTEX_LIMIT"));
+ return -1;
+ }
+ if (guestfs_set_mutex_limit (g, limit) == -1)
+ return -1;
+ }
+
return 0;
}
@@ -326,6 +341,7 @@ guestfs_close (guestfs_h *g)
if (g->pda)
hash_free (g->pda);
+ free (g->mutex_file);
free (g->tmpdir);
free (g->env_tmpdir);
free (g->int_tmpdir);
@@ -378,6 +394,8 @@ shutdown_backend (guestfs_h *g, int check_for_errors)
if (g->attach_ops->shutdown (g, check_for_errors) == -1)
ret = -1;
+ guestfs___release_mutex (g);
+
guestfs___free_drives (g);
g->state = CONFIG;
diff --git a/src/launch.c b/src/launch.c
index 7c37667..0401c42 100644
--- a/src/launch.c
+++ b/src/launch.c
@@ -597,6 +597,10 @@ guestfs__launch (guestfs_h *g)
return -1;
}
+ /* Acquire mutex if we need to. */
+ if (guestfs___acquire_mutex (g) == -1)
+ return -1;
+
/* Start the clock ... */
gettimeofday (&g->launch_t, NULL);
TRACE0 (launch_start);
diff --git a/src/mutex.c b/src/mutex.c
new file mode 100644
index 0000000..82efc9b
--- /dev/null
+++ b/src/mutex.c
@@ -0,0 +1,213 @@
+/* libguestfs
+ * Copyright (C) 2013 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <semaphore.h>
+#include <errno.h>
+
+#include "guestfs.h"
+#include "guestfs-internal.h"
+#include "guestfs-internal-actions.h"
+#include "guestfs_protocol.h"
+
+int
+guestfs__set_mutex_file (guestfs_h *g, const char *file)
+{
+ free (g->mutex_file);
+ g->mutex_file = NULL;
+
+ if (file)
+ g->mutex_file = safe_strdup (g, file);
+
+ return 0;
+}
+
+const char *
+guestfs__get_mutex_file (guestfs_h *g)
+{
+ return g->mutex_file;
+}
+
+int
+guestfs__set_mutex_limit (guestfs_h *g, int limit)
+{
+ if (limit > 1000) { /* that would be > 1000 concurrent handles */
+ error (g, _("mutex limit is too large. If you want it to be unlimited set it to
0. If you want a larger-but-finite limit then you have to recompile libguestfs."));
+ return -1;
+ }
+ if (limit == -1) {
+ error (g, _("mutex limit cannot be set to -1 (did you mean -2?)"));
+ return -1;
+ }
+ if (limit >= -127 && limit <= -3) {
+ error (g, _("mutex limit cannot be set to -3..-127"));
+ return -1;
+ }
+ if (limit < -10000) { /* that would be a > 10 GB process limit */
+ error (g, _("mutex limit is too small"));
+ return -1;
+ }
+
+ g->mutex_limit = limit;
+ return 0;
+}
+
+int
+guestfs__get_mutex_limit (guestfs_h *g)
+{
+ return g->mutex_limit;
+}
+
+static int
+mutex_is_enabled (guestfs_h *g)
+{
+ return g->mutex_file != NULL && g->mutex_limit != 0;
+}
+
+static void
+read_free_memory (guestfs_h *g, void *datav, const char *line, size_t len)
+{
+ int *mbytesp = (int *) datav;
+
+ if (sscanf (line, "%d", mbytesp) == -1)
+ *mbytesp = -1;
+}
+
+static int
+get_free_memory (guestfs_h *g)
+{
+ CLEANUP_CMD_CLOSE struct command *cmd = guestfs___new_command (g);
+ int r, mbytes = -1;
+
+ guestfs___cmd_add_string_unquoted (cmd, "free -m | "
+ "grep 'buffers/cache' | "
+ "awk '{print $NF}'");
+ guestfs___cmd_set_stdout_callback (cmd, read_free_memory, &mbytes, 0);
+ r = guestfs___cmd_run (cmd);
+ if (r == -1)
+ return -1;
+ if (!WIFEXITED (r) || WEXITSTATUS (r) != 0) {
+ error (g, _("failed when trying to read free memory"));
+ return -1;
+ }
+
+ if (mbytes == -1) {
+ error (g, _("unexpected output from 'free -m' command"));
+ return -1;
+ }
+
+ debug (g, "get_free_memory: %d MB free", mbytes);
+
+ return mbytes;
+}
+
+static int
+get_initial_value (guestfs_h *g)
+{
+ int free_mbytes;
+
+ if (g->mutex_limit >= 1)
+ return g->mutex_limit;
+
+ free_mbytes = get_free_memory (g);
+ if (free_mbytes == -1)
+ return -1;
+
+ if (g->mutex_limit == -2) {
+ /* This is an overestimate ... */
+ int estimate_size_per_appliance = DEFAULT_MEMSIZE + 350;
+ return MAX (1, free_mbytes / estimate_size_per_appliance);
+ }
+
+ return MAX (1, free_mbytes / -g->mutex_limit);
+}
+
+/* This is called very early in 'launch' to acquire the mutex. */
+int
+guestfs___acquire_mutex (guestfs_h *g)
+{
+ sem_t *semaphore;
+ int v;
+
+ if (!mutex_is_enabled (g))
+ return 0;
+
+ v = get_initial_value (g);
+ if (v == -1)
+ return -1;
+
+ debug (g, "mutex: initializing semaphore %s with value %d (note that if the
semaphore exists already, then this value is ignored)", g->mutex_file, v);
+
+ /* XXX umask */
+ semaphore = sem_open (g->mutex_file, O_CREAT|O_CLOEXEC, 0777, (unsigned) v);
+ if (semaphore == SEM_FAILED) {
+ perrorf (g, _("mutex: sem_open: %s"), g->mutex_file);
+ return -1;
+ }
+
+ if (sem_trywait (semaphore) == -1) {
+ if (errno == EAGAIN) {
+ debug (g, _("mutex: blocked waiting for semaphore %s "
+ "(see \"MUTEX\" in guestfs(3))"),
+ g->mutex_file);
+
+ guestfs___call_callbacks_message (g, GUESTFS_EVENT_MUTEX_BLOCKED,
+ g->mutex_file, strlen (g->mutex_file));
+
+ if (sem_wait (semaphore) == -1)
+ goto error;
+ }
+ else {
+ error:
+ perrorf (g, _("mutex: sem_wait: %s"), g->mutex_file);
+ sem_close (semaphore);
+ return -1;
+ }
+ }
+
+ g->semaphore = semaphore;
+
+ guestfs___call_callbacks_message (g, GUESTFS_EVENT_MUTEX_ENTERED,
+ g->mutex_file, strlen (g->mutex_file));
+
+ return 0;
+}
+
+/* This is called late in 'shutdown' to release the mutex. */
+void
+guestfs___release_mutex (guestfs_h *g)
+{
+ if (!g->semaphore)
+ return;
+
+ if (sem_post (g->semaphore) == -1)
+ debug (g, "sem_post: %s", g->mutex_file);
+ if (sem_close (g->semaphore) == -1)
+ debug (g, "sem_close: %s", g->mutex_file);
+ g->semaphore = NULL;
+
+ guestfs___call_callbacks_message (g, GUESTFS_EVENT_MUTEX_RELEASED,
+ g->mutex_file, strlen (g->mutex_file));
+}
diff --git a/src/proto.c b/src/proto.c
index 2e3b480..f909f6e 100644
--- a/src/proto.c
+++ b/src/proto.c
@@ -168,6 +168,7 @@ child_cleanup (guestfs_h *g)
g->fd[1] = -1;
g->sock = -1;
memset (&g->launch_t, 0, sizeof g->launch_t);
+ guestfs___release_mutex (g);
guestfs___free_drives (g);
g->state = CONFIG;
guestfs___call_callbacks_void (g, GUESTFS_EVENT_SUBPROCESS_QUIT);
diff --git a/test-tool/test-tool.c b/test-tool/test-tool.c
index d71caed..621f625 100644
--- a/test-tool/test-tool.c
+++ b/test-tool/test-tool.c
@@ -248,6 +248,9 @@ main (int argc, char *argv[])
free (p);
printf ("guestfs_get_direct: %d\n", guestfs_get_direct (g));
printf ("guestfs_get_memsize: %d\n", guestfs_get_memsize (g));
+ printf ("guestfs_get_mutex_file: %s\n",
+ guestfs_get_mutex_file (g) ? : "(null)");
+ printf ("guestfs_get_mutex_limit: %d\n", guestfs_get_mutex_limit (g));
printf ("guestfs_get_network: %d\n", guestfs_get_network (g));
printf ("guestfs_get_path: %s\n", guestfs_get_path (g) ? :
"(null)");
printf ("guestfs_get_pgroup: %d\n", guestfs_get_pgroup (g));
--
1.8.1.2