Skip to content

conversion from string to JSON is not multithread-safe #675

@bhaible

Description

@bhaible

When two different threads use json_loads to convert a string to a JSON object, they can disturb each other: one of the threads may run into an assertion failure and crash the program.

How to reproduce:
On a system with glibc, compile and run the following program foo.c:

#include <locale.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <jansson.h>

static void *
thread1_func (void *arg)
{
  locale_t thread1_locale = newlocale (LC_ALL_MASK, "en_US.UTF-8", NULL);
  uselocale (thread1_locale);

  char input[] = "{\"a\":1.5}";

  for (;;)
    {
      json_error_t e;
      json_t *j = json_loads (input, 0, &e);
      if (!json_is_object (j))
        abort ();
      json_t *k = json_object_get (j, "a");
      if (!json_is_real (k))
        abort ();
      if (json_real_value (k) != 1.5)
        {
          fprintf (stderr, "thread1 disturbed by thread2, read %g\n", json_real_value (k)); fflush (stderr);
          abort ();
        }
      json_decref (j);
    }

  /*NOTREACHED*/
}

static void *
thread2_func (void *arg)
{
  locale_t thread2_locale = newlocale (LC_ALL_MASK, "fr_FR.UTF-8", NULL);
  uselocale (thread2_locale);

  char input[] = "{\"b\":2.5}";

  for (;;)
    {
      json_error_t e;
      json_t *j = json_loads (input, 0, &e);
      if (!json_is_object (j))
        abort ();
      json_t *k = json_object_get (j, "b");
      if (!json_is_real (k))
        abort ();
      if (json_real_value (k) != 2.5)
        {
          fprintf (stderr, "thread2 disturbed by thread1, read %g\n", json_real_value (k)); fflush (stderr);
          abort ();
        }
      json_decref (j);
    }

  /*NOTREACHED*/
}

int
main (int argc, char *argv[])
{
  /* Create the threads.  */
  pthread_t thread1, thread2;
  pthread_create (&thread1, NULL, thread1_func, NULL);
  pthread_create (&thread2, NULL, thread2_func, NULL);

  /* Let them run for 1 second.  */
  {
    struct timespec duration;
    duration.tv_sec = (argc > 1 ? atoi (argv[1]) : 1);
    duration.tv_nsec = 0;

    nanosleep (&duration, NULL);
  }

  return 0;
}

Like this:

$ gcc -Wall foo.c /usr/lib/x86_64-linux-gnu/libjansson.a -lpthread
$ ./a.out
a.out: strconv.c:68: jsonp_strtod: Assertion `end == strbuffer->value + strbuffer->length' failed.
Aborted (core dumped)

Here is the gdb stack trace:

(gdb) where
#0  0x00007ffff7e079fc in pthread_kill () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x00007ffff7db3476 in raise () from /lib/x86_64-linux-gnu/libc.so.6
#2  0x00007ffff7d997f3 in abort () from /lib/x86_64-linux-gnu/libc.so.6
#3  0x00007ffff7d9971b in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#4  0x00007ffff7daae96 in __assert_fail () from /lib/x86_64-linux-gnu/libc.so.6
#5  0x0000555555557ae5 in jsonp_strtod ()
#6  0x0000555555556670 in lex_scan.isra ()
#7  0x0000555555556b14 in parse_value ()
#8  0x0000555555556e65 in parse_json ()
#9  0x0000555555556feb in json_loads ()
#10 0x000055555555564c in thread1_func ()

Note: When one of the pthread_create lines is commented out, such that only one thread is created, the program runs fine. Only when both pthread_create lines are enabled, does the program crash. This proves that there is an interaction between the threads.

Note: The test program fulfils the rules documented in https://jansson.readthedocs.io/en/latest/threadsafety.html :

  • The json_t objects are private to each of the threads.
  • It does not invoke setlocale.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions