From 994d970c648b3ab13bd9120e03a97823628a5255 Mon Sep 17 00:00:00 2001
From: Jeff Davis <jeff@j-davis.com>
Date: Wed, 7 Aug 2024 12:25:37 -0700
Subject: [PATCH v2 5/6] Use resource owners to track locale_t and ICU collator
 objects.

Rather than rely on careful ordering of operations in error paths,
track collator objects with CurrentResourceOwner soon after they are
allocated. Then, move them to the new CollationCacheOwner when the
pg_locale_t is successfully allocated, right after copying the memory
to CollationCacheContext.
---
 src/backend/utils/adt/pg_locale.c | 72 ++++++++++++++++++++++++++++++-
 src/include/utils/pg_locale.h     |  4 +-
 2 files changed, 73 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index c52d6989802..47f902b8f59 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -66,6 +66,7 @@
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/pg_locale.h"
+#include "utils/resowner.h"
 #include "utils/syscache.h"
 
 #ifdef USE_ICU
@@ -151,6 +152,12 @@ typedef struct
 static MemoryContext CollationCacheContext = NULL;
 static collation_cache_hash *CollationCache = NULL;
 
+/*
+ * Collator objects (UCollator for ICU or locale_t for libc) are allocated in
+ * an external library, so track them using a resource owner.
+ */
+static ResourceOwner CollationCacheOwner = NULL;
+
 #if defined(WIN32) && defined(LC_MESSAGES)
 static char *IsoLocaleName(const char *);
 #endif
@@ -174,6 +181,24 @@ static void icu_set_collation_attributes(UCollator *collator, const char *loc,
 										 UErrorCode *status);
 #endif
 
+static void ResOwnerReleasePGLocale(Datum val);
+static void pg_freelocale(pg_locale_t val);
+
+static const ResourceOwnerDesc PGLocaleResourceKind =
+{
+	.name = "pg_locale_t reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_LAST,
+	.ReleaseResource = ResOwnerReleasePGLocale,
+	.DebugPrint = NULL			/* the default message is fine */
+};
+
+static void
+ResOwnerReleasePGLocale(Datum val)
+{
+	pg_freelocale((pg_locale_t) DatumGetPointer(val));
+}
+
 /*
  * POSIX doesn't define _l-variants of these functions, but several systems
  * have them.  We provide our own replacements here.
@@ -1707,6 +1732,7 @@ pg_newlocale_from_collation(Oid collid)
 	 */
 	if (CollationCache == NULL)
 	{
+		CollationCacheOwner = ResourceOwnerCreate(NULL, "collation cache");
 		CollationCacheContext = AllocSetContextCreate(TopMemoryContext,
 													  "collation cache",
 													  ALLOCSET_DEFAULT_SIZES);
@@ -1717,16 +1743,60 @@ pg_newlocale_from_collation(Oid collid)
 	cache_entry = collation_cache_insert(CollationCache, collid, &found);
 	if (!found || !cache_entry->locale)
 	{
-		pg_locale_t locale = create_pg_locale(collid, CollationCacheContext);
+		pg_locale_t locale;
+
+		ResourceOwnerEnlarge(CollationCacheOwner);
+
+		locale = create_pg_locale(collid, CollationCacheContext);
+		ResourceOwnerRemember(CurrentResourceOwner,
+							  PointerGetDatum(locale),
+							  &PGLocaleResourceKind);
 
 		check_collation_version(collid);
 
+		/* reassign locale from CurrentResourceOwner to CollationCacheOwner */
+		ResourceOwnerForget(CurrentResourceOwner,
+							PointerGetDatum(locale),
+							&PGLocaleResourceKind);
+		ResourceOwnerRemember(CollationCacheOwner,
+							  PointerGetDatum(locale),
+							  &PGLocaleResourceKind);
+
 		cache_entry->locale = locale;
 	}
 
 	return cache_entry->locale;
 }
 
+static void
+pg_freelocale(pg_locale_t locale)
+{
+	if (locale->provider == COLLPROVIDER_BUILTIN)
+	{
+		pfree(locale->info.builtin.locale);
+	}
+#ifdef USE_ICU
+	else if (locale->provider == COLLPROVIDER_ICU)
+	{
+		ucol_close(locale->info.icu.ucol);
+		pfree(locale->info.icu.locale);
+	}
+#endif
+	else if (locale->provider == COLLPROVIDER_LIBC)
+	{
+		if (locale->info.lt != NULL)
+		{
+#ifndef WIN32
+			freelocale(locale->info.lt);
+#else
+			_free_locale(locale->info.lt);
+#endif
+		}
+	}
+
+	pfree(locale);
+}
+
 /*
  * Get provider-specific collation version string for the given collation from
  * the operating system/library.
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index 1ca0b7fa74b..57096fe484f 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -92,13 +92,13 @@ struct pg_locale_struct
 	{
 		struct
 		{
-			const char *locale;
+			char	   *locale;
 		}			builtin;
 		locale_t	lt;
 #ifdef USE_ICU
 		struct
 		{
-			const char *locale;
+			char	   *locale;
 			UCollator  *ucol;
 		}			icu;
 #endif
-- 
2.34.1

