Skip to content

Commit db01c90

Browse files
committed
Silence Valgrind leakage complaints in more-or-less-hackish ways.
These changes don't actually fix any leaks. They just make sure that Valgrind will find pointers to data structures that remain allocated at process exit, and thus not falsely complain about leaks. In particular, we are trying to avoid situations where there is no pointer to the beginning of an allocated block (except possibly within the block itself, which Valgrind won't count). * Because dynahash.c never frees hashtable storage except by deleting the whole hashtable context, it doesn't bother to track the individual blocks of elements allocated by element_alloc(). This results in "possibly lost" complaints from Valgrind except when the first element of each block is actively in use. (Otherwise it'll be on a freelist, but very likely only reachable via "interior pointers" within element blocks, which doesn't satisfy Valgrind.) To fix, if we're building with USE_VALGRIND, expend an extra pointer's worth of space in each element block so that we can chain them all together from the HTAB header. Skip this in shared hashtables though: Valgrind doesn't track those, and we'd need additional locking to make it safe to manipulate a shared chain. While here, update a comment obsoleted by 9c911ec. * Put the dlist_node fields of catctup and catclist structs first. This ensures that the dlist pointers point to the starts of these palloc blocks, and thus that Valgrind won't consider them "possibly lost". * The postmaster's PMChild structs and the autovac launcher's avl_dbase structs also have the dlist_node-is-not-first problem, but putting it first still wouldn't silence the warning because we bulk-allocate those structs in an array, so that Valgrind sees a single allocation. Commonly the first array element will be pointed to only from some later element, so that the reference would be an interior pointer even if it pointed to the array start. (This is the same issue as for dynahash elements.) Since these are pretty simple data structures, I don't feel too bad about faking out Valgrind by just keeping a static pointer to the array start. (This is all quite hacky, and it's not hard to imagine usages where we'd need some other idea in order to have reasonable leak tracking of structures that are only accessible via dlist_node lists. But these changes seem to be enough to silence this class of leakage complaints for the moment.) * Free a couple of data structures manually near the end of an autovacuum worker's run when USE_VALGRIND, and ensure that the final vac_update_datfrozenxid() call is done in a non-permanent context. This doesn't have any real effect on the process's total memory consumption, since we're going to exit as soon as that last transaction is done. But it does pacify Valgrind. * Valgrind complains about the postmaster's socket-files and lock-files lists being leaked, which we can silence by just not nulling out the static pointers to them. * Valgrind seems not to consider the global "environ" variable as a valid root pointer; so when we allocate a new environment array, it claims that data is leaked. To fix that, keep our own statically-allocated copy of the pointer, similarly to the previous item. 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
1 parent e78d1d6 commit db01c90

File tree

7 files changed

+118
-19
lines changed

7 files changed

+118
-19
lines changed

src/backend/libpq/pqcomm.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,6 @@ RemoveSocketFiles(void)
858858
(void) unlink(sock_path);
859859
}
860860
/* Since we're about to exit, no need to reclaim storage */
861-
sock_paths = NIL;
862861
}
863862

864863

src/backend/postmaster/autovacuum.c

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,16 @@ static AutoVacuumShmemStruct *AutoVacuumShmem;
310310
static dlist_head DatabaseList = DLIST_STATIC_INIT(DatabaseList);
311311
static MemoryContext DatabaseListCxt = NULL;
312312

313+
/*
314+
* Dummy pointer to persuade Valgrind that we've not leaked the array of
315+
* avl_dbase structs. Make it global to ensure the compiler doesn't
316+
* optimize it away.
317+
*/
318+
#ifdef USE_VALGRIND
319+
extern avl_dbase *avl_dbase_array;
320+
avl_dbase *avl_dbase_array;
321+
#endif
322+
313323
/* Pointer to my own WorkerInfo, valid on each worker */
314324
static WorkerInfo MyWorkerInfo = NULL;
315325

@@ -1020,6 +1030,10 @@ rebuild_database_list(Oid newdb)
10201030

10211031
/* put all the hash elements into an array */
10221032
dbary = palloc(nelems * sizeof(avl_dbase));
1033+
/* keep Valgrind quiet */
1034+
#ifdef USE_VALGRIND
1035+
avl_dbase_array = dbary;
1036+
#endif
10231037

10241038
i = 0;
10251039
hash_seq_init(&seq, dbhash);
@@ -2565,8 +2579,18 @@ do_autovacuum(void)
25652579

