On 1/20/2026 2:39 PM, Peter Eisentraut wrote:
> On 08.01.26 15:57, Bryan Green wrote:
>> I agree with the above changes and have implemented them, including the
>> correction to the cutoff version.  But, before sharing the patch with
>> those changes I think we should discuss 1) should we short-circuit
>> C/POSIX and not ever call gettext in that case,
> 
> You had written that you had submitted a patch to gettext to handle that
> there.  Has that gotten anywhere?
> 
>> 2) should we try to
>> convert "ISO" to Windows legacy format.
> 
> I don't know.  We can just tell users to set their locale in the right
> format.
Peter,
I have attached the patch with the changes you suggested/requested.  The
patch was added to gnulib in December.  The latest release of gnu
gettext (1.0) does include the patch.  Yes, they jumped from 0.26 to 1.0.

-- 
Bryan Green
EDB: https://www.enterprisedb.com
From 01716e1912c6ff5ccd2a992c57b97bff749f441d Mon Sep 17 00:00:00 2001
From: Bryan Green <[email protected]>
Date: Wed, 4 Feb 2026 08:37:32 -0600
Subject: [PATCH v2] Avoid gettext 0.20+ performance bug on Windows.

gettext 0.20+ expects Windows locale format ("English_United States")
not POSIX format ("en_US"), and has a cache bug where failed lookups
cause repeated enumeration of all ~259 system locales on every gettext()
call.  This makes exception-heavy workloads 5-6x slower.

PostgreSQL converts to POSIX format via IsoLocaleName() before setting
LC_MESSAGES, which triggers this bug.  Setting lc_messages to 'C'
or 'POSIX' triggers it too, since these aren't Windows names.

Fix by using Windows format for gettext 0.20+, which handles it
correctly.  Retain POSIX format for 0.19.8 and earlier.  Detect version
via LIBINTL_VERSION macro.  Map "C" and "POSIX" to the current LC_CTYPE
locale to avoid the enumeration bug for those special values.

Mark IsoLocaleName() with pg_attribute_unused() to suppress compiler
warnings when building with gettext 0.20+, as the function becomes
unreferenced in that configuration.

We should strongly suggest that legacy windows locale format and not
POSIX format be used on Windows.  The caching bug in gnulib has been
fixed and gnu gettext includes the fixed gnulib in version 1.0.

Improves 1M exception test from ~180s to ~40s.

---
 src/backend/utils/adt/pg_locale.c | 31 ++++++++++++++++++++++++++++++-
 1 file changed, 30 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/pg_locale.c 
b/src/backend/utils/adt/pg_locale.c
index ac324ecaad..4ab6b2703e 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -233,10 +233,38 @@ pg_perm_setlocale(int category, const char *locale)
                case LC_MESSAGES:
                        envvar = "LC_MESSAGES";
 #ifdef WIN32
+
+                       /*
+                        * gettext 0.20+ expects Windows locale names (e.g.,
+                        * "English_United States") rather than POSIX names 
(e.g.,
+                        * "en_US").  When given a POSIX name, its get_lcid() 
function
+                        * enumerates all ~259 system locales looking for a 
match, fails,
+                        * and due to a cache bug, repeats this enumeration on 
every
+                        * gettext() call.  This causes exception-heavy 
workloads to be
+                        * 5-6x slower.
+                        *
+                        * For gettext 0.20+, pass the locale through as-is 
(it's already
+                        * in Windows format from setlocale()).  For older 
versions, convert
+                        * to POSIX/ISO format via IsoLocaleName() since they 
expect that.
+                        *
+                        * "C" and "POSIX" are not valid Windows locale names 
and would
+                        * trigger the same enumeration bug, so map them to the 
current
+                        * LC_CTYPE locale instead.
+                        *
+                        * Note: locale is guaranteed non-NULL and non-empty 
here, as those
+                        * cases are handled earlier in the LC_MESSAGES block 
above.
+                        */
+#if defined(LIBINTL_VERSION) && (LIBINTL_VERSION >= 0x001400)
+                       if (strcmp(locale, "C") == 0 || strcmp(locale, "POSIX") 
== 0)
+                               result = setlocale(LC_CTYPE, NULL);
+                       else
+                               result = (char *) locale;
+#else
                        result = IsoLocaleName(locale);
                        if (result == NULL)
                                result = (char *) locale;
-                       elog(DEBUG3, "IsoLocaleName() executed; locale: 
\"%s\"", result);
+#endif
+                       elog(DEBUG3, "LC_MESSAGES locale: \"%s\"", result);
 #endif                                                 /* WIN32 */
                        break;
 #endif                                                 /* LC_MESSAGES */
@@ -1025,6 +1053,7 @@ get_iso_localename(const char *winlocname)
        return NULL;
 }
 
+pg_attribute_unused()
 static char *
 IsoLocaleName(const char *winlocname)
 {
-- 
2.52.0.windows.1

Reply via email to