commit fead9669cb67badb22789d3ed1888779ed85c679 Author: Tomas Halman Date: Thu Mar 9 11:34:44 2023 +0100 Test that jq works in threads This patch implements test that searches a key in simple json in pthread. diff -up jq-1.6/src/jq_test.c.orig jq-1.6/src/jq_test.c --- jq-1.6/src/jq_test.c.orig 2023-03-09 14:02:16.980062057 +0100 +++ jq-1.6/src/jq_test.c 2023-03-09 14:03:38.347017032 +0100 @@ -2,12 +2,17 @@ #include #include #include +#ifdef HAVE_PTHREAD +#include +#endif #include "jv.h" #include "jq.h" static void jv_test(); static void run_jq_tests(jv, int, FILE *, int, int); - +#ifdef HAVE_PTHREAD +static void run_jq_pthread_tests(); +#endif int jq_testsuite(jv libdirs, int verbose, int argc, char* argv[]) { FILE *testdata = stdin; @@ -32,6 +37,9 @@ int jq_testsuite(jv libdirs, int verbose } } run_jq_tests(libdirs, verbose, testdata, skip, take); +#ifdef HAVE_PTHREAD + run_jq_pthread_tests(); +#endif return 0; } @@ -105,7 +113,7 @@ static void run_jq_tests(jv lib_dirs, in if (buf[0] == '\n' || (buf[0] == '\r' && buf[1] == '\n')) break; } - + must_fail = 0; check_msg = 0; @@ -229,7 +237,7 @@ static void run_jq_tests(jv lib_dirs, in total_skipped = tests_to_skip - skip; } - printf("%d of %d tests passed (%d malformed, %d skipped)\n", + printf("%d of %d tests passed (%d malformed, %d skipped)\n", passed, tests, invalid, total_skipped); if (skip > 0) { @@ -241,6 +249,88 @@ static void run_jq_tests(jv lib_dirs, in } +/// pthread regression test +#ifdef HAVE_PTHREAD +#define NUMBER_OF_THREADS 3 +struct test_pthread_data { + int result; +}; + +static int test_pthread_jq_parse(jq_state *jq, struct jv_parser *parser) +{ + int rv = 0; + jv value; + + value = jv_parser_next(parser); + while (jv_is_valid(value)) { + jq_start(jq, value, 0); + jv result = jq_next(jq); + + while (jv_is_valid(result)) { + jv_free(result); + result = jq_next(jq); + } + jv_free(result); + value = jv_parser_next(parser); + } + jv_free(value); + return rv; +} + +static void *test_pthread_run(void *ptr) { + int rv; + jq_state *jq; + const char *prg = ".data"; + const char *buf = "{ \"data\": 1 }"; + struct test_pthread_data *data = ptr; + + jq = jq_init(); + if (jq_compile(jq, prg) == 0) { + jq_teardown(&jq); + return NULL; + } + + struct jv_parser *parser = jv_parser_new(0); + jv_parser_set_buf(parser, buf, strlen(buf), 0); + rv = test_pthread_jq_parse(jq, parser); + + data->result = rv; + + jv_parser_free(parser); + jq_teardown(&jq); + return NULL; +} + +static void run_jq_pthread_tests() { + pthread_t threads[NUMBER_OF_THREADS]; + struct test_pthread_data data[NUMBER_OF_THREADS]; + int createerror; + int a; + + memset(&threads, 0, sizeof(threads)); + memset(&data, 0, sizeof(data)); + + // Create all threads + for (a = 0; a < NUMBER_OF_THREADS; ++a) { + createerror = pthread_create(&threads[a], NULL, test_pthread_run, &data[a]); + assert(createerror == 0); + } + + // wait for all threads + for(a = 0; a < NUMBER_OF_THREADS; ++a) { + if (threads[a] != 0) { + pthread_join(threads[a], NULL); + } + } + + // check results + for(a = 0; a < NUMBER_OF_THREADS; ++a) { + assert(data[a].result == 0); + } +} +#endif // HAVE_PTHREAD + + static void jv_test() { /// JSON parser regression tests { commit f05c5bb521f404592b137e02a1b8abd50da87ca3 Author: Tomas Halman Date: Wed Mar 1 20:07:28 2023 +0100 Fix segmentation fault when using jq in threads In previous code nomem_handler in jv_alloc.c is called only once and therefore the structure is not initialized for second and following threads. This leads to segmentation fault in multi-threading environment. This patch moves initialization of nomem_handler out of the pthread_once call. diff -up jq-1.6/src/jv_alloc.c.orig jq-1.6/src/jv_alloc.c --- jq-1.6/src/jv_alloc.c.orig 2018-11-02 02:49:29.000000000 +0100 +++ jq-1.6/src/jv_alloc.c 2023-03-09 14:13:47.810177037 +0100 @@ -45,9 +45,11 @@ static void memory_exhausted() { #ifdef HAVE_PTHREAD_KEY_CREATE #include -pthread_key_t nomem_handler_key; -pthread_once_t mem_once = PTHREAD_ONCE_INIT; +static pthread_key_t nomem_handler_key; +static pthread_once_t mem_once = PTHREAD_ONCE_INIT; +/* tsd_fini is called on application exit */ +/* it clears the nomem_handler allocated in the main thread */ static void tsd_fini(void) { struct nomem_handler *nomem_handler; nomem_handler = pthread_getspecific(nomem_handler_key); @@ -57,30 +59,45 @@ static void tsd_fini(void) { } } +/* The tsd_fini_thread is a destructor set by calling */ +/* pthread_key_create(&nomem_handler_key, tsd_fini_thread) */ +/* It is called when thread ends */ +static void tsd_fini_thread(void *nomem_handler) { + free(nomem_handler); +} + static void tsd_init(void) { - if (pthread_key_create(&nomem_handler_key, NULL) != 0) { - fprintf(stderr, "error: cannot create thread specific key"); + if (pthread_key_create(&nomem_handler_key, tsd_fini_thread) != 0) { + fprintf(stderr, "jq: error: cannot create thread specific key"); abort(); } if (atexit(tsd_fini) != 0) { - fprintf(stderr, "error: cannot set an exit handler"); + fprintf(stderr, "jq: error: cannot set an exit handler"); abort(); } - struct nomem_handler *nomem_handler = calloc(1, sizeof(struct nomem_handler)); - if (pthread_setspecific(nomem_handler_key, nomem_handler) != 0) { - fprintf(stderr, "error: cannot set thread specific data"); - abort(); +} + +static void tsd_init_nomem_handler(void) +{ + if (pthread_getspecific(nomem_handler_key) == NULL) { + struct nomem_handler *nomem_handler = calloc(1, sizeof(struct nomem_handler)); + if (pthread_setspecific(nomem_handler_key, nomem_handler) != 0) { + fprintf(stderr, "jq: error: cannot set thread specific data"); + abort(); + } } } void jv_nomem_handler(jv_nomem_handler_f handler, void *data) { pthread_once(&mem_once, tsd_init); // cannot fail + tsd_init_nomem_handler(); + struct nomem_handler *nomem_handler; nomem_handler = pthread_getspecific(nomem_handler_key); if (nomem_handler == NULL) { handler(data); - fprintf(stderr, "error: cannot allocate memory\n"); + fprintf(stderr, "jq: error: cannot allocate memory\n"); abort(); } nomem_handler->handler = handler; @@ -91,6 +108,8 @@ static void memory_exhausted() { struct nomem_handler *nomem_handler; pthread_once(&mem_once, tsd_init); + tsd_init_nomem_handler(); + nomem_handler = pthread_getspecific(nomem_handler_key); if (nomem_handler) nomem_handler->handler(nomem_handler->data); // Maybe handler() will longjmp() to safety