23

I'm using boosts read_json in a couple of threads in a piece of code. A simplified breakdown of the call is below. I'm getting segfaults in one of the threads (and sometimes the other) and it's making me think that read_json is not thread safe (Or I'm just using it in a dumb way)

void someclass::dojson() {
   using boost::property_tree::ptree;
   ptree pt;
   std::stringstream ss(json_data_string);

   read_json(ss,pt);
 }

Now json_data_string is different between the two classes (it's just json data received over a socket).

So is read_json thread safe or do I have to mutex it (rather not) or is there a better way of calling read_json that is thread safe?

ildjarn
  • 62,044
  • 9
  • 127
  • 211
Ross W
  • 1,300
  • 3
  • 14
  • 24

4 Answers4

22

Because boost json parser depends on boost::spirit, and spirit is not thread-safety default.

You can add this macro before any ptree header file to resolve it.

#define BOOST_SPIRIT_THREADSAFE
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
Wei
  • 246
  • 2
  • 5
  • 2
    If you don't define this globally but have Spirit/json_parser used in multiple independent cpps, it'll still crash because annoyingly Sprit has a static variable that'll be shared amongst them all. – G Huxley Nov 14 '14 at 04:39
7

TL;DR:

My suggestion: use the atomic swap idiom

ptree my_shared;
mutex shared_ptree_lock;

{
    ptree parsed;     // temporary
    read_json(ss,pt); // this may take a while (or even fail)

    lock_guard hold(shared_ptree_lock);
    std::swap(pt, my_shared); // swap under lock
}

Now, whether you need to lock the shared tree before reading, depends on your knowledge of the threading context (in other words, depends whether you know your tree can be being modified at the same time).

To make things insanely flexible, do the same through shared_ptr<ptree> - but this will bear considerable overhead. The pre is, that with the swap idiom, you won't have to lock things on the read side, since readers will happily continue reading the old tree, and if they are done reading and release the shared_ptr it will get destroyed in the end.


I'm not completely sure what you expect. With the property tree being accessed for writing from two threads is never going to be thread-safe without locking. Therefore, I assume that you mean, is property tree threadsafe for reading while simultaneously parsing it somewhere else.

Here, my primary expectation is: no. C++ has a culture of 'pay-for-what-you-need' you won't see any thread-safe general-purpose classes. There would be the option of

  • a preprocessor #define to switch on thread-safety
  • a policy template parameter that govern's the behaviour

After looking at the source code, surprisingly, it looks as if it was almost thread safe. But not fully :)

It appears that there is no #define or flag to set to make property tree thread safe, so you're stuck with locking.

Rationale:

Looking at internal_read_json I see that it only accesses the stream (which should be private to this reader anyway, as sharing streams across multiple (concurrent) users is hardly ever useful1), and then, very correctly, only swaps the ptree's (pt) root node out with the parser's context tree.

Obviously, the atomic swap functionality is mainly there for exception safety (you don't want to change your ptree if an exception occurred halfway parsing the JSON). However, IFF the swap operation were to be thread-safe, this would also make the access to pt thread-safe.

Alas, onto the ptree_implementation, we see that the swap is not threadsafe:

template<class K, class D, class C> inline
void basic_ptree<K, D, C>::swap(basic_ptree<K, D, C> &rhs)
{
    m_data.swap(rhs.m_data);
    // Void pointers, no ADL necessary
    std::swap(m_children, rhs.m_children);
}

For one, you could have a race condition between swapping m_data and m_children, further more, the swaps are standard, not atomic swaps.


1besides istringstream obviously not being thread-safe as it is a C++98 standard library class

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Yes I'm actually trying to use the read_json function from two separate threads (on independent data). I'm guessing they are calling the same function in memory (net a separate instance) and hence the segfault - damn.. – Ross W Nov 16 '11 at 19:41
  • @RossW: that sounds confused. The function must be the same (there is only one binarY), yet they cannot interfere (they are on different stacks, different threads). The conflict can _only_ arise from the access to either the input stream or the pt output parameter. You need to synchronize access to them when they are shared. What's more, you will have to think of the lifetime (destruction of the old tree will invalidate any pointers/reference you had to it's internals). – sehe Nov 16 '11 at 19:46
  • That's what I though too which is why I got confused over this. I'm going to try and simplified version of the code and see what I get – Ross W Nov 16 '11 at 19:56
  • 11
    mmm ok so I did a backtrace and it looks like read_json is using boost::spirit. I searched around and found that you should #define BOOST_SPIRIT_THREADSAFE if using spirit. I've put that in the header file for the two thread classes and recompiled using the -mt libraries (I didn't think those made a difference on linux). Anyhow - hasn't segfaulted for 5 min which is a lot longer than previously. Will see how it goes. – Ross W Nov 16 '11 at 20:43
  • @RossW, good work Ross. I hadn't even _guessed_ that it was using spirit, which is a shame, because I use Spirit a lot and could have saved you the search. +1 for finding your own answer. – sehe Nov 16 '11 at 20:52
  • 1
    @Ross W: thanks for the pointer. I'm in the same situation (multiple threads, completely separate data to parse) and was seeing a similar crash when the spirit grammar was instantiated. With the #define, things are now working correctly. – Ben Mar 26 '12 at 21:33
  • Same problem here, same solution, thank you. Note that for me, I made no changes other than adding the define. With increasingly-sized batch POST tests, my server was rock solid and in the end I crashed firefox. Go boost asio! :-) – moodboom Oct 07 '13 at 21:17