25662580
/*
25672581
* We leak table_toast_map here (among other things), but since we're
2568-
* going away soon, it's not a problem.
2582+
* going away soon, it's not a problem normally. But when using Valgrind,
2583+
* release some stuff to reduce complaints about leaked storage.
25692584
*/
2585+
#ifdef USE_VALGRIND
2586+
hash_destroy(table_toast_map);
2587+
FreeTupleDesc(pg_class_desc);
2588+
if (bstrategy)
2589+
pfree(bstrategy);
2590+
#endif
2591+
2592+
/* Run the rest in xact context, mainly to avoid Valgrind leak warnings */
2593+
MemoryContextSwitchTo(TopTransactionContext);
25702594

25712595
/*
25722596
* Update pg_database.datfrozenxid, and truncate pg_xact if possible. We

src/backend/postmaster/pmchild.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,17 @@ NON_EXEC_STATIC int num_pmchild_slots = 0;
5959
*/
6060
dlist_head ActiveChildList;
6161

62+
/*
63+
* Dummy pointer to persuade Valgrind that we've not leaked the array of
64+
* PMChild structs. Make it global to ensure the compiler doesn't
65+
* optimize it away.
66+
*/
67+
#ifdef USE_VALGRIND
68+
extern PMChild *pmchild_array;
69+
PMChild *pmchild_array;
70+
#endif
71+
72+
6273
/*
6374
* MaxLivePostmasterChildren
6475
*
@@ -125,8 +136,13 @@ InitPostmasterChildSlots(void)
125136
for (int i = 0; i < BACKEND_NUM_TYPES; i++)
126137
num_pmchild_slots += pmchild_pools[i].size;
127138

128-
/* Initialize them */
139+
/* Allocate enough slots, and make sure Valgrind doesn't complain */
129140
slots = palloc(num_pmchild_slots * sizeof(PMChild));
141+
#ifdef USE_VALGRIND
142+
pmchild_array = slots;
143+
#endif
144+
145+
/* Initialize them */
130146
slotno = 0;
131147
for (int btype = 0; btype < BACKEND_NUM_TYPES; btype++)
132148
{

src/backend/utils/hash/dynahash.c

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@
2222
* lookup key's hash value as a partition number --- this will work because
2323
* of the way calc_bucket() maps hash values to bucket numbers.
2424
*
25-
* For hash tables in shared memory, the memory allocator function should
26-
* match malloc's semantics of returning NULL on failure. For hash tables
27-
* in local memory, we typically use palloc() which will throw error on
28-
* failure. The code in this file has to cope with both cases.
25+
* The memory allocator function should match malloc's semantics of returning
26+
* NULL on failure. (This is essential for hash tables in shared memory.
27+
* For hash tables in local memory, we used to use palloc() which will throw
28+
* error on failure; but we no longer do, so it's untested whether this
29+
* module will still cope with that behavior.)
2930
*
3031
* dynahash.c provides support for these types of lookup keys:
3132
*
@@ -98,6 +99,7 @@
9899

99100
#include "access/xact.h"
100101
#include "common/hashfn.h"
102+
#include "lib/ilist.h"
101103
#include "port/pg_bitutils.h"
102104
#include "storage/shmem.h"
103105
#include "storage/spin.h"
@@ -236,6 +238,16 @@ struct HTAB
236238
Size keysize; /* hash key length in bytes */
237239
long ssize; /* segment size --- must be power of 2 */
238240
int sshift; /* segment shift = log2(ssize) */
241+
242+
/*
243+
* In a USE_VALGRIND build, non-shared hashtables keep an slist chain of
244+
* all the element blocks they have allocated. This pacifies Valgrind,
245+
* which would otherwise often claim that the element blocks are "possibly
246+
* lost" for lack of any non-interior pointers to their starts.
247+
*/
248+
#ifdef USE_VALGRIND
249+
slist_head element_blocks;
250+
#endif
239251
};
240252

241253
/*
@@ -1712,6 +1724,8 @@ element_alloc(HTAB *hashp, int nelem, int freelist_idx)
17121724
{
17131725
HASHHDR *hctl = hashp->hctl;
17141726
Size elementSize;
1727+
Size requestSize;
1728+
char *allocedBlock;
17151729
HASHELEMENT *firstElement;
17161730
HASHELEMENT *tmpElement;
17171731
HASHELEMENT *prevElement;
@@ -1723,12 +1737,38 @@ element_alloc(HTAB *hashp, int nelem, int freelist_idx)
17231737
/* Each element has a HASHELEMENT header plus user data. */
17241738
elementSize = MAXALIGN(sizeof(HASHELEMENT)) + MAXALIGN(hctl->entrysize);
17251739

1740+
requestSize = nelem * elementSize;
1741+
1742+
/* Add space for slist_node list link if we need one. */
1743+
#ifdef USE_VALGRIND
1744+
if (!hashp->isshared)
1745+
requestSize += MAXALIGN(sizeof(slist_node));
1746+
#endif
1747+
1748+
/* Allocate the memory. */
17261749
CurrentDynaHashCxt = hashp->hcxt;
1727-
firstElement = (HASHELEMENT *) hashp->alloc(nelem * elementSize);
1750+
allocedBlock = hashp->alloc(requestSize);
17281751

