[Xcb] [PATCH RFC 1/2] Add some tests for thread hangs

Uli Schlachter psychon at znc.in
Thu Apr 10 07:25:15 PDT 2014


This requires a working X server and thus is more of an integration test than a
unit test. And having threads in unit tests is ugly anyway since they are
supposed to be reproducible.

Thus:

NOT-Signed-Off-by!

(For what the result is: Once the thread_break_connection() got the connection
into an error state, all threads but one exit. The one thread is
thread_wait_for_event() and it is stuck in xcb_wait_for_event())
---
 tests/Makefile.am     |   2 +-
 tests/check_all.c     |   1 +
 tests/check_suites.h  |   1 +
 tests/check_threads.c | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 207 insertions(+), 1 deletion(-)
 create mode 100644 tests/check_threads.c

diff --git a/tests/Makefile.am b/tests/Makefile.am
index ceef722..eb2db92 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -10,7 +10,7 @@ LDADD = @CHECK_LIBS@ $(top_builddir)/src/libxcb.la
 if HAVE_CHECK
 TESTS = check_all
 check_PROGRAMS = check_all
-check_all_SOURCES =  check_all.c check_suites.h check_public.c
+check_all_SOURCES =  check_all.c check_suites.h check_public.c check_threads.c
 
 check-local: check-TESTS
 	$(RM) CheckLog.html
