--- .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