1729-
if (!firstElement)
1752+
if (!allocedBlock)
17301753
return false;
17311754

1755+
/*
1756+
* If USE_VALGRIND, each allocated block of elements of a non-shared
1757+
* hashtable is chained into a list, so that Valgrind won't think it's
1758+
* been leaked.
1759+
*/
1760+
#ifdef USE_VALGRIND
1761+
if (hashp->isshared)
1762+
firstElement = (HASHELEMENT *) allocedBlock;
1763+
else
1764+
{
1765+
slist_push_head(&hashp->element_blocks, (slist_node *) allocedBlock);
1766+
firstElement = (HASHELEMENT *) (allocedBlock + MAXALIGN(sizeof(slist_node)));
1767+
}
1768+
#else
1769+
firstElement = (HASHELEMENT *) allocedBlock;
1770+
#endif
1771+
17321772
/* prepare to link all the new entries into the freelist */
17331773
prevElement = NULL;
17341774
tmpElement = firstElement;

src/backend/utils/init/miscinit.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1183,7 +1183,6 @@ UnlinkLockFiles(int status, Datum arg)
11831183
/* Should we complain if the unlink fails? */
11841184
}
11851185
/* Since we're about to exit, no need to reclaim storage */
1186-
lock_files = NIL;
11871186

11881187
/*
11891188
* Lock file removal should always be the last externally visible action

src/backend/utils/misc/ps_status.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,17 @@ static void flush_ps_display(void);
100100
static int save_argc;
101101
static char **save_argv;
102102

103+
/*
104+
* Valgrind seems not to consider the global "environ" variable as a valid
105+
* root pointer; so when we allocate a new environment array, it claims that
106+
* data is leaked. To fix that, keep our own statically-allocated copy of the
107+
* pointer. (Oddly, this doesn't seem to be a problem for "argv".)
108+
*/
109+
#if defined(PS_USE_CLOBBER_ARGV) && defined(USE_VALGRIND)
110+
extern char **ps_status_new_environ;
111+
char **ps_status_new_environ;
112+
#endif
113+
103114

104115
/*
105116
* Call this early in startup to save the original argc/argv values.
@@ -206,6 +217,11 @@ save_ps_display_args(int argc, char **argv)
206217
}
207218
new_environ[i] = NULL;
208219
environ = new_environ;
220+
221+
/* See notes about Valgrind above. */
222+
#ifdef USE_VALGRIND
223+
ps_status_new_environ = new_environ;
224+
#endif
209225
}
210226

211227
/*

src/include/utils/catcache.h

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ typedef struct catcache
8787

8888
typedef struct catctup
8989
{
90+
/*
91+
* Each tuple in a cache is a member of a dlist that stores the elements
92+
* of its hash bucket. We keep each dlist in LRU order to speed repeated
93+
* lookups. Keep the dlist_node field first so that Valgrind understands
94+
* the struct is reachable.
95+
*/
96+
dlist_node cache_elem; /* list member of per-bucket list */
97+
9098
int ct_magic; /* for identifying CatCTup entries */
9199
#define CT_MAGIC 0x57261502
92100

@@ -98,13 +106,6 @@ typedef struct catctup
98106
*/
99107
Datum keys[CATCACHE_MAXKEYS];
100108

101-
/*
102-
* Each tuple in a cache is a member of a dlist that stores the elements
103-
* of its hash bucket. We keep each dlist in LRU order to speed repeated
104-
* lookups.
105-
*/
106-
dlist_node cache_elem; /* list member of per-bucket list */
107-
108109
/*
109110
* A tuple marked "dead" must not be returned by subsequent searches.
110111
* However, it won't be physically deleted from the cache until its
@@ -158,13 +159,17 @@ typedef struct catctup
158159
*/
159160
typedef struct catclist
160161
{
162+
/*
163+
* Keep the dlist_node field first so that Valgrind understands the struct
164+
* is reachable.
165+
*/
166+
dlist_node cache_elem; /* list member of per-catcache list */
167+
161168
int cl_magic; /* for identifying CatCList entries */
162169
#define CL_MAGIC 0x52765103
163170

164171
uint32 hash_value; /* hash value for lookup keys */
165172

166-
dlist_node cache_elem; /* list member of per-catcache list */
167-
168173
/*
169174
* Lookup keys for the entry, with the first nkeys elements being valid.
170175
* All by-reference are separately allocated.

0 commit comments

Comments
 (0)