Hi,
I have noticed that the string to float conversion make multithreaded programs crash when used intensively in multiple threads in apps that are using non-C locale. This is due to the fact that setlocale is not thread safe at all.
Please find below a proposed patch to overcome this issue (using uselocal instead, with a custom implementation for windows as it does not exist there):
Index: scriptstdstring/scriptstdstring.cpp
===================================================================
--- scriptstdstring/scriptstdstring.cpp (original)
+++ scriptstdstring/scriptstdstring.cpp (working copy)
@@ -6,7 +6,145 @@
#include <stdlib.h> // strtod()
#ifndef __psp2__
#include <locale.h> // setlocale()
+#ifdef _WIN32
+// win32 does not have the thread safe uselocal feature
+// here is an implementation
+struct LocaleStruct
+{
+ char* localeStrings[LC_MAX + 1];
+};
+typedef LocaleStruct* locale_t;
+
+#define LC_GLOBAL_LOCALE ((locale_t)-1)
+#define LC_COLLATE_MASK (1<<0)
+#define LC_CTYPE_MASK (1<<1)
+#define LC_MONETARY_MASK (1<<2)
+#define LC_NUMERIC_MASK (1<<3)
+#define LC_TIME_MASK (1<<4)
+#define LC_MESSAGES_MASK (1<<5)
+#define LC_ALL_MASK (LC_COLLATE_MASK | LC_CTYPE_MASK | LC_MESSAGES_MASK | \
+ LC_MONETARY_MASK | LC_NUMERIC_MASK | LC_TIME_MASK)
+
+
+inline void freelocale(locale_t locale)
+{
+ if (locale != NULL && locale != LC_GLOBAL_LOCALE)
+ {
+ for (int i = LC_MIN; i <= LC_MAX; i++)
+ {
+ if (locale->localeStrings[i] != NULL)
+ free(locale->localeStrings[i]);
+ }
+ free(locale);
+ }
+}
+
+inline locale_t duplocale(locale_t base)
+{
+ locale_t loc = (locale_t)malloc(sizeof(LocaleStruct));
+ if (loc)
+ {
+ // use the equivalent of
+ if (base == LC_GLOBAL_LOCALE)
+ base = NULL;
+ }
+}
+
+inline locale_t newlocale(int categoryMask, const char* locale, locale_t base)
+{
+ // allocate new object
+ locale_t loc = (locale_t)malloc(sizeof(LocaleStruct));
+ if (loc)
+ {
+ // duplicate locale under lock
+ _lock_locales();
+ for (int i = LC_MIN; i <= LC_MAX; i++)
+ {
+ // either copy existing or use new depending on mask
+ char const* localeToSet = locale;
+ if ((i & categoryMask) == 0)
+ {
+ if (base == NULL || base == LC_GLOBAL_LOCALE)
+ localeToSet = setlocale(i, NULL);
+ else
+ localeToSet = base->localeStrings[i];
+ }
+
+ // copy locale string
+ if (localeToSet)
+ loc->localeStrings[i] = strdup(locale);
+ else
+ loc->localeStrings[i] = NULL;
+ }
+ _unlock_locales();
+ }
+ // free old object
+ freelocale(base);
+ return loc;
+}
+
+inline locale_t uselocale(locale_t new_locale)
+{
+ // per-thread locale data cache
+ __declspec(thread) static
+ struct OwnedLocalePtr
+ {
+ OwnedLocalePtr(locale_t locale) :
+ localePtr(locale) {}
+ ~OwnedLocalePtr()
+ {
+ freelocale(localePtr);
+ }
+ locale_t localePtr;
+ } staticLocaleData(NULL);
+
+ locale_t old_locale = LC_GLOBAL_LOCALE;
+
+ // check if current locale is per thread
+ const bool isPerThread = (_configthreadlocale(0) == _ENABLE_PER_THREAD_LOCALE);
+
+ // Retrieve the current thread-specific locale
+ if (isPerThread)
+ {
+ if (staticLocaleData.localePtr == NULL)
+ {
+ // create new data as it has not been initialized yet
+ staticLocaleData.localePtr = newlocale(LC_ALL_MASK, NULL, NULL);
+ }
+
+ // store pointer to be returned
+ old_locale = staticLocaleData.localePtr;
+ }
+
+ if (new_locale == LC_GLOBAL_LOCALE)
+ {
+ // Restore the global locale
+ _configthreadlocale(_DISABLE_PER_THREAD_LOCALE);
+ }
+ else if (new_locale != NULL)
+ {
+ // Configure the thread to set the locale only for this thread
+ _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
+
+ // Set new locale categories
+ for (int i = LC_MIN; i <= LC_MAX; i++)
+ {
+ if (new_locale->localeStrings[i] != NULL)
+ setlocale(i, new_locale->localeStrings[i]);
+ }
+ }
+
+ // store in local thread storage if not NULL
+ if (new_locale != NULL)
+ staticLocaleData.localePtr = new_locale;
+ return old_locale;
+}
+
+#else
+// thread safe uselocal functions
+#include <xlocale.h>
#endif
+#endif
using namespace std;
@@ -837,9 +975,9 @@
// locale is ",".
#if !defined(_WIN32_WCE) && !defined(ANDROID) && !defined(__psp2__)
// Set the locale to C so that we are guaranteed to parse the float value correctly
- char *tmp = setlocale(LC_NUMERIC, 0);
- string orig = tmp ? tmp : "C";
- setlocale(LC_NUMERIC, "C");
+ // using the thread safe uselocal instead of setlocale
+ locale_t locale = newlocale(LC_NUMERIC_MASK, "C", NULL);
+ locale_t orig_locale = uselocale(locale);
#endif
double res = strtod(val.c_str(), &end);
@@ -846,7 +984,8 @@
#if !defined(_WIN32_WCE) && !defined(ANDROID) && !defined(__psp2__)
// Restore the locale
- setlocale(LC_NUMERIC, orig.c_str());
+ uselocale(orig_locale);
+ freelocale(locale);
#endif
if( byteCount )
It would be nice if this (or any other fix - you could make it shorter if not fully re-implementing uselocal on windows) could be incorporated into the mainline. Thanks again for your great work!