---
.gitignore | 1 +
tests/c-api/Makefile.am | 21 ++++++-
tests/c-api/test-threads.c | 133 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 153 insertions(+), 2 deletions(-)
diff --git a/.gitignore b/.gitignore
index bbd9284c6..5b936239c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -562,6 +562,7 @@ Makefile.in
/tests/c-api/test-pwd
/tests/c-api/tests
/tests/c-api/tests.c
+/tests/c-api/test-threads
/tests/c-api/test*.tmp
/tests/c-api/test-user-cancel
/tests/charsets/test-charset-fidelity
diff --git a/tests/c-api/Makefile.am b/tests/c-api/Makefile.am
index d79b8ed52..e80f88fa0 100644
--- a/tests/c-api/Makefile.am
+++ b/tests/c-api/Makefile.am
@@ -41,7 +41,8 @@ check_PROGRAMS = \
test-debug-to-file \
test-environment \
test-pwd \
- test-event-string
+ test-event-string \
+ test-threads
if HAVE_LIBDL
check_PROGRAMS += \
test-dlopen
@@ -59,7 +60,8 @@ TESTS = \
test-user-cancel \
test-debug-to-file \
test-environment \
- test-event-string
+ test-event-string \
+ test-threads
if HAVE_LIBDL
TESTS += \
test-dlopen
@@ -265,6 +267,21 @@ test_event_string_LDADD = \
$(LTLIBINTL) \
$(top_builddir)/gnulib/lib/libgnu.la
+test_threads_SOURCES = test-threads.c
+test_threads_CPPFLAGS = \
+ -I$(top_srcdir)/common/utils -I$(top_builddir)/common/utils \
+ -I$(top_srcdir)/lib -I$(top_builddir)/lib \
+ -I$(top_srcdir)/gnulib/lib \
+ -I$(top_builddir)/gnulib/lib
+test_threads_CFLAGS = \
+ -pthread \
+ $(WARN_CFLAGS) $(WERROR_CFLAGS)
+test_threads_LDADD = \
+ $(top_builddir)/lib/libguestfs.la \
+ $(LTLIBTHREAD) \
+ $(LTLIBINTL) \
+ $(top_builddir)/gnulib/lib/libgnu.la
+
if HAVE_LIBVIRT
test_add_libvirt_dom_SOURCES = test-add-libvirt-dom.c
test_add_libvirt_dom_CPPFLAGS = \
diff --git a/tests/c-api/test-threads.c b/tests/c-api/test-threads.c
new file mode 100644
index 000000000..673b4bbff
--- /dev/null
+++ b/tests/c-api/test-threads.c
@@ -0,0 +1,133 @@
+/* libguestfs
+ * Copyright (C) 2015-2017 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* Test that we can make API calls safely from multiple threads. */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+
+#include <pthread.h>
+
+#include "guestfs.h"
+#include "guestfs-utils.h"
+
+static guestfs_h *g;
+
+#define RUN_TIME 60 /* seconds */
+#define NR_CONCURRENT_THREADS 4
+
+static void *start_thread (void *nullv);
+
+int
+main (int argc, char *argv[])
+{
+ time_t start_t, t;
+ pthread_t threads[NR_CONCURRENT_THREADS];
+ void *ret;
+ int i, r;
+
+ /* Because we rely on error message content below, force LC_ALL=C. */
+ setenv ("LC_ALL", "C", 1);
+
+ g = guestfs_create ();
+ if (!g) {
+ perror ("guestfs_create");
+ exit (EXIT_FAILURE);
+ }
+
+ time (&start_t);
+
+ while (time (&t), t - start_t < RUN_TIME) {
+ for (i = 0; i < NR_CONCURRENT_THREADS; ++i) {
+ r = pthread_create (&threads[i], NULL, start_thread, NULL);
+ if (r != 0) {
+ fprintf (stderr, "pthread_create: %s\n", strerror (r));
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ for (i = 0; i < NR_CONCURRENT_THREADS; ++i) {
+ r = pthread_join (threads[i], &ret);
+ if (r != 0) {
+ fprintf (stderr, "pthread_join: %s\n", strerror (r));
+ exit (EXIT_FAILURE);
+ }
+ if (ret != NULL) {
+ fprintf (stderr, "thread[%d] failed\n", i);
+ exit (EXIT_FAILURE);
+ }
+ }
+ }
+
+ guestfs_close (g);
+
+ exit (EXIT_SUCCESS);
+}
+
+static void *
+start_thread (void *nullv)
+{
+ char *p;
+ const char *err;
+ int iterations;
+
+ for (iterations = 0; iterations < 1000; ++iterations) {
+ guestfs_set_hv (g, "test");
+ p = guestfs_get_hv (g);
+ if (!p || STRNEQ (p, "test")) {
+ fprintf (stderr, "invalid return from guestfs_get_hv\n");
+ pthread_exit ((void *)-1);
+ }
+ free (p);
+
+ guestfs_push_error_handler (g, NULL, NULL);
+ guestfs_set_hv (g, "test");
+ p = guestfs_get_hv (g);
+ guestfs_pop_error_handler (g);
+ if (!p || STRNEQ (p, "test")) {
+ fprintf (stderr, "invalid return from guestfs_get_hv\n");
+ pthread_exit ((void *)-1);
+ }
+ free (p);
+
+ guestfs_push_error_handler (g, NULL, NULL);
+ guestfs_set_program (g, NULL); /* deliberately cause an error */
+ guestfs_pop_error_handler (g);
+ err = guestfs_last_error (g);
+ if (!err || !STRPREFIX (err, "set_program: program: ")) {
+ fprintf (stderr, "invalid error message: %s\n", err ? err :
"NULL");
+ pthread_exit ((void *)-1);
+ }
+
+ guestfs_push_error_handler (g, NULL, NULL);
+ guestfs_set_memsize (g, 1); /* deliberately cause an error */
+ guestfs_pop_error_handler (g);
+ err = guestfs_last_error (g);
+ if (!err || strstr (err, "memsize") == NULL) {
+ fprintf (stderr, "invalid error message: %s\n", err ? err :
"NULL");
+ pthread_exit ((void *)-1);
+ }
+ }
+
+ pthread_exit (NULL);
+}
--
2.13.2