gdatetime: Fix incorrect alt-digits being used after changing locale

The alt-digits are loaded from `nl_langinfo()` in a `GOnce` section,
which means `nl_langinfo()` is not re-queried after the process changes
locale (if that happens).

So, change the `GOnce` to a mutex and store the locale of the alt-digits
alongside them. This will introduce contention when calling
`format_number()` is called, but how often are multiple threads trying
to format dates at the same time?

If this does get highlighted as a performance problem, the other
approach I considered was a `GPrivate` struct containing all the
locale-specific cached data. That comes at the cost of using a
`GPrivate` slot (although that’s only particularly expensive on Windows,
and the locale code is quite different for Windows, so perhaps that
could be avoided entirely). It does mean that all locale printing could
be lock-free and still safely update cached data on a locale change.

Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
This commit is contained in:
Philip Withnall 2023-10-18 15:52:07 +01:00 committed by Philip Withnall
parent 592be87b7a
commit afd8dde13f
3 changed files with 42 additions and 3 deletions

View File

@ -28,6 +28,7 @@ RUN dnf -y update \
glibc-langpack-es \
glibc-langpack-fa \
glibc-langpack-fr \
glibc-langpack-gu \
glibc-langpack-hr \
glibc-langpack-ja \
glibc-langpack-lt \

View File

@ -54,6 +54,7 @@
#define _GNU_SOURCE 1
#endif
#include <locale.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
@ -2820,6 +2821,9 @@ format_z (GString *outstr,
/* Initializes the array with UTF-8 encoded alternate digits suitable for use
* in current locale. Returns NULL when current locale does not use alternate
* digits or there was an error converting them to UTF-8.
*
* This needs external locking, so must only be called from within
* format_number().
*/
static const gchar * const *
initialize_alt_digits (void)
@ -2874,6 +2878,9 @@ format_number (GString *str,
const gchar * const *digits = ascii_digits;
const gchar *tmp[10];
gint i = 0;
#ifdef HAVE_LANGINFO_OUTDIGIT
static GMutex alt_digits_mutex;
#endif
g_return_if_fail (width <= 10);
@ -2881,16 +2888,21 @@ format_number (GString *str,
if (use_alt_digits)
{
static const gchar * const *alt_digits = NULL;
static gsize initialised;
static char *alt_digits_locale = NULL;
const char *current_ctype_locale = setlocale (LC_CTYPE, NULL);
if G_UNLIKELY (g_once_init_enter (&initialised))
/* Lock so we can initialise (or re-initialise, if the locale has changed)
* and hold access to the digits buffer until done formatting. */
g_mutex_lock (&alt_digits_mutex);
if (g_strcmp0 (alt_digits_locale, current_ctype_locale) != 0)
{
alt_digits = initialize_alt_digits ();
if (alt_digits == NULL)
alt_digits = ascii_digits;
g_once_init_leave (&initialised, TRUE);
alt_digits_locale = g_strdup (current_ctype_locale);
}
digits = alt_digits;
@ -2907,6 +2919,11 @@ format_number (GString *str,
while (pad && i < width)
tmp[i++] = *pad == '0' ? digits[0] : pad;
#ifdef HAVE_LANGINFO_OUTDIGIT
if (use_alt_digits)
g_mutex_unlock (&alt_digits_mutex);
#endif
/* should really be impossible */
g_assert (i <= 10);

View File

@ -1834,6 +1834,7 @@ test_modifiers (void)
TEST_PRINTF_TIME (1, 0, 0, "%#P", "am");
oldlocale = g_strdup (setlocale (LC_ALL, NULL));
setlocale (LC_ALL, "fa_IR.utf-8");
#ifdef HAVE_LANGINFO_OUTDIGIT
if (strstr (setlocale (LC_ALL, NULL), "fa_IR") != NULL)
@ -1852,6 +1853,26 @@ test_modifiers (void)
#else
g_test_skip ("langinfo not available, skipping O modifier tests");
#endif
setlocale (LC_ALL, "gu_IN.utf-8");
#ifdef HAVE_LANGINFO_OUTDIGIT
if (strstr (setlocale (LC_ALL, NULL), "gu_IN") != NULL)
{
TEST_PRINTF_TIME (23, 0, 0, "%OH", "૨૩"); /* '23' */
TEST_PRINTF_TIME (23, 0, 0, "%OI", "૧૧"); /* '11' */
TEST_PRINTF_TIME (23, 0, 0, "%OM", ""); /* '00' */
TEST_PRINTF_DATE (2011, 7, 1, "%Om", "૦૭"); /* '07' */
TEST_PRINTF_DATE (2011, 7, 1, "%0Om", "૦૭"); /* '07' */
TEST_PRINTF_DATE (2011, 7, 1, "%-Om", ""); /* '7' */
TEST_PRINTF_DATE (2011, 7, 1, "%_Om", ""); /* ' 7' */
}
else
g_test_skip ("locale gu_IN not available, skipping O modifier tests");
#else
g_test_skip ("langinfo not available, skipping O modifier tests");
#endif
setlocale (LC_ALL, oldlocale);
g_free (oldlocale);
}