Skip to content

Commit a9eb492

Browse files
committed
Allow regex operations to be terminated early by query cancel requests.
The regex code didn't have any provision for query cancel; which is unsurprising given its non-Postgres origin, but still problematic since some operations can take a long time. Introduce a callback function to check for a pending query cancel or session termination request, and call it in a couple of strategic spots where we can make the regex code exit with an error indicator. If we ever actually split out the regex code as a standalone library, some additional work will be needed to let the cancel callback function be specified externally to the library. But that's straightforward (certainly so by comparison to putting the locale-dependent character classification logic on a similar arms-length basis), and there seems no need to do it right now. A bigger issue is that there may be more places than these two where we need to check for cancels. We can always add more checks later, now that the infrastructure is in place. Since there are known examples of not-terribly-long regexes that can lock up a backend for a long time, back-patch to all supported branches. I have hopes of fixing the known performance problems later, but adding query cancel ability seems like a good idea even if they were all fixed.
1 parent 3e2db4c commit a9eb492

File tree

8 files changed

+57
-0
lines changed

8 files changed

+57
-0
lines changed

src/backend/regex/regc_nfa.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,23 @@ newstate(struct nfa * nfa)
174174
{
175175
struct state *s;
176176

177+
/*
178+
* This is a handy place to check for operation cancel during regex
179+
* compilation, since no code path will go very long without making a new
180+
* state.
181+
*/
182+
if (CANCEL_REQUESTED(nfa->v->re))
183+
{
184+
NERR(REG_CANCEL);
185+
return NULL;
186+
}
187+
177188
if (TooManyStates(nfa))
178189
{
179190
NERR(REG_ETOOBIG);
180191
return NULL;
181192
}
193+
182194
if (nfa->free != NULL)
183195
{
184196
s = nfa->free;

src/backend/regex/regcomp.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434

3535
#include "regex/regguts.h"
3636

37+
#include "miscadmin.h" /* needed by rcancelrequested() */
38+
3739
/*
3840
* forward declarations, up here so forward datatypes etc. are defined early
3941
*/
@@ -67,6 +69,7 @@ static long nfanode(struct vars *, struct subre *, FILE *);
6769
static int newlacon(struct vars *, struct state *, struct state *, int);
6870
static void freelacons(struct subre *, int);
6971
static void rfree(regex_t *);
72+
static int rcancelrequested(void);
7073

7174
#ifdef REG_DEBUG
7275
static void dump(regex_t *, FILE *);
@@ -276,6 +279,7 @@ struct vars
276279
/* static function list */
277280
static struct fns functions = {
278281
rfree, /* regfree insides */
282+
rcancelrequested /* check for cancel request */
279283
};
280284

281285

@@ -1876,6 +1880,22 @@ rfree(regex_t *re)
18761880
}
18771881
}
18781882

1883+
/*
1884+
* rcancelrequested - check for external request to cancel regex operation
1885+
*
1886+
* Return nonzero to fail the operation with error code REG_CANCEL,
1887+
* zero to keep going
1888+
*
1889+
* The current implementation is Postgres-specific. If we ever get around
1890+
* to splitting the regex code out as a standalone library, there will need
1891+
* to be some API to let applications define a callback function for this.
1892+
*/
1893+
static int
1894+
rcancelrequested(void)
1895+
{
1896+
return InterruptPending && (QueryCancelPending || ProcDiePending);
1897+
}
1898+
18791899
#ifdef REG_DEBUG
18801900

18811901
/*

src/backend/regex/regexec.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,10 @@ cdissect(struct vars * v,
713713
assert(t != NULL);
714714
MDEBUG(("cdissect %ld-%ld %c\n", LOFF(begin), LOFF(end), t->op));
715715

716+
/* handy place to check for operation cancel */
717+
if (CANCEL_REQUESTED(v->re))
718+
return REG_CANCEL;
719+
716720
switch (t->op)
717721
{
718722
case '=': /* terminal node */

src/backend/utils/adt/regexp.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
#include "catalog/pg_type.h"
3333
#include "funcapi.h"
34+
#include "miscadmin.h"
3435
#include "regex/regex.h"
3536
#include "utils/builtins.h"
3637
#include "utils/guc.h"
@@ -188,6 +189,15 @@ RE_compile_and_cache(text *text_re, int cflags, Oid collation)
188189
if (regcomp_result != REG_OKAY)
189190
{
190191
/* re didn't compile (no need for pg_regfree, if so) */
192+
193+
/*
194+
* Here and in other places in this file, do CHECK_FOR_INTERRUPTS
195+
* before reporting a regex error. This is so that if the regex
196+
* library aborts and returns REG_CANCEL, we don't print an error
197+
* message that implies the regex was invalid.
198+
*/
199+
CHECK_FOR_INTERRUPTS();
200+
191201
pg_regerror(regcomp_result, &re_temp.cre_re, errMsg, sizeof(errMsg));
192202
ereport(ERROR,
193203
(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
@@ -268,6 +278,7 @@ RE_wchar_execute(regex_t *re, pg_wchar *data, int data_len,
268278
if (regexec_result != REG_OKAY && regexec_result != REG_NOMATCH)
269279
{
270280
/* re failed??? */
281+
CHECK_FOR_INTERRUPTS();
271282
pg_regerror(regexec_result, re, errMsg, sizeof(errMsg));
272283
ereport(ERROR,
273284
(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
@@ -1216,6 +1227,7 @@ regexp_fixed_prefix(text *text_re, bool case_insensitive, Oid collation,
12161227

12171228
default:
12181229
/* re failed??? */
1230+
CHECK_FOR_INTERRUPTS();
12191231
pg_regerror(re_result, re, errMsg, sizeof(errMsg));
12201232
ereport(ERROR,
12211233
(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),

src/backend/utils/adt/varlena.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2854,6 +2854,7 @@ replace_text_regexp(text *src_text, void *regexp,
28542854
{
28552855
char errMsg[100];
28562856

2857+
CHECK_FOR_INTERRUPTS();
28572858
pg_regerror(regexec_result, re, errMsg, sizeof(errMsg));
28582859
ereport(ERROR,
28592860
(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),

src/include/regex/regerrs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,7 @@
8181
{
8282
REG_ECOLORS, "REG_ECOLORS", "too many colors"
8383
},
84+
85+
{
86+
REG_CANCEL, "REG_CANCEL", "operation cancelled"
87+
},

src/include/regex/regex.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ typedef struct
154154
#define REG_BADOPT 18 /* invalid embedded option */
155155
#define REG_ETOOBIG 19 /* nfa has too many states */
156156
#define REG_ECOLORS 20 /* too many colors */
157+
#define REG_CANCEL 21 /* operation cancelled */
157158
/* two specials for debugging and testing */
158159
#define REG_ATOI 101 /* convert error-code name to number */
159160
#define REG_ITOA 102 /* convert error-code number to name */

src/include/regex/regguts.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,8 +403,11 @@ struct subre
403403
struct fns
404404
{
405405
void FUNCPTR(free, (regex_t *));
406+
int FUNCPTR(cancel_requested, (void));
406407
};
407408

409+
#define CANCEL_REQUESTED(re) \
410+
((*((struct fns *) (re)->re_fns)->cancel_requested) ())
408411

409412

410413
/*

0 commit comments

Comments
 (0)