Re: [PATCH v3] libselinux: improve performance with pcre matches
Stephen Smalley <[email protected]> Fri, 24 Jul 2026 09:43:26 -0400
| Newsgroups | org.kernel.vger.selinux |
|---|---|
| Message-ID | <CAEjxPJ5=CmnH_fZVnCtpFPzCCgz6GxYdfD4zhVaViRAbX1EyEg@mail.gmail.com> |
On Thu, Jul 23, 2026 at 8:54 PM Inseob Kim <[email protected]> wrote: > > From: Carlo Marcelo Arenas Belón <[email protected]> > > Since 30b3e9d2 (libselinux: Workaround for heap overhead of pcre, > 2023-01-12), performance of PCRE2 matches has been affected due to > excessive recreation of the match_data in an attempt to reduce memory > utilization; instead of a workaround, it would be better to address > the problem and maybe even improve performance in the process. > > The issue is that currently the structure that holds PCRE state has > both a pcre2_code (which is per pattern) and a pcre2_match_data (which > is per match), forcing us to add a mutex to prevent multiple matches to > step on each other. > > Lets remove the match_data and the mutex and instead allocate one once > in a thread independent way that could be used and reused, by extending > our pthread interface to not only store TLS variables but also retrieve > them, and then use one of those. > > Since we are not interested on the capture groups (if any) lets only > allocate 1 pair which is all that will be needed and change the logic > so that a return of 0 (which means the pattern matched but there were > not enough capture spots) is also considered a match. > > This will ensure that the memory use would be bound to the number of > concurrent matches instead of the number of patterns and therefore > reduce the impact that recent changes on the way that the frames used > for matching are allocated have brought since PCRE2 10.41 was released. > > For cases where threads are not available, just keep it working in slow > mode as done before the workaround was reverted. > > Signed-off-by: Carlo Marcelo Arenas Belón <[email protected]> > Signed-off-by: Inseob Kim <[email protected]> Acked-by: Stephen Smalley <[email protected]> > --- > v2: > - Fix the dual destructor antipattern by separating the thread-exit > destructor (match_data_thread_free) from the library-unload key cleanup > (match_data_key_destroy, annotated with __attribute__((destructor))). > - Eliminate the data race on match_data_key_initialized by using pthread_once > (via __selinux_once) for safe, race-free initialization of match_data_key. > - Remove redundant match_data_initialized thread-local variable, making > lookups purely based on __selinux_getspecific(). > v3: > - Fix typos in the commit message, along with "PCRE2 10.41" > clarification > --- > libselinux/src/regex.c | 105 +++++++++++++++--------------- > libselinux/src/selinux_internal.h | 4 ++ > 2 files changed, 56 insertions(+), 53 deletions(-) > > diff --git a/libselinux/src/regex.c b/libselinux/src/regex.c > index 80b01692..b132b558 100644 > --- a/libselinux/src/regex.c > +++ b/libselinux/src/regex.c > @@ -36,6 +36,10 @@ > static struct regex_data *regex_data_create(void); > > #ifdef USE_PCRE2 > +static pthread_key_t match_data_key; > +static pthread_once_t match_data_key_once = PTHREAD_ONCE_INIT; > +static int match_data_key_alloc_failed = 0; > +static int match_data_key_created = 0; > static pthread_once_t once = PTHREAD_ONCE_INIT; > static char arch_string_buffer[32]; > > @@ -71,14 +75,6 @@ const char *regex_arch_string(void) > > struct regex_data { > pcre2_code *regex; /* compiled regular expression */ > -#ifndef AGGRESSIVE_FREE_AFTER_REGEX_MATCH > - /* > - * match data block required for the compiled > - * pattern in pcre2 > - */ > - pcre2_match_data *match_data; > -#endif > - pthread_mutex_t match_mutex; > }; > > int regex_prepare_data(struct regex_data **regex, char const *pattern_string, > @@ -98,13 +94,6 @@ int regex_prepare_data(struct regex_data **regex, char const *pattern_string, > goto err; > } > > -#ifndef AGGRESSIVE_FREE_AFTER_REGEX_MATCH > - (*regex)->match_data = > - pcre2_match_data_create_from_pattern((*regex)->regex, NULL); > - if (!(*regex)->match_data) { > - goto err; > - } > -#endif > return 0; > > err: > @@ -159,13 +148,6 @@ int regex_load_mmap(struct mmap_area *mmap_area, struct regex_data **regex, > if (rc != 1) > goto err; > > -#ifndef AGGRESSIVE_FREE_AFTER_REGEX_MATCH > - (*regex)->match_data = pcre2_match_data_create_from_pattern( > - (*regex)->regex, NULL); > - if (!(*regex)->match_data) > - goto err; > -#endif > - > *regex_compiled = true; > } > > @@ -223,18 +205,32 @@ out: > return rc; > } > > +static void match_data_thread_free(void *ptr) > +{ > + pcre2_match_data_free(ptr); > +} > + > +static void match_data_key_init(void) > +{ > + if (__selinux_key_create(&match_data_key, match_data_thread_free) == 0) > + match_data_key_created = 1; > + else > + match_data_key_alloc_failed = 1; > +} > + > +static void __attribute__((destructor)) match_data_key_destroy(void) > +{ > + if (match_data_key_created) { > + __selinux_key_delete(match_data_key); > + match_data_key_created = 0; > + } > +} > + > void regex_data_free(struct regex_data *regex) > { > if (regex) { > if (regex->regex) > pcre2_code_free(regex->regex); > - > -#ifndef AGGRESSIVE_FREE_AFTER_REGEX_MATCH > - if (regex->match_data) > - pcre2_match_data_free(regex->match_data); > -#endif > - > - __pthread_mutex_destroy(®ex->match_mutex); > free(regex); > } > } > @@ -242,31 +238,38 @@ void regex_data_free(struct regex_data *regex) > int regex_match(struct regex_data *regex, char const *subject, int partial) > { > int rc; > - pcre2_match_data *match_data; > - __pthread_mutex_lock(®ex->match_mutex); > + bool slow; > + pcre2_match_data *match_data = NULL; > + > + __selinux_once(match_data_key_once, match_data_key_init); > + > + if (!match_data_key_alloc_failed) { > + match_data = __selinux_getspecific(match_data_key); > + if (!match_data) { > + match_data = pcre2_match_data_create(1, NULL); > + if (match_data) { > + __selinux_setspecific(match_data_key, > + match_data); > + } > + } > + } > > -#ifdef AGGRESSIVE_FREE_AFTER_REGEX_MATCH > - match_data = pcre2_match_data_create_from_pattern(regex->regex, NULL); > - if (match_data == NULL) { > - __pthread_mutex_unlock(®ex->match_mutex); > - return REGEX_ERROR; > + slow = (match_data_key_alloc_failed || match_data == NULL); > + if (slow) { > + match_data = pcre2_match_data_create_from_pattern(regex->regex, > + NULL); > + if (!match_data) > + return REGEX_ERROR; > } > -#else > - match_data = regex->match_data; > -#endif > > - rc = pcre2_match(regex->regex, (PCRE2_SPTR)subject, > - PCRE2_ZERO_TERMINATED, 0, > - partial ? PCRE2_PARTIAL_SOFT : 0, match_data, NULL); > + rc = pcre2_match( > + regex->regex, (PCRE2_SPTR)subject, PCRE2_ZERO_TERMINATED, 0, > + partial ? PCRE2_PARTIAL_SOFT : 0, match_data, NULL); > > -#ifdef AGGRESSIVE_FREE_AFTER_REGEX_MATCH > - // pcre2_match allocates heap and it won't be freed until > - // pcre2_match_data_free, resulting in heap overhead. > - pcre2_match_data_free(match_data); > -#endif > + if (slow) > + pcre2_match_data_free(match_data); > > - __pthread_mutex_unlock(®ex->match_mutex); > - if (rc > 0) > + if (rc >= 0) > return REGEX_MATCH; > switch (rc) { > case PCRE2_ERROR_PARTIAL: > @@ -305,10 +308,6 @@ static struct regex_data *regex_data_create(void) > { > struct regex_data *regex_data = > (struct regex_data *)calloc(1, sizeof(struct regex_data)); > - if (!regex_data) > - return NULL; > - > - __pthread_mutex_init(®ex_data->match_mutex, NULL); > return regex_data; > } > > diff --git a/libselinux/src/selinux_internal.h b/libselinux/src/selinux_internal.h > index b0ef5a75..5feb5d97 100644 > --- a/libselinux/src/selinux_internal.h > +++ b/libselinux/src/selinux_internal.h > @@ -14,6 +14,7 @@ extern size_t selinux_page_size; > #pragma weak pthread_key_create > #pragma weak pthread_key_delete > #pragma weak pthread_setspecific > +#pragma weak pthread_getspecific > > /* Call handler iff the first call. */ > #define __selinux_once(ONCE_CONTROL, INIT_FUNCTION) \ > @@ -42,6 +43,9 @@ extern size_t selinux_page_size; > pthread_setspecific(KEY, VALUE); \ > } while (0) > > +#define __selinux_getspecific(KEY) \ > + (pthread_getspecific != NULL ? pthread_getspecific(KEY) : NULL) > + > /* selabel_lookup() is only thread safe if we're compiled with pthreads */ > > #pragma weak pthread_mutex_init > -- > 2.55.0.229.g6434b31f56-goog >