Why isn't Darwin's strtod thread safe?
Asked Answered
B

1

5

The following test always produces failures or bus errors for me on my Intel Mac Mini.

Compiler:

uname -a:
Darwin vogon13 9.8.0 Darwin Kernel Version 9.8.0: Wed Jul 15 16:55:01 PDT 2009; root:xnu-1228.15.4~1/RELEASE_I386 i386

g++ -v:
Target: i686-apple-darwin9
Configured with: /var/tmp/gcc/gcc-5493~1/src/configure
--disable-checking -enable-werror --prefix=/usr --mandir=/share/man
--enable-languages=c,objc,c++,obj-c++
--program-transform-name=/^[cg][^.-]*$/s/$/-4.0/
--with-gxx-include-dir=/include/c++/4.0.0 --with-slibdir=/usr/lib
--build=i686-apple-darwin9 --with-arch=apple --with-tune=generic
--host=i686-apple-darwin9 --target=i686-apple-darwin9
Thread model: posix
gcc version 4.0.1 (Apple Inc. build 5493)

Compile commands:

"g++"  -ftemplate-depth-128 -O3 -finline-functions -Wno-inline -Wall
-pedantic -g -no-cpp-precomp -gdwarf-2 -Wno-long-double -Wno-long-long -fPIC
-DBOOST_DATE_TIME_NO_LIB=1 -DBOOST_THREAD_NO_LIB=1 -DBOOST_THREAD_USE_LIB=1
-DDATE_TIME_INLINE -DNDEBUG  -I"." -c -o "strtod_test.o" "strtod_test.cpp"

"g++"  -o "strtod_test" "strtod_test.o"
"libboost_thread-xgcc40-mt-s.a" "libboost_date_time-xgcc40-mt-s.a"
-g -nodefaultlibs -shared-libgcc -lstdc++-static -lgcc_eh -lgcc -lSystem
-Wl,-dead_strip -no_dead_strip_inits_and_terms -fPIC

Source code:

#include "boost/thread/thread.hpp"
#include "boost/thread/barrier.hpp"
#include <iostream>

using namespace std;

void testThreadSafetyWorker(boost::barrier* testBarrier)
{
    testBarrier->wait(); // wait until all threads have started
    try
    {
        const char* str = "1234.5678";
        const char* end = str;
        double result = strtod(str, const_cast<char**>(&end));
        if (fabs(result-1234.5678) > 1e-6)
            throw runtime_error("result is wrong!");
    }
    catch (exception& e) {cerr << "Exception in worker thread: " << e.what() << endl;}
    catch (...) {cerr << "Unhandled exception in worker thread." << endl;}
}

void testThreadSafety(const int& testThreadCount)
{
    boost::barrier testBarrier(testThreadCount);
    boost::thread_group testThreadGroup;
    for (int i=0; i < testThreadCount; ++i)
        testThreadGroup.add_thread(new boost::thread(&testThreadSafetyWorker, &testBarrier));
    testThreadGroup.join_all();
}

int main(int argc, char* argv[])
{
    try
    {
        testThreadSafety(2);
        testThreadSafety(4);
        testThreadSafety(8);
        testThreadSafety(16);
        return 0;
    }
    catch (exception& e) {cerr << e.what() << endl;}
    catch (...) {cerr << "Unhandled exception in main thread." << endl;}
    return 1;
}

Stack trace:

(gdb) bt
#0 0x950c8f30 in strlen ()
#1 0x9518c16d in strtod_l$UNIX2003 ()
#2 0x9518d2e0 in strtod$UNIX2003 ()
#3 0x00001950 in testThreadSafetyWorker (testBarrier=0xbffff850) at strtod_test.cpp:21
#4 0x0000545d in thread_proxy (param=0xffffffff) at boost_1_43_0/libs/thread/src/pthread/thread.cpp:12? ?1
#5 0x950f1155 in _pthread_start () #6 0x950f1012 in thread_start ()
Bowdlerize answered 29/6, 2011 at 21:19 Comment(5)
Your code works fine for me (once I add a closing brace to the line catch (exception& e) {... in testThreadSafetyWorker). What does the backtrace look like in gdb when you get a failure/bus error?Henryk
@Adam Rosenfield: Thanks for the note on the missed brace, I fixed it. I assume you're testing on some version of Darwin: which one? If not, I know other platforms work fine, both POSIX and MSVC. See updated question for stack trace.Bowdlerize
GCC 4.0.1 dates back to July 7, 2005. That's coming up on seven years ago. Just FYI.Doris
I don't think strtod is supposed to be thread-safe. Some implementations explicitly claim thread-safety for it (e.g. MKS Toolkit), but I can find no evidence of that for glibc. Why do you think that it is?Doris
POSIX requires any function not explicitly documented as non-thread-safe to be thread-safe. See IEEE Std 1003.1-2008, XSH 2.9.1.Hermaphroditism
T
2

strtod is not thread safe. It's never been stated in any specs that it was thread-safe. You should be using strtod_l, which takes a locale. In all likelehood there is a shared data structure that is being clobbered by it's use in the threaded code.

locale_t c_locale = newlocale (LC_ALL_MASK, "C", 0);
r = strtod_l(nptr, endptr, c_locale);
freelocale (c_locale);
Tights answered 29/6, 2011 at 22:14 Comment(6)
I was actually led to this error by using lexical_cast, which in turn uses C++ streams, which in turn uses strtod (at least on Darwin). So evidently I'm not the only one who thinks it should be thread safe! I know it's not guaranteed to be thread safe, but why on earth would it not be. :( Creating a locale for every call? Isn't lexical_cast slow enough already? ;) So what does someone using lexical_cast or C++ streams do in this case?Bowdlerize
@Matt Chambers: use a modern compiler? strtod is part of the compiler.Sacken
strtod is part of glibc, IIRC. There was a fix for the library some time ago to ensure that it was thread safe. Maybe that is not in the Darwin copy of the library.Tights
@MSalters, @Tomalak Geret'kal: I upgraded to llvm-gcc42 and the problem persists. That's the latest Darwin compiler provided by MacPorts. I'll try vanilla GCC 4.4 next. Funny how Microsoft's unmodern compilers got this right but not Apple's...Bowdlerize
strtod is thread-safe, or at least it's required to be thread-safe by posix. If it's not thread-safe on Darwin, that's (yet another) conformance bug in a broken OS...Hermaphroditism
strtod is marked as "MT-Safe locale" in glibc 2.22.Fame

© 2022 - 2024 — McMap. All rights reserved.