v4-0001-Use-MemoryContext-API-for-regexp-memory-managemen.patch
text/x-patch
Filename: v4-0001-Use-MemoryContext-API-for-regexp-memory-managemen.patch
Type: text/x-patch
Part: 0
Patch
Format: format-patch
Series: patch v4-0001
Subject: Use MemoryContext API for regexp memory management.
| File | + | − |
|---|---|---|
| src/backend/regex/regcomp.c | 7 | 7 |
| src/backend/utils/adt/regexp.c | 42 | 28 |
| src/include/regex/regcustom.h | 3 | 3 |
From ee8eafb249368b889308fa23704f2a34a575b254 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Wed, 4 Jan 2023 14:15:40 +1300
Subject: [PATCH v4 1/2] Use MemoryContext API for regexp memory management.
Previously, regex_t objects' memory was managed using malloc() and
free() directly. Since regexp compilation can take same time, regcomp()
would periodically call a callback function that would check for query
cancelation. That design assumed that asynchronous query cancelation
could be detected by reading flags set by signal handlers, and that the
flags were a reliable indication that a later CHECK_FOR_INTERRUPTS()
would exit or throw an error. This allowed the code to free memory
before aborting.
A later commit will refactor the recovery conflict interrupt system, to
move its logic out of signal handlers, because it is not
async-signal-safe (among other problems). That would break the above
assumption, so we need another approach to memory cleanup.
Switch to using palloc(), the standard mechanism for garbage collection
in PostgreSQL. Since regex_t objects have to survive across transaction
boundaries in a cache, introduce RegexpCacheMemoryContext. Since
partial regex_t objects need to be cleaned up on failure to compile due
to interruption, also give each regex_t its own context. It is
re-parented to the longer living RegexpCacheMemoryContext only if
compilation is successful, following a pattern seen elsewhere in the
tree.
XXX Experimental, may contain nuts
---
src/backend/regex/regcomp.c | 14 +++----
src/backend/utils/adt/regexp.c | 70 ++++++++++++++++++++--------------
src/include/regex/regcustom.h | 6 +--
3 files changed, 52 insertions(+), 38 deletions(-)
diff --git a/src/backend/regex/regcomp.c b/src/backend/regex/regcomp.c
index bb8c240598..c0f8e77b49 100644
--- a/src/backend/regex/regcomp.c
+++ b/src/backend/regex/regcomp.c
@@ -2471,17 +2471,17 @@ rfree(regex_t *re)
/*
* rcancelrequested - check for external request to cancel regex operation
*
- * Return nonzero to fail the operation with error code REG_CANCEL,
- * zero to keep going
- *
- * The current implementation is Postgres-specific. If we ever get around
- * to splitting the regex code out as a standalone library, there will need
- * to be some API to let applications define a callback function for this.
+ * The current implementation always returns 0, if CHECK_FOR_INTERRUPTS()
+ * doesn't exit non-locally via ereport(). Memory allocated while compiling is
+ * expected to be cleaned up by virtue of being allocated using palloc in a
+ * suitable memory context.
*/
static int
rcancelrequested(void)
{
- return InterruptPending && (QueryCancelPending || ProcDiePending);
+ CHECK_FOR_INTERRUPTS();
+
+ return 0;
}
/*
diff --git a/src/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index 810dcb85b6..0336bf70f3 100644
--- a/src/backend/utils/adt/regexp.c
+++ b/src/backend/utils/adt/regexp.c
@@ -96,9 +96,13 @@ typedef struct regexp_matches_ctx
#define MAX_CACHED_RES 32
#endif
+/* A parent memory context for regular expressions. */
+static MemoryContext RegexpCacheMemoryContext;
+
/* this structure describes one cached regular expression */
typedef struct cached_re_str
{
+ MemoryContext cre_context; /* memory context for this regexp */
char *cre_pat; /* original RE (not null terminated!) */
int cre_pat_len; /* length of original RE, in bytes */
int cre_flags; /* compile flags: extended,icase etc */
@@ -145,6 +149,7 @@ RE_compile_and_cache(text *text_re, int cflags, Oid collation)
int regcomp_result;
cached_re_str re_temp;
char errMsg[100];
+ MemoryContext oldcontext;
/*
* Look for a match among previously compiled REs. Since the data
@@ -172,6 +177,13 @@ RE_compile_and_cache(text *text_re, int cflags, Oid collation)
}
}
+ /* Set up the cache memory on first go through. */
+ if (unlikely(RegexpCacheMemoryContext == NULL))
+ RegexpCacheMemoryContext =
+ AllocSetContextCreate(TopMemoryContext,
+ "RegexpCacheMemoryContext",
+ ALLOCSET_SMALL_SIZES);
+
/*
* Couldn't find it, so try to compile the new RE. To avoid leaking
* resources on failure, we build into the re_temp local.
@@ -183,6 +195,18 @@ RE_compile_and_cache(text *text_re, int cflags, Oid collation)
pattern,
text_re_len);
+ /*
+ * Make a memory context for this compiled regexp. This is initially a
+ * child of the current memory context, so it will be cleaned up
+ * automatically if compilation is interrupted and throws an ERROR.
+ * We'll re-parent it under the longer lived cache context if we make it
+ * to the bottom of this function.
+ */
+ re_temp.cre_context = AllocSetContextCreate(CurrentMemoryContext,
+ "RegexpMemoryContext",
+ ALLOCSET_SMALL_SIZES);
+ oldcontext = MemoryContextSwitchTo(re_temp.cre_context);
+
regcomp_result = pg_regcomp(&re_temp.cre_re,
pattern,
pattern_len,
@@ -193,37 +217,24 @@ RE_compile_and_cache(text *text_re, int cflags, Oid collation)
if (regcomp_result != REG_OKAY)
{
- /* re didn't compile (no need for pg_regfree, if so) */
-
- /*
- * Here and in other places in this file, do CHECK_FOR_INTERRUPTS
- * before reporting a regex error. This is so that if the regex
- * library aborts and returns REG_CANCEL, we don't print an error
- * message that implies the regex was invalid.
- */
- CHECK_FOR_INTERRUPTS();
-
+ /* re didn't compile */
pg_regerror(regcomp_result, &re_temp.cre_re, errMsg, sizeof(errMsg));
ereport(ERROR,
(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
errmsg("invalid regular expression: %s", errMsg)));
}
+ /* Copy the pattern into the per-regexp memory context. */
+ re_temp.cre_pat = palloc(text_re_len + 1);
+ memcpy(re_temp.cre_pat, text_re_val, text_re_len);
+
/*
- * We use malloc/free for the cre_pat field because the storage has to
- * persist across transactions, and because we want to get control back on
- * out-of-memory. The Max() is because some malloc implementations return
- * NULL for malloc(0).
+ * NUL-terminate it only for the benefit of the identifier used for the
+ * memory context, visible int he pg_backend_memory_contexts view.
*/
- re_temp.cre_pat = malloc(Max(text_re_len, 1));
- if (re_temp.cre_pat == NULL)
- {
- pg_regfree(&re_temp.cre_re);
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("out of memory")));
- }
- memcpy(re_temp.cre_pat, text_re_val, text_re_len);
+ re_temp.cre_pat[text_re_len] = 0;
+ MemoryContextSetIdentifier(re_temp.cre_context, re_temp.cre_pat);
+
re_temp.cre_pat_len = text_re_len;
re_temp.cre_flags = cflags;
re_temp.cre_collation = collation;
@@ -236,16 +247,21 @@ RE_compile_and_cache(text *text_re, int cflags, Oid collation)
{
--num_res;
Assert(num_res < MAX_CACHED_RES);
- pg_regfree(&re_array[num_res].cre_re);
- free(re_array[num_res].cre_pat);
+ /* Delete the memory context holding the regexp and pattern. */
+ MemoryContextDelete(re_array[num_res].cre_context);
}
+ /* Re-parent the memory context to our long-lived cache context. */
+ MemoryContextSetParent(re_temp.cre_context, RegexpCacheMemoryContext);
+
if (num_res > 0)
memmove(&re_array[1], &re_array[0], num_res * sizeof(cached_re_str));
re_array[0] = re_temp;
num_res++;
+ MemoryContextSwitchTo(oldcontext);
+
return &re_array[0].cre_re;
}
@@ -283,7 +299,6 @@ RE_wchar_execute(regex_t *re, pg_wchar *data, int data_len,
if (regexec_result != REG_OKAY && regexec_result != REG_NOMATCH)
{
/* re failed??? */
- CHECK_FOR_INTERRUPTS();
pg_regerror(regexec_result, re, errMsg, sizeof(errMsg));
ereport(ERROR,
(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
@@ -1976,7 +1991,6 @@ regexp_fixed_prefix(text *text_re, bool case_insensitive, Oid collation,
default:
/* re failed??? */
- CHECK_FOR_INTERRUPTS();
pg_regerror(re_result, re, errMsg, sizeof(errMsg));
ereport(ERROR,
(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
@@ -1990,7 +2004,7 @@ regexp_fixed_prefix(text *text_re, bool case_insensitive, Oid collation,
slen = pg_wchar2mb_with_len(str, result, slen);
Assert(slen < maxlen);
- free(str);
+ pfree(str);
return result;
}
diff --git a/src/include/regex/regcustom.h b/src/include/regex/regcustom.h
index fc158e1bb7..8f4025128e 100644
--- a/src/include/regex/regcustom.h
+++ b/src/include/regex/regcustom.h
@@ -49,9 +49,9 @@
/* overrides for regguts.h definitions, if any */
#define FUNCPTR(name, args) (*name) args
-#define MALLOC(n) malloc(n)
-#define FREE(p) free(VS(p))
-#define REALLOC(p,n) realloc(VS(p),n)
+#define MALLOC(n) palloc_extended((n), MCXT_ALLOC_NO_OOM)
+#define FREE(p) pfree(VS(p))
+#define REALLOC(p,n) repalloc_extended(VS(p),(n), MCXT_ALLOC_NO_OOM)
#define assert(x) Assert(x)
/* internal character type and related */
--
2.35.1