diff --git a/tests/check_all.c b/tests/check_all.c
index 4393422..314e5dc 100644
--- a/tests/check_all.c
+++ b/tests/check_all.c
@@ -12,6 +12,7 @@ int main(void)
 {
 	int nf;
 	SRunner *sr = srunner_create(public_suite());
+	srunner_add_suite(sr, thread_suite());
 	srunner_set_xml(sr, "CheckLog_xcb.xml");
 	srunner_run_all(sr, CK_NORMAL);
 	nf = srunner_ntests_failed(sr);
diff --git a/tests/check_suites.h b/tests/check_suites.h
index 499f1af..f887bc6 100644
--- a/tests/check_suites.h
+++ b/tests/check_suites.h
@@ -2,3 +2,4 @@
 
 void suite_add_test(Suite *s, TFun tf, const char *name);
 Suite *public_suite(void);
+Suite *thread_suite(void);
diff --git a/tests/check_threads.c b/tests/check_threads.c
new file mode 100644
index 0000000..6aaaead
--- /dev/null
+++ b/tests/check_threads.c
@@ -0,0 +1,204 @@
+/* Copyright (C) 2014 Uli Schlachter.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Except as contained in this notice, the names of the authors or their
+ * institutions shall not be used in advertising or otherwise to promote the
+ * sale, use or other dealings in this Software without prior written
+ * authorization from the authors.
+ */
+
+#include <check.h>
+#include <pthread.h>
+#include <string.h>
+#include <stdlib.h>
+#include "check_suites.h"
+#include "xcb.h"
+#include "xcbext.h"
+
+/* An extension definition for an extension that (hopefully) does never exist */
+static xcb_extension_t no_such_extension = {
+	"NO_SUCH_EXTENSION", 0
+};
+
+/* threads {{{ */
+
+static void *thread_wait_for_event(void *arg)
+{
+	xcb_connection_t *conn = arg;
+	xcb_generic_event_t *event;
+
+	while (!xcb_connection_has_error(conn) && (event = xcb_wait_for_event(conn))) {
+		free(event);
+	}
+
+	return NULL;
+}
+
+static void *thread_poll_for_event(void *arg)
+{
+	xcb_connection_t *conn = arg;
+	xcb_generic_event_t *event;
+
+	while (!xcb_connection_has_error(conn) && (event = xcb_poll_for_event(conn))) {
+		free(event);
+	}
+
+	return NULL;
+}
+
+static void *thread_send_noops(void *arg)
+{
+	xcb_connection_t *conn = arg;
+
+	while (!xcb_connection_has_error(conn)) {
+		xcb_no_operation(conn);
+	}
+
+	return NULL;
+}
+
+static void *thread_send_big_noops(void *arg)
+{
+	xcb_connection_t *conn = arg;
+	static const xcb_protocol_request_t xcb_req = {
+		2, /* count */
+		0, /* ext */
+		XCB_NO_OPERATION, /* opcode */
+		1 /* isvoid */
+	};
+	struct iovec xcb_parts[4];
+	xcb_no_operation_request_t xcb_out;
+	/* The idea here is to fill up xcb's output buffer and causing threads
+	 * to wait for another writing thread. We do this by sending a really,
+	 * really big NoOperation request
+	 */
+	size_t request_length = xcb_get_maximum_request_length(conn);
+	char *buffer;
+
+	/* Did things already go downhill? */
+	if (request_length == 0)
+		return NULL;
+
+	/* Round request length down to a multiple of 4 */
+	request_length = (request_length - sizeof(xcb_out)) & ~0x3;
+
+	buffer = malloc(request_length);
+	ck_assert_ptr_ne(buffer, NULL);
+	memset(buffer, 0, request_length);
+	xcb_out.pad0 = 0;
+
+	while (!xcb_connection_has_error(conn)) {
+		xcb_parts[2].iov_base = &xcb_out;
+		xcb_parts[2].iov_len = sizeof(xcb_out);
+		xcb_parts[3].iov_base = buffer;
+		xcb_parts[3].iov_len = request_length;
+
+		xcb_send_request(conn, 0, &xcb_parts[2], &xcb_req);
+	}
+
+	free(buffer);
+
+	return NULL;
+}
+
+static void *thread_sync(void *arg)
+{
+	xcb_connection_t *conn = arg;
+
+	while (!xcb_connection_has_error(conn)) {
+		free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL));
+	}
+
+	return NULL;
+}
+
+static void *thread_break_connection(void *arg)
+{
+	int i;
+	struct iovec parts[3];
+	xcb_connection_t *conn = arg;
+	xcb_get_input_focus_request_t request = { 0 };
+	const xcb_protocol_request_t xcb_req = {
+		1, /* count */
+		&no_such_extension, /* extension */
+		42, /* opcode */
+		0 /* isvoid */
+	};
+
+	/* First wait a bit */
+	for (i = 0; i < 10; i++)
+		free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL));
+
+	/* Then break the connection by using an extension that doesn't exist */
+	parts[2].iov_base = &request;
+	parts[2].iov_len = sizeof(request);
+
+	xcb_send_request(conn, 0, &parts[2], &xcb_req);
+
+	return NULL;
+}
+
+/* }}} */
+
+static xcb_connection_t *conn = NULL;
+
+void connect_display(void)
+{
+	putenv("DISPLAY=:0"); /* XXX: Really, really ugly */
+	conn = xcb_connect(NULL, NULL);
+	ck_assert_int_eq(xcb_connection_has_error(conn), 0);
+}
+
+void disconnect_display(void)
+{
+	xcb_disconnect(conn);
+	conn = NULL;
+}
+
+START_TEST(threads)
+{
+	pthread_t threads[6];
+
+	ck_assert_int_eq(pthread_create(&threads[0], NULL, thread_wait_for_event, conn), 0);
+	ck_assert_int_eq(pthread_create(&threads[1], NULL, thread_poll_for_event, conn), 0);
+	ck_assert_int_eq(pthread_create(&threads[2], NULL, thread_send_noops, conn), 0);
+	ck_assert_int_eq(pthread_create(&threads[3], NULL, thread_send_big_noops, conn), 0);
+	ck_assert_int_eq(pthread_create(&threads[4], NULL, thread_sync, conn), 0);
+	ck_assert_int_eq(pthread_create(&threads[5], NULL, thread_break_connection, conn), 0);
+
+	mark_point();
+
+	ck_assert_int_eq(pthread_join(threads[0], NULL), 0);
+	ck_assert_int_eq(pthread_join(threads[1], NULL), 0);
+	ck_assert_int_eq(pthread_join(threads[2], NULL), 0);
+	ck_assert_int_eq(pthread_join(threads[3], NULL), 0);
+	ck_assert_int_eq(pthread_join(threads[4], NULL), 0);
+	ck_assert_int_eq(pthread_join(threads[5], NULL), 0);
+}
+END_TEST
+
+Suite *thread_suite(void)
+{
+	Suite *s = suite_create("Thread safety tests");
+	TCase *tc = tcase_create("Some thread magic");
+	tcase_add_checked_fixture(tc, connect_display, disconnect_display);
+	tcase_add_test(tc, threads);
+	suite_add_tcase(s, tc);
+	return s;
+}
-- 
1.9.1



More information about the Xcb mailing list