Skip to content

Commit b98be8a

Browse files
committed
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 f1da075), 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
1 parent 4a02af8 commit b98be8a

File tree

9 files changed

+403
-108
lines changed

9 files changed

+403
-108
lines changed

configure

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15401,7 +15401,7 @@ fi
1540115401
LIBS_including_readline="$LIBS"
1540215402
LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'`
1540315403

15404-
for ac_func in backtrace_symbols copyfile copy_file_range elf_aux_info getauxval getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strchrnul strsignal syncfs sync_file_range uselocale wcstombs_l
15404+
for ac_func in backtrace_symbols copyfile copy_file_range elf_aux_info getauxval getifaddrs getpeerucred inet_pton kqueue localeconv_l mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strchrnul strsignal syncfs sync_file_range uselocale wcstombs_l
1540515405
do :
1540615406
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
1540715407
ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"

configure.ac

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1764,6 +1764,7 @@ AC_CHECK_FUNCS(m4_normalize([
17641764
getpeerucred
17651765
inet_pton
17661766
kqueue
1767+
localeconv_l
17671768
mbstowcs_l
17681769
memset_s
17691770
posix_fallocate

meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2738,6 +2738,7 @@ func_checks = [
27382738
['inet_aton'],
27392739
['inet_pton'],
27402740
['kqueue'],
2741+
['localeconv_l'],
27412742
['mbstowcs_l'],
27422743
['memset_s'],
27432744
['mkdtemp'],

src/backend/utils/adt/pg_locale.c

Lines changed: 21 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -547,12 +547,8 @@ PGLC_localeconv(void)
547547
static struct lconv CurrentLocaleConv;
548548
static bool CurrentLocaleConvAllocated = false;
549549
struct lconv *extlconv;
550-
struct lconv worklconv;
551-
char *save_lc_monetary;
552-
char *save_lc_numeric;
553-
#ifdef WIN32
554-
char *save_lc_ctype;
555-
#endif
550+
struct lconv tmp;
551+
struct lconv worklconv = {0};
556552

557553
/* Did we do it already? */
558554
if (CurrentLocaleConvValid)
@@ -566,77 +562,21 @@ PGLC_localeconv(void)
566562
}
567563

568564
/*
569-
* This is tricky because we really don't want to risk throwing error
570-
* while the locale is set to other than our usual settings. Therefore,
571-
* the process is: collect the usual settings, set locale to special
572-
* setting, copy relevant data into worklconv using strdup(), restore
573-
* normal settings, convert data to desired encoding, and finally stash
574-
* the collected data in CurrentLocaleConv. This makes it safe if we
575-
* throw an error during encoding conversion or run out of memory anywhere
576-
* in the process. All data pointed to by struct lconv members is
577-
* allocated with strdup, to avoid premature elog(ERROR) and to allow
578-
* using a single cleanup routine.
565+
* Use thread-safe method of obtaining a copy of lconv from the operating
566+
* system.
579567
*/
580-
memset(&worklconv, 0, sizeof(worklconv));
581-
582-
/* Save prevailing values of monetary and numeric locales */
583-
save_lc_monetary = setlocale(LC_MONETARY, NULL);
584-
if (!save_lc_monetary)
585-
elog(ERROR, "setlocale(NULL) failed");
586-
save_lc_monetary = pstrdup(save_lc_monetary);
587-
588-
save_lc_numeric = setlocale(LC_NUMERIC, NULL);
589-
if (!save_lc_numeric)
590-
elog(ERROR, "setlocale(NULL) failed");
591-
save_lc_numeric = pstrdup(save_lc_numeric);
592-
593-
#ifdef WIN32
594-
595-
/*
596-
* The POSIX standard explicitly says that it is undefined what happens if
597-
* LC_MONETARY or LC_NUMERIC imply an encoding (codeset) different from
598-
* that implied by LC_CTYPE. In practice, all Unix-ish platforms seem to
599-
* believe that localeconv() should return strings that are encoded in the
600-
* codeset implied by the LC_MONETARY or LC_NUMERIC locale name. Hence,
601-
* once we have successfully collected the localeconv() results, we will
602-
* convert them from that codeset to the desired server encoding.
603-
*
604-
* Windows, of course, resolutely does things its own way; on that
605-
* platform LC_CTYPE has to match LC_MONETARY/LC_NUMERIC to get sane
606-
* results. Hence, we must temporarily set that category as well.
607-
*/
608-
609-
/* Save prevailing value of ctype locale */
610-
save_lc_ctype = setlocale(LC_CTYPE, NULL);
611-
if (!save_lc_ctype)
612-
elog(ERROR, "setlocale(NULL) failed");
613-
save_lc_ctype = pstrdup(save_lc_ctype);
614-
615-
/* Here begins the critical section where we must not throw error */
616-
617-
/* use numeric to set the ctype */
618-
setlocale(LC_CTYPE, locale_numeric);
619-
#endif
620-
621-
/* Get formatting information for numeric */
622-
setlocale(LC_NUMERIC, locale_numeric);
623-
extlconv = localeconv();
624-
625-
/* Must copy data now in case setlocale() overwrites it */
568+
if (pg_localeconv_r(locale_monetary,
569+
locale_numeric,
570+
&tmp) != 0)
571+
elog(ERROR,
572+
"could not get lconv for LC_MONETARY = \"%s\", LC_NUMERIC = \"%s\": %m",
573+
locale_monetary, locale_numeric);
574+
575+
/* Must copy data now now so we can re-encode it. */
576+
extlconv = &tmp;
626577
worklconv.decimal_point = strdup(extlconv->decimal_point);
627578
worklconv.thousands_sep = strdup(extlconv->thousands_sep);
628579
worklconv.grouping = strdup(extlconv->grouping);
629-
630-
#ifdef WIN32
631-
/* use monetary to set the ctype */
632-
setlocale(LC_CTYPE, locale_monetary);
633-
#endif
634-
635-
/* Get formatting information for monetary */
636-
setlocale(LC_MONETARY, locale_monetary);
637-
extlconv = localeconv();
638-
639-
/* Must copy data now in case setlocale() overwrites it */
640580
worklconv.int_curr_symbol = strdup(extlconv->int_curr_symbol);
641581
worklconv.currency_symbol = strdup(extlconv->currency_symbol);
642582
worklconv.mon_decimal_point = strdup(extlconv->mon_decimal_point);
@@ -654,45 +594,19 @@ PGLC_localeconv(void)
654594
worklconv.p_sign_posn = extlconv->p_sign_posn;
655595
worklconv.n_sign_posn = extlconv->n_sign_posn;
656596

657-
/*
658-
* Restore the prevailing locale settings; failure to do so is fatal.
659-
* Possibly we could limp along with nondefault LC_MONETARY or LC_NUMERIC,
660-
* but proceeding with the wrong value of LC_CTYPE would certainly be bad
661-
* news; and considering that the prevailing LC_MONETARY and LC_NUMERIC
662-
* are almost certainly "C", there's really no reason that restoring those
663-
* should fail.
664-
*/
665-
#ifdef WIN32
666-
if (!setlocale(LC_CTYPE, save_lc_ctype))
667-
elog(FATAL, "failed to restore LC_CTYPE to \"%s\"", save_lc_ctype);
668-
#endif
669-
if (!setlocale(LC_MONETARY, save_lc_monetary))
670-
elog(FATAL, "failed to restore LC_MONETARY to \"%s\"", save_lc_monetary);
671-
if (!setlocale(LC_NUMERIC, save_lc_numeric))
672-
elog(FATAL, "failed to restore LC_NUMERIC to \"%s\"", save_lc_numeric);
597+
/* Free the contents of the object populated by pg_localeconv_r(). */
598+
pg_localeconv_free(&tmp);
599+
600+
/* If any of the preceding strdup calls failed, complain now. */
601+
if (!struct_lconv_is_valid(&worklconv))
602+
ereport(ERROR,
603+
(errcode(ERRCODE_OUT_OF_MEMORY),
604+
errmsg("out of memory")));
673605

674-
/*
675-
* At this point we've done our best to clean up, and can call functions
676-
* that might possibly throw errors with a clean conscience. But let's
677-
* make sure we don't leak any already-strdup'd fields in worklconv.
678-
*/
679606
PG_TRY();
680607
{
681608
int encoding;
682609

683-
/* Release the pstrdup'd locale names */
684-
pfree(save_lc_monetary);
685-
pfree(save_lc_numeric);
686-
#ifdef WIN32
687-
pfree(save_lc_ctype);
688-
#endif
689-
690-
/* If any of the preceding strdup calls failed, complain now. */
691-
if (!struct_lconv_is_valid(&worklconv))
692-
ereport(ERROR,
693-
(errcode(ERRCODE_OUT_OF_MEMORY),
694-
errmsg("out of memory")));
695-
696610
/*
697611
* Now we must perform encoding conversion from whatever's associated
698612
* with the locales into the database encoding. If we can't identify

src/include/pg_config.h.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,9 @@
268268
/* Define to 1 if you have the `zstd' library (-lzstd). */
269269
#undef HAVE_LIBZSTD
270270

271+
/* Define to 1 if you have the `localeconv_l' function. */
272+
#undef HAVE_LOCALECONV_L
273+
271274
/* Define to 1 if you have the <mbarrier.h> header file. */
272275
#undef HAVE_MBARRIER_H
273276

src/include/port.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,12 @@ extern void *bsearch_arg(const void *key, const void *base0,
487487
int (*compar) (const void *, const void *, void *),
488488
void *arg);
489489

490+
/* port/pg_localeconv_r.c */
491+
extern int pg_localeconv_r(const char *lc_monetary,
492+
const char *lc_numeric,
493+
struct lconv *output);
494+
extern void pg_localeconv_free(struct lconv *lconv);
495+
490496
/* port/chklocale.c */
491497
extern int pg_get_encoding_from_locale(const char *ctype, bool write_message);
492498

src/port/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ OBJS = \
4444
noblock.o \
4545
path.o \
4646
pg_bitutils.o \
47+
pg_localeconv_r.o \
4748
pg_popcount_avx512.o \
4849
pg_strong_random.o \
4950
pgcheckdir.o \

src/port/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pgport_sources = [
77
'noblock.c',
88
'path.c',
99
'pg_bitutils.c',
10+
'pg_localeconv_r.c',
1011
'pg_popcount_avx512.c',
1112
'pg_strong_random.c',
1213
'pgcheckdir.c',

0 commit comments

Comments
 (0)