Allow regex operations to be terminated early by query cancel requests.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 1 Mar 2014 20:21:13 +0000 (15:21 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 1 Mar 2014 20:21:13 +0000 (15:21 -0500)
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.

src/backend/regex/regc_nfa.c
src/backend/regex/regcomp.c
src/backend/regex/regexec.c
src/backend/utils/adt/regexp.c
src/backend/utils/adt/varlena.c
src/include/regex/regerrs.h
src/include/regex/regex.h
src/include/regex/regguts.h

index c99a1f13d6aec17c3b568536136e8de7524a7581..e0e32344ca45d5a6b861c66373704863b71fe4d8 100644 (file)
@@ -174,11 +174,23 @@ newstate(struct nfa * nfa)
 {
    struct state *s;
 
+   /*
+    * This is a handy place to check for operation cancel during regex
+    * compilation, since no code path will go very long without making a new
+    * state.
+    */
+   if (CANCEL_REQUESTED(nfa->v->re))
+   {
+       NERR(REG_CANCEL);
+       return NULL;
+   }
+
    if (TooManyStates(nfa))
    {
        NERR(REG_ETOOBIG);
        return NULL;
    }
+
    if (nfa->free != NULL)
    {
        s = nfa->free;
index 71713f8538238e9add146eedca8d2826f1b7e4d7..8d9e58820f6068f626c1173c6777483c916069e8 100644 (file)
@@ -34,6 +34,8 @@
 
 #include "regex/regguts.h"
 
+#include "miscadmin.h"         /* needed by rcancelrequested() */
+
 /*
  * forward declarations, up here so forward datatypes etc. are defined early
  */
@@ -67,6 +69,7 @@ static long nfanode(struct vars *, struct subre *, FILE *);
 static int newlacon(struct vars *, struct state *, struct state *, int);
 static void freelacons(struct subre *, int);
 static void rfree(regex_t *);
+static int rcancelrequested(void);
 
 #ifdef REG_DEBUG
 static void dump(regex_t *, FILE *);
@@ -274,6 +277,7 @@ struct vars
 /* static function list */
 static struct fns functions = {
    rfree,                      /* regfree insides */
+   rcancelrequested            /* check for cancel request */
 };
 
 
@@ -1859,6 +1863,22 @@ 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.
+ */
+static int
+rcancelrequested(void)
+{
+   return InterruptPending && (QueryCancelPending || ProcDiePending);
+}
+
 #ifdef REG_DEBUG
 
 /*
index b64bac464a8fa971ecf2fce012c18a2cda246b35..06596e8e289bac3a507bb7f886f5363339934118 100644 (file)
@@ -710,6 +710,10 @@ cdissect(struct vars * v,
    assert(t != NULL);
    MDEBUG(("cdissect %ld-%ld %c\n", LOFF(begin), LOFF(end), t->op));
 
+   /* handy place to check for operation cancel */
+   if (CANCEL_REQUESTED(v->re))
+       return REG_CANCEL;
+
    switch (t->op)
    {
        case '=':               /* terminal node */
index 83f124677fb04544ef820116a9f45654eb6571d7..a5d7c83d84443b3d7420cfef4e9d221cfb4239c1 100644 (file)
@@ -31,6 +31,7 @@
 
 #include "catalog/pg_type.h"
 #include "funcapi.h"
+#include "miscadmin.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
 #include "utils/guc.h"
@@ -187,6 +188,15 @@ RE_compile_and_cache(text *text_re, int cflags)
    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();
+
        pg_regerror(regcomp_result, &re_temp.cre_re, errMsg, sizeof(errMsg));
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
@@ -266,6 +276,7 @@ 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),
@@ -1193,6 +1204,7 @@ regexp_fixed_prefix(text *text_re, bool case_insensitive,
 
        default:
            /* re failed??? */
+           CHECK_FOR_INTERRUPTS();
            pg_regerror(re_result, re, errMsg, sizeof(errMsg));
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
index ecf3b83b1d5ff550013391e1fd1477c07d989d92..59c0c0282b78d673fec906a29ec4ddc554feb29b 100644 (file)
@@ -2576,6 +2576,7 @@ replace_text_regexp(text *src_text, void *regexp,
        {
            char        errMsg[100];
 
+           CHECK_FOR_INTERRUPTS();
            pg_regerror(regexec_result, re, errMsg, sizeof(errMsg));
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
index 15da324b66041534117169c7e6c3f54a889333a7..956b56b50ee305764bc780e3917f8bcb4c83a385 100644 (file)
@@ -81,3 +81,7 @@
 {
    REG_ECOLORS, "REG_ECOLORS", "too many colors"
 },
+
+{
+   REG_CANCEL, "REG_CANCEL", "operation cancelled"
+},
index 0ab02203124b7acf3d517f32ef815ea0a3c49833..4b35b0ccd765cf6430b876f6ea903ec2c783785f 100644 (file)
@@ -153,6 +153,7 @@ typedef struct
 #define REG_BADOPT 18          /* invalid embedded option */
 #define REG_ETOOBIG 19         /* nfa has too many states */
 #define REG_ECOLORS 20         /* too many colors */
+#define REG_CANCEL 21          /* operation cancelled */
 /* two specials for debugging and testing */
 #define REG_ATOI   101         /* convert error-code name to number */
 #define REG_ITOA   102         /* convert error-code number to name */
index 9cd0ef9fdf6d07fa30d65c0729c68241a68fb1a4..1a0a8bd7cd45e627eeaf18f89374ee88e8e91497 100644 (file)
@@ -403,8 +403,11 @@ struct subre
 struct fns
 {
    void        FUNCPTR(free, (regex_t *));
+   int         FUNCPTR(cancel_requested, (void));
 };
 
+#define CANCEL_REQUESTED(re)  \
+   ((*((struct fns *) (re)->re_fns)->cancel_requested) ())
 
 
 /*