aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt
diff options
context:
space:
mode:
authorPeter Eisentraut <peter@eisentraut.org>2025-03-27 07:52:22 +0100
committerPeter Eisentraut <peter@eisentraut.org>2025-03-27 10:54:28 +0100
commitb98be8a2a2a6196dc24631dfe4d8785b86800d23 (patch)
treeb4b1579cb103bf66f92c24c67be884c53421cd79 /src/backend/utils/adt
parent4a02af8b1a5f3f8221c7381bc4de08a3d830f682 (diff)
downloadpostgresql-b98be8a2a2a6196dc24631dfe4d8785b86800d23.tar.gz
postgresql-b98be8a2a2a6196dc24631dfe4d8785b86800d23.zip
Provide thread-safe pg_localeconv_r().
This involves four different implementation strategies: 1. For Windows, we now require _configthreadlocale() to be available and work (commit f1da075d9a0), and the documentation says that the object returned by localeconv() is in thread-local memory. 2. For glibc, we translate to nl_langinfo_l() calls, because it offers the same information that way as an extension, and that API is thread-safe. 3. For macOS/*BSD, use localeconv_l(), which is thread-safe. 4. For everything else, use uselocale() to set the locale for the thread, and use a big ugly lock to defend against the returned object being concurrently clobbered. In practice this currently means only Solaris. The new call is used in pg_locale.c, replacing calls to setlocale() and localeconv(). Author: Thomas Munro <thomas.munro@gmail.com> Reviewed-by: Heikki Linnakangas <hlinnaka@iki.fi> Reviewed-by: Peter Eisentraut <peter@eisentraut.org> Discussion: https://postgr.es/m/CA%2BhUKGJqVe0%2BPv9dvC9dSums_PXxGo9SWcxYAMBguWJUGbWz-A%40mail.gmail.com
Diffstat (limited to 'src/backend/utils/adt')
-rw-r--r--src/backend/utils/adt/pg_locale.c128
1 files changed, 21 insertions, 107 deletions
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 7d92f580a57..4dd4313b779 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -547,12 +547,8 @@ PGLC_localeconv(void)
static struct lconv CurrentLocaleConv;
static bool CurrentLocaleConvAllocated = false;
struct lconv *extlconv;
- struct lconv worklconv;
- char *save_lc_monetary;
- char *save_lc_numeric;
-#ifdef WIN32
- char *save_lc_ctype;
-#endif
+ struct lconv tmp;
+ struct lconv worklconv = {0};
/* Did we do it already? */
if (CurrentLocaleConvValid)
@@ -566,77 +562,21 @@ PGLC_localeconv(void)
}
/*
- * This is tricky because we really don't want to risk throwing error
- * while the locale is set to other than our usual settings. Therefore,
- * the process is: collect the usual settings, set locale to special
- * setting, copy relevant data into worklconv using strdup(), restore
- * normal settings, convert data to desired encoding, and finally stash
- * the collected data in CurrentLocaleConv. This makes it safe if we
- * throw an error during encoding conversion or run out of memory anywhere
- * in the process. All data pointed to by struct lconv members is
- * allocated with strdup, to avoid premature elog(ERROR) and to allow
- * using a single cleanup routine.
+ * Use thread-safe method of obtaining a copy of lconv from the operating
+ * system.
*/
- memset(&worklconv, 0, sizeof(worklconv));
-
- /* Save prevailing values of monetary and numeric locales */
- save_lc_monetary = setlocale(LC_MONETARY, NULL);
- if (!save_lc_monetary)
- elog(ERROR, "setlocale(NULL) failed");
- save_lc_monetary = pstrdup(save_lc_monetary);
-
- save_lc_numeric = setlocale(LC_NUMERIC, NULL);
- if (!save_lc_numeric)
- elog(ERROR, "setlocale(NULL) failed");
- save_lc_numeric = pstrdup(save_lc_numeric);
-
-#ifdef WIN32
-
- /*
- * The POSIX standard explicitly says that it is undefined what happens if
- * LC_MONETARY or LC_NUMERIC imply an encoding (codeset) different from
- * that implied by LC_CTYPE. In practice, all Unix-ish platforms seem to
- * believe that localeconv() should return strings that are encoded in the
- * codeset implied by the LC_MONETARY or LC_NUMERIC locale name. Hence,
- * once we have successfully collected the localeconv() results, we will
- * convert them from that codeset to the desired server encoding.
- *
- * Windows, of course, resolutely does things its own way; on that
- * platform LC_CTYPE has to match LC_MONETARY/LC_NUMERIC to get sane
- * results. Hence, we must temporarily set that category as well.
- */
-
- /* Save prevailing value of ctype locale */
- save_lc_ctype = setlocale(LC_CTYPE, NULL);
- if (!save_lc_ctype)
- elog(ERROR, "setlocale(NULL) failed");
- save_lc_ctype = pstrdup(save_lc_ctype);
-
- /* Here begins the critical section where we must not throw error */
-
- /* use numeric to set the ctype */
- setlocale(LC_CTYPE, locale_numeric);
-#endif
-
- /* Get formatting information for numeric */
- setlocale(LC_NUMERIC, locale_numeric);
- extlconv = localeconv();
-
- /* Must copy data now in case setlocale() overwrites it */
+ if (pg_localeconv_r(locale_monetary,
+ locale_numeric,
+ &tmp) != 0)
+ elog(ERROR,
+ "could not get lconv for LC_MONETARY = \"%s\", LC_NUMERIC = \"%s\": %m",
+ locale_monetary, locale_numeric);
+
+ /* Must copy data now now so we can re-encode it. */
+ extlconv = &tmp;
worklconv.decimal_point = strdup(extlconv->decimal_point);
worklconv.thousands_sep = strdup(extlconv->thousands_sep);
worklconv.grouping = strdup(extlconv->grouping);
-
-#ifdef WIN32
- /* use monetary to set the ctype */
- setlocale(LC_CTYPE, locale_monetary);
-#endif
-
- /* Get formatting information for monetary */
- setlocale(LC_MONETARY, locale_monetary);
- extlconv = localeconv();
-
- /* Must copy data now in case setlocale() overwrites it */
worklconv.int_curr_symbol = strdup(extlconv->int_curr_symbol);
worklconv.currency_symbol = strdup(extlconv->currency_symbol);
worklconv.mon_decimal_point = strdup(extlconv->mon_decimal_point);
@@ -654,45 +594,19 @@ PGLC_localeconv(void)
worklconv.p_sign_posn = extlconv->p_sign_posn;
worklconv.n_sign_posn = extlconv->n_sign_posn;
- /*
- * Restore the prevailing locale settings; failure to do so is fatal.
- * Possibly we could limp along with nondefault LC_MONETARY or LC_NUMERIC,
- * but proceeding with the wrong value of LC_CTYPE would certainly be bad
- * news; and considering that the prevailing LC_MONETARY and LC_NUMERIC
- * are almost certainly "C", there's really no reason that restoring those
- * should fail.
- */
-#ifdef WIN32
- if (!setlocale(LC_CTYPE, save_lc_ctype))
- elog(FATAL, "failed to restore LC_CTYPE to \"%s\"", save_lc_ctype);
-#endif
- if (!setlocale(LC_MONETARY, save_lc_monetary))
- elog(FATAL, "failed to restore LC_MONETARY to \"%s\"", save_lc_monetary);
- if (!setlocale(LC_NUMERIC, save_lc_numeric))
- elog(FATAL, "failed to restore LC_NUMERIC to \"%s\"", save_lc_numeric);
+ /* Free the contents of the object populated by pg_localeconv_r(). */
+ pg_localeconv_free(&tmp);
+
+ /* If any of the preceding strdup calls failed, complain now. */
+ if (!struct_lconv_is_valid(&worklconv))
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
- /*
- * At this point we've done our best to clean up, and can call functions
- * that might possibly throw errors with a clean conscience. But let's
- * make sure we don't leak any already-strdup'd fields in worklconv.
- */
PG_TRY();
{
int encoding;
- /* Release the pstrdup'd locale names */
- pfree(save_lc_monetary);
- pfree(save_lc_numeric);
-#ifdef WIN32
- pfree(save_lc_ctype);
-#endif
-
- /* If any of the preceding strdup calls failed, complain now. */
- if (!struct_lconv_is_valid(&worklconv))
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("out of memory")));
-
/*
* Now we must perform encoding conversion from whatever's associated
* with the locales into the database encoding. If we can't identify