Suppress complaints about leaks in TS dictionary loading.

Like the situation with function cache loading, text search
dictionary loading functions tend to leak some cruft into the
dictionary's long-lived cache context.  To judge by the examples in
the core regression tests, not very many bytes are at stake.
Moreover, I don't see a way to prevent such leaks without changing the
API for TS template initialization functions: right now they do not
have to worry about making sure that their results are long-lived.

Hence, I think we should install a suppression rule rather than trying
to fix this completely.  However, I did grab some low-hanging fruit:
several places were leaking the result of get_tsearch_config_filename.
This seems worth doing mostly because they are inconsistent with other
dictionaries that were freeing it already.

Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Andres Freund <andres@anarazel.de>
Discussion: https://postgr.es/m/285483.1746756246@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2025-08-02 19:43:53 -04:00
parent 2c7b4ad24d
commit 7f6ededa76
5 changed files with 32 additions and 10 deletions

View File

@ -47,24 +47,30 @@ dispell_init(PG_FUNCTION_ARGS)
if (strcmp(defel->defname, "dictfile") == 0) if (strcmp(defel->defname, "dictfile") == 0)
{ {
char *filename;
if (dictloaded) if (dictloaded)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("multiple DictFile parameters"))); errmsg("multiple DictFile parameters")));
NIImportDictionary(&(d->obj), filename = get_tsearch_config_filename(defGetString(defel),
get_tsearch_config_filename(defGetString(defel), "dict");
"dict")); NIImportDictionary(&(d->obj), filename);
pfree(filename);
dictloaded = true; dictloaded = true;
} }
else if (strcmp(defel->defname, "afffile") == 0) else if (strcmp(defel->defname, "afffile") == 0)
{ {
char *filename;
if (affloaded) if (affloaded)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("multiple AffFile parameters"))); errmsg("multiple AffFile parameters")));
NIImportAffixes(&(d->obj), filename = get_tsearch_config_filename(defGetString(defel),
get_tsearch_config_filename(defGetString(defel), "affix");
"affix")); NIImportAffixes(&(d->obj), filename);
pfree(filename);
affloaded = true; affloaded = true;
} }
else if (strcmp(defel->defname, "stopwords") == 0) else if (strcmp(defel->defname, "stopwords") == 0)

View File

@ -199,6 +199,7 @@ skipline:
} }
tsearch_readline_end(&trst); tsearch_readline_end(&trst);
pfree(filename);
d->len = cur; d->len = cur;
qsort(d->syn, d->len, sizeof(Syn), compareSyn); qsort(d->syn, d->len, sizeof(Syn), compareSyn);

View File

@ -167,17 +167,17 @@ addWrd(DictThesaurus *d, char *b, char *e, uint32 idsubst, uint16 nwrd, uint16 p
static void static void
thesaurusRead(const char *filename, DictThesaurus *d) thesaurusRead(const char *filename, DictThesaurus *d)
{ {
char *real_filename = get_tsearch_config_filename(filename, "ths");
tsearch_readline_state trst; tsearch_readline_state trst;
uint32 idsubst = 0; uint32 idsubst = 0;
bool useasis = false; bool useasis = false;
char *line; char *line;
filename = get_tsearch_config_filename(filename, "ths"); if (!tsearch_readline_begin(&trst, real_filename))
if (!tsearch_readline_begin(&trst, filename))
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_CONFIG_FILE_ERROR), (errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("could not open thesaurus file \"%s\": %m", errmsg("could not open thesaurus file \"%s\": %m",
filename))); real_filename)));
while ((line = tsearch_readline(&trst)) != NULL) while ((line = tsearch_readline(&trst)) != NULL)
{ {
@ -297,6 +297,7 @@ thesaurusRead(const char *filename, DictThesaurus *d)
d->nsubst = idsubst; d->nsubst = idsubst;
tsearch_readline_end(&trst); tsearch_readline_end(&trst);
pfree(real_filename);
} }
static TheLexeme * static TheLexeme *

View File

@ -321,7 +321,9 @@ lookup_ts_dictionary_cache(Oid dictId)
/* /*
* Init method runs in dictionary's private memory context, and we * Init method runs in dictionary's private memory context, and we
* make sure the options are stored there too * make sure the options are stored there too. This typically
* results in a small amount of memory leakage, but it's not worth
* complicating the API for tmplinit functions to avoid it.
*/ */
oldcontext = MemoryContextSwitchTo(entry->dictCtx); oldcontext = MemoryContextSwitchTo(entry->dictCtx);

View File

@ -215,3 +215,15 @@
... ...
fun:cached_function_compile fun:cached_function_compile
} }
# Suppress complaints about stuff leaked during TS dictionary loading.
# Not very much is typically lost there, and preventing it would
# require a risky API change for TS tmplinit functions.
{
hide_ts_dictionary_leaks
Memcheck:Leak
match-leak-kinds: definite,possible,indirect
...
fun:lookup_ts_dictionary_cache
}