On Sat, Nov 8, 2014 at 2:21 AM, Jerry DeLisle <[email protected]> wrote:
> On 11/07/2014 02:15 AM, Janne Blomqvist wrote:
>>
>> On Thu, Nov 6, 2014 at 12:38 PM, Janne Blomqvist
>> <[email protected]> wrote:
>>>
>>> On Wed, Nov 5, 2014 at 12:48 PM, Janne Blomqvist
>>> <[email protected]> wrote:
>>>>
>>>> Hi,
>>>>
>>>> the attached patch fixes a few locale related failures in libgfortran,
>>>> in the case where the POSIX 2008 extended locale functionality and
>>>> extensions strto{f,d,ld}_l are present.
>>>>
>>>> These failures typically occur when libgfortran is used from a program
>>>> which has set the locale with setlocale(), and the locale uses a
>>>> different decimal separator than the C locale. The patch fixes this by
>>>> creating a C locale which is then used by strto{f,d,ld}_l, and also is
>>>> installed as the per-thread locale when starting a formatted IO, then
>>>> reset to the previous value when the IO is done. I have chosen to not
>>>> fallback to calling setlocale() in case the POSIX 2008 locale stuff
>>>> isn't available, as that could create nasty hard to debug race
>>>> conditions in a multi-threaded program.
>>>>
>>>> (I think Jerry's proposed patch which checks the locale for the
>>>> decimal separator is still useful as a fallback in case the POSIX 2008
>>>> locale stuff isn't available)
>>>
>>>
>>> Hi,
>>>
>>> updated patch attached. Since the patch sets the per-thread locale
>>> with uselocale, using the non-standard strto{f,d,ld}_l functions isn't
>>> necessary. When getting rid of this part of the original patch, I
>>> noticed a few failures due to the uselocale() calls being in the wrong
>>> places. These are fixed in the updated patch. Also Jakub's suggestion
>>> has been incorporated. Further, investigation revealed that some
>>> targets (Darwin and Freebsd) have the extended locale functionality in
>>> xlocale.h rather than locale.h as POSIX 2008 specifies. So check for
>>> that header. Finally, as we set the per-thread locale to "C", we'd
>>> lose localized error messages. So the updated patch fixes this by
>>> updating the gf_strerror() function as well.
>>
>>
>> Hi again!
>>
>> Well, for all my ranting against using setlocale() in a library
>> potentially used by multi-threaded programs, here's a patch that does
>> exactly that, as a fallback in case the POSIX 2008 per-thread locale
>> stuff isn't available. So this can be seen as an alternative to the
>> approach Jerry suggested in the patch attached in PR 61847.
>>
>> - Jerry's patch (use localeconv() to figure out the decimal separator)
>> - Race condition if locale is set between OPEN (where the separator
>> is checked) and READ/WRITE.
>> - Potential breakage in weird locales where the decimal separator
>> isn't "." or ","? See
>> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61847#c21
>> - Other potential issues with weird locales? E.g. are there locales
>> which use grouping characters, e.g. 1e6 is 1,000,000.0?
>>
>> - My patch (use setlocale(LC_NUMERIC, "C") when starting formatted
>> I/O, change back when I/O statement is done.)
>> - Race condition if locale is set concurrently in another thread
>> while a formatted I/O is in progress.
>> - Potential problem if another thread does something dependent on
>> LC_NUMERIC while a formatted I/O is in progress in one thread.
>> - Should be robust against weird locales.
>>
>> In both cases IMHO these approaches should be used only if the POSIX
>> 2008 per-thread locale stuff isn't available. I have no strong
>> opinions which is preferable, comments?
>>
>> Attached is locale3_top2.diff, which is on top of my previous patch,
>> and locale3.diff which includes the previous patch and applies against
>> trunk.
>>
>> Ok for trunk?
>
>
> OK, thanks for doing all the work. ;)
I realized my setlocale fallback wouldn't work correctly if we have
multiple threads doing formatted I/O concurrently. Committed the patch
with the fallback part per the attached part.
--
Janne Blomqvist
diff --git a/gcc/fortran/gfortran.texi b/gcc/fortran/gfortran.texi
index 41d6559..0d19e7a 100644
--- a/gcc/fortran/gfortran.texi
+++ b/gcc/fortran/gfortran.texi
@@ -1223,10 +1223,26 @@ implemented with the @code{system} function, which need
not be
thread-safe. It is the responsibility of the user to ensure that
@code{system} is not called concurrently.
-Finally, for platforms not supporting thread-safe POSIX functions,
-further functionality might not be thread-safe. For details, please
-consult the documentation for your operating system.
-
+For platforms not supporting thread-safe POSIX functions, further
+functionality might not be thread-safe. For details, please consult
+the documentation for your operating system.
+
+The GNU Fortran runtime library uses various C library functions that
+depend on the locale, such as @code{strtod} and @code{snprintf}. In
+order to work correctly in locale-aware programs that set the locale
+using @code{setlocale}, the locale is reset to the default ``C''
+locale while executing a formatted @code{READ} or @code{WRITE}
+statement. On targets supporting the POSIX 2008 per-thread locale
+functions (e.g. @code{newlocale}, @code{uselocale},
+@code{freelocale}), these are used and thus the global locale set
+using @code{setlocale} or the per-thread locales in other threads are
+not affected. However, on targets lacking this functionality, the
+global LC_NUMERIC locale is set to ``C'' during the formatted I/O.
+Thus, on such targets it's not safe to call @code{setlocale}
+concurrently from another thread while a Fortran formatted I/O
+operation is in progress. Also, other threads doing something
+dependent on the LC_NUMERIC locale might not work correctly if a
+formatted I/O operation is in progress in another thread.
@node Data consistency and durability
@section Data consistency and durability
diff --git a/libgfortran/io/io.h b/libgfortran/io/io.h
index 0ff5dcc..a75177f 100644
--- a/libgfortran/io/io.h
+++ b/libgfortran/io/io.h
@@ -54,6 +54,14 @@ struct gfc_unit;
#ifdef HAVE_NEWLOCALE
/* We have POSIX 2008 extended locale stuff. */
extern locale_t c_locale;
+internal_proto(c_locale);
+#else
+extern char* old_locale;
+internal_proto(old_locale);
+extern int old_locale_ctr;
+internal_proto(old_locale_ctr);
+extern __gthread_mutex_t old_locale_lock;
+internal_proto(old_locale_lock);
#endif
diff --git a/libgfortran/io/transfer.c b/libgfortran/io/transfer.c
index 203f516..87b8c05 100644
--- a/libgfortran/io/transfer.c
+++ b/libgfortran/io/transfer.c
@@ -2874,6 +2874,14 @@ data_transfer_init (st_parameter_dt *dtp, int read_flag)
{
#ifdef HAVE_USELOCALE
dtp->u.p.old_locale = uselocale (c_locale);
+#else
+ __gthread_mutex_lock (&old_locale_lock);
+ if (!old_locale_ctr++)
+ {
+ old_locale = setlocale (LC_NUMERIC, NULL);
+ setlocale (LC_NUMERIC, "C");
+ }
+ __gthread_mutex_unlock (&old_locale_lock);
#endif
/* Start the data transfer if we are doing a formatted transfer. */
if ((cf & (IOPARM_DT_LIST_FORMAT | IOPARM_DT_HAS_NAMELIST_NAME)) == 0
@@ -3614,6 +3622,14 @@ finalize_transfer (st_parameter_dt *dtp)
uselocale (dtp->u.p.old_locale);
dtp->u.p.old_locale = (locale_t) 0;
}
+#else
+ __gthread_mutex_lock (&old_locale_lock);
+ if (!--old_locale_ctr)
+ {
+ setlocale (LC_NUMERIC, old_locale);
+ old_locale = NULL;
+ }
+ __gthread_mutex_unlock (&old_locale_lock);
#endif
}
diff --git a/libgfortran/io/unit.c b/libgfortran/io/unit.c
index e4bc60b..277c7a1 100644
--- a/libgfortran/io/unit.c
+++ b/libgfortran/io/unit.c
@@ -93,8 +93,23 @@ static char stderr_name[] = "stderr";
#ifdef HAVE_NEWLOCALE
locale_t c_locale;
+#else
+/* If we don't have POSIX 2008 per-thread locales, we need to use the
+ traditional setlocale(). To prevent multiple concurrent threads
+ doing formatted I/O from messing up the locale, we need to store a
+ global old_locale, and a counter keeping track of how many threads
+ are currently doing formatted I/O. The first thread saves the old
+ locale, and the last one restores it. */
+char *old_locale;
+int old_locale_ctr;
+#ifdef __GTHREAD_MUTEX_INIT
+__gthread_mutex_t old_locale_lock = __GTHREAD_MUTEX_INIT;
+#else
+__gthread_mutex_t old_locale_lock;
+#endif
#endif
+
/* This implementation is based on Stefan Nilsson's article in the
* July 1997 Doctor Dobb's Journal, "Treaps in Java". */
@@ -568,6 +583,10 @@ init_units (void)
#ifdef HAVE_NEWLOCALE
c_locale = newlocale (0, "C", 0);
+#else
+#ifndef __GTHREAD_MUTEX_INIT
+ __GTHREAD_MUTEX_INIT_FUNCTION (&old_locale_lock);
+#endif
#endif
#ifndef __GTHREAD_MUTEX_INIT