1

JSON parser in ptree has been rewritten and released in Boost 1.59. Adding BOOST_SPIRIT_THREADSAFE define is no longer needed for Property Tree since it doesn't use Boost.Spirit anymore and read_json function may be considered thread safe.

  • 1
    Can you please point me to the official boost documentation that states this. I checked https://www.boost.org/users/history/version_1_59_0.html. There it has been mentioned a new JSON parser is used in Property Tree, but can you please point me to the location where they say it doesn't use Boost.Spirit – Vivek Maran Jul 23 '19 at 19:21
0

Thanks to Wei and liquidblueocean; the #define fixed my issue. FYI I was getting this stack trace out of windbg !analyse -v when I had two threads calling read_json.

10 07b3e4fc 0021b2de sseng!_STL::for_each<_STL::reverse_iterator<boost::spirit::classic::impl::grammar_helper_base<boost::spirit::classic::grammar<boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >,boost::spirit::classic::parser_context<boost::spirit::classic::nil_t> > > * *>,_STL::binder2nd<_STL::mem_fun1_t<int,boost::spirit::classic::impl::grammar_helper_base<boost::spirit::classic::grammar<boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >,boost::spirit::classic::parser_context<boost::spirit::classic::nil_t> > >,boost::spirit::classic::grammar<boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >,boost::spirit::classic::parser_context<boost::spirit::classic::nil_t> > *> > >+0x11 [c:\ss\tp\aoo341\main\stlport\rel\inc\stlport\stl\_algo.h @ 65]
11 07b3e520 0021f867 sseng!boost::spirit::classic::impl::grammar_destruct<boost::spirit::classic::grammar<boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >,boost::spirit::classic::parser_context<boost::spirit::classic::nil_t> > >+0x28 [c:\ss\tp\aoo341\main\boost\rel\inc\boost\spirit\home\classic\core\non_terminal\impl\grammar.ipp @ 325]
12 07b3e54c 002224fa sseng!boost::spirit::classic::grammar<boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >,boost::spirit::classic::parser_context<boost::spirit::classic::nil_t> >::~grammar<boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >,boost::spirit::classic::parser_context<boost::spirit::classic::nil_t> >+0x1e [c:\ss\tp\aoo341\main\boost\rel\inc\boost\spirit\home\classic\core\non_terminal\grammar.hpp @ 52]
13 07b3e574 00226e37 sseng!boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >::~json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >+0x28
14 07b3e784 00226f5c sseng!boost::property_tree::json_parser::read_json_internal<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >+0x149 [c:\ss\tp\aoo341\main\boost\rel\inc\boost\property_tree\detail\json_parser_read.hpp @ 317]
15 07b3e7c0 00232261 sseng!boost::property_tree::json_parser::read_json<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >+0x25 [c:\ss\tp\aoo341\main\boost\rel\inc\boost\property_tree\json_parser.hpp @ 45]
16 07b3ea20 00232a28 sseng!SSPhone::Handshake+0x15b [c:\ss\xl\src\cpp\bin\eng\ssphone.cpp @ 272]
17 07b3ea5c 00234fc7 sseng!SSPhone::OnEvent+0x1a9 [c:\ss\xl\src\cpp\bin\eng\ssphone.cpp @ 232]
18 07b3fb7c 6f6b3433 sseng!PhoneThreadFunc+0x1ed [c:\ss\xl\src\cpp\bin\eng\ssthrd.cpp @ 198]
MSalters
  • 173,980
  • 10
  • 155
  • 350
osullivj
  • 341
  • 1
  • 5