Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix segmentation fault when using jq in threads #2546

Merged
merged 2 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 93 additions & 3 deletions src/jq_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#ifdef HAVE_PTHREAD
#include <pthread.h>
#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;
Expand All @@ -32,6 +37,9 @@ int jq_testsuite(jv libdirs, int verbose, int argc, char* argv[]) {
}
}
run_jq_tests(libdirs, verbose, testdata, skip, take);
#ifdef HAVE_PTHREAD
run_jq_pthread_tests();
#endif
return 0;
}

Expand Down Expand Up @@ -105,7 +113,7 @@ static void run_jq_tests(jv lib_dirs, int verbose, FILE *testdata, int skip, int
if (buf[0] == '\n' || (buf[0] == '\r' && buf[1] == '\n'))
break;
}

must_fail = 0;
check_msg = 0;

Expand Down Expand Up @@ -229,7 +237,7 @@ static void run_jq_tests(jv lib_dirs, int verbose, FILE *testdata, int skip, int
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) {
Expand All @@ -241,6 +249,88 @@ static void run_jq_tests(jv lib_dirs, int verbose, FILE *testdata, int skip, int
}


/// 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
{
Expand Down
33 changes: 26 additions & 7 deletions src/jv_alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ static void memory_exhausted() {
#ifdef HAVE_PTHREAD_KEY_CREATE
#include <pthread.h>

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);
Expand All @@ -57,24 +59,39 @@ 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) {
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, "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, "jq: 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);
Expand All @@ -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
Expand Down