PostgreSQL Source Code  git master
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros
resowner.c
Go to the documentation of this file.
1 /*-------------------------------------------------------------------------
2  *
3  * resowner.c
4  * POSTGRES resource owner management code.
5  *
6  * Query-lifespan resources are tracked by associating them with
7  * ResourceOwner objects. This provides a simple mechanism for ensuring
8  * that such resources are freed at the right time.
9  * See utils/resowner/README for more info.
10  *
11  *
12  * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
13  * Portions Copyright (c) 1994, Regents of the University of California
14  *
15  *
16  * IDENTIFICATION
17  * src/backend/utils/resowner/resowner.c
18  *
19  *-------------------------------------------------------------------------
20  */
21 #include "postgres.h"
22 
23 #include "access/hash.h"
24 #include "storage/predicate.h"
25 #include "storage/proc.h"
26 #include "utils/memutils.h"
27 #include "utils/rel.h"
28 #include "utils/resowner_private.h"
29 #include "utils/snapmgr.h"
30 
31 
32 /*
33  * All resource IDs managed by this code are required to fit into a Datum,
34  * which is fine since they are generally pointers or integers.
35  *
36  * Provide Datum conversion macros for a couple of things that are really
37  * just "int".
38  */
39 #define FileGetDatum(file) Int32GetDatum(file)
40 #define DatumGetFile(datum) ((File) DatumGetInt32(datum))
41 #define BufferGetDatum(buffer) Int32GetDatum(buffer)
42 #define DatumGetBuffer(datum) ((Buffer) DatumGetInt32(datum))
43 
44 /*
45  * ResourceArray is a common structure for storing all types of resource IDs.
46  *
47  * We manage small sets of resource IDs by keeping them in a simple array:
48  * itemsarr[k] holds an ID, for 0 <= k < nitems <= maxitems = capacity.
49  *
50  * If a set grows large, we switch over to using open-addressing hashing.
51  * Then, itemsarr[] is a hash table of "capacity" slots, with each
52  * slot holding either an ID or "invalidval". nitems is the number of valid
53  * items present; if it would exceed maxitems, we enlarge the array and
54  * re-hash. In this mode, maxitems should be rather less than capacity so
55  * that we don't waste too much time searching for empty slots.
56  *
57  * In either mode, lastidx remembers the location of the last item inserted
58  * or returned by GetAny; this speeds up searches in ResourceArrayRemove.
59  */
60 typedef struct ResourceArray
61 {
62  Datum *itemsarr; /* buffer for storing values */
63  Datum invalidval; /* value that is considered invalid */
64  uint32 capacity; /* allocated length of itemsarr[] */
65  uint32 nitems; /* how many items are stored in items array */
66  uint32 maxitems; /* current limit on nitems before enlarging */
67  uint32 lastidx; /* index of last item returned by GetAny */
69 
70 /*
71  * Initially allocated size of a ResourceArray. Must be power of two since
72  * we'll use (arraysize - 1) as mask for hashing.
73  */
74 #define RESARRAY_INIT_SIZE 16
75 
76 /*
77  * When to switch to hashing vs. simple array logic in a ResourceArray.
78  */
79 #define RESARRAY_MAX_ARRAY 64
80 #define RESARRAY_IS_ARRAY(resarr) ((resarr)->capacity <= RESARRAY_MAX_ARRAY)
81 
82 /*
83  * How many items may be stored in a resource array of given capacity.
84  * When this number is reached, we must resize.
85  */
86 #define RESARRAY_MAX_ITEMS(capacity) \
87  ((capacity) <= RESARRAY_MAX_ARRAY ? (capacity) : (capacity)/4 * 3)
88 
89 /*
90  * To speed up bulk releasing or reassigning locks from a resource owner to
91  * its parent, each resource owner has a small cache of locks it owns. The
92  * lock manager has the same information in its local lock hash table, and
93  * we fall back on that if cache overflows, but traversing the hash table
94  * is slower when there are a lot of locks belonging to other resource owners.
95  *
96  * MAX_RESOWNER_LOCKS is the size of the per-resource owner cache. It's
97  * chosen based on some testing with pg_dump with a large schema. When the
98  * tests were done (on 9.2), resource owners in a pg_dump run contained up
99  * to 9 locks, regardless of the schema size, except for the top resource
100  * owner which contained much more (overflowing the cache). 15 seems like a
101  * nice round number that's somewhat higher than what pg_dump needs. Note that
102  * making this number larger is not free - the bigger the cache, the slower
103  * it is to release locks (in retail), when a resource owner holds many locks.
104  */
105 #define MAX_RESOWNER_LOCKS 15
106 
107 /*
108  * ResourceOwner objects look like this
109  */
110 typedef struct ResourceOwnerData
111 {
112  ResourceOwner parent; /* NULL if no parent (toplevel owner) */
113  ResourceOwner firstchild; /* head of linked list of children */
114  ResourceOwner nextchild; /* next child of same parent */
115  const char *name; /* name (just for debugging) */
116 
117  /* We have built-in support for remembering: */
118  ResourceArray bufferarr; /* owned buffers */
119  ResourceArray catrefarr; /* catcache references */
120  ResourceArray catlistrefarr; /* catcache-list pins */
121  ResourceArray relrefarr; /* relcache references */
122  ResourceArray planrefarr; /* plancache references */
123  ResourceArray tupdescarr; /* tupdesc references */
124  ResourceArray snapshotarr; /* snapshot references */
125  ResourceArray filearr; /* open temporary files */
126  ResourceArray dsmarr; /* dynamic shmem segments */
127 
128  /* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */
129  int nlocks; /* number of owned locks */
130  LOCALLOCK *locks[MAX_RESOWNER_LOCKS]; /* list of owned locks */
132 
133 
134 /*****************************************************************************
135  * GLOBAL MEMORY *
136  *****************************************************************************/
137 
141 
142 /*
143  * List of add-on callbacks for resource releasing
144  */
146 {
149  void *arg;
151 
153 
154 
155 /* Internal routines */
156 static void ResourceArrayInit(ResourceArray *resarr, Datum invalidval);
157 static void ResourceArrayEnlarge(ResourceArray *resarr);
158 static void ResourceArrayAdd(ResourceArray *resarr, Datum value);
159 static bool ResourceArrayRemove(ResourceArray *resarr, Datum value);
160 static bool ResourceArrayGetAny(ResourceArray *resarr, Datum *value);
161 static void ResourceArrayFree(ResourceArray *resarr);
163  ResourceReleasePhase phase,
164  bool isCommit,
165  bool isTopLevel);
166 static void PrintRelCacheLeakWarning(Relation rel);
167 static void PrintPlanCacheLeakWarning(CachedPlan *plan);
168 static void PrintTupleDescLeakWarning(TupleDesc tupdesc);
169 static void PrintSnapshotLeakWarning(Snapshot snapshot);
170 static void PrintFileLeakWarning(File file);
171 static void PrintDSMLeakWarning(dsm_segment *seg);
172 
173 
174 /*****************************************************************************
175  * INTERNAL ROUTINES *
176  *****************************************************************************/
177 
178 
179 /*
180  * Initialize a ResourceArray
181  */
182 static void
184 {
185  /* Assert it's empty */
186  Assert(resarr->itemsarr == NULL);
187  Assert(resarr->capacity == 0);
188  Assert(resarr->nitems == 0);
189  Assert(resarr->maxitems == 0);
190  /* Remember the appropriate "invalid" value */
191  resarr->invalidval = invalidval;
192  /* We don't allocate any storage until needed */
193 }
194 
195 /*
196  * Make sure there is room for at least one more resource in an array.
197  *
198  * This is separate from actually inserting a resource because if we run out
199  * of memory, it's critical to do so *before* acquiring the resource.
200  */
201 static void
203 {
204  uint32 i,
205  oldcap,
206  newcap;
207  Datum *olditemsarr;
208  Datum *newitemsarr;
209 
210  if (resarr->nitems < resarr->maxitems)
211  return; /* no work needed */
212 
213  olditemsarr = resarr->itemsarr;
214  oldcap = resarr->capacity;
215 
216  /* Double the capacity of the array (capacity must stay a power of 2!) */
217  newcap = (oldcap > 0) ? oldcap * 2 : RESARRAY_INIT_SIZE;
218  newitemsarr = (Datum *) MemoryContextAlloc(TopMemoryContext,
219  newcap * sizeof(Datum));
220  for (i = 0; i < newcap; i++)
221  newitemsarr[i] = resarr->invalidval;
222 
223  /* We assume we can't fail below this point, so OK to scribble on resarr */
224  resarr->itemsarr = newitemsarr;
225  resarr->capacity = newcap;
226  resarr->maxitems = RESARRAY_MAX_ITEMS(newcap);
227  resarr->nitems = 0;
228 
229  if (olditemsarr != NULL)
230  {
231  /*
232  * Transfer any pre-existing entries into the new array; they don't
233  * necessarily go where they were before, so this simple logic is the
234  * best way. Note that if we were managing the set as a simple array,
235  * the entries after nitems are garbage, but that shouldn't matter
236  * because we won't get here unless nitems was equal to oldcap.
237  */
238  for (i = 0; i < oldcap; i++)
239  {
240  if (olditemsarr[i] != resarr->invalidval)
241  ResourceArrayAdd(resarr, olditemsarr[i]);
242  }
243 
244  /* And release old array. */
245  pfree(olditemsarr);
246  }
247 
248  Assert(resarr->nitems < resarr->maxitems);
249 }
250 
251 /*
252  * Add a resource to ResourceArray
253  *
254  * Caller must have previously done ResourceArrayEnlarge()
255  */
256 static void
258 {
259  uint32 idx;
260 
261  Assert(value != resarr->invalidval);
262  Assert(resarr->nitems < resarr->maxitems);
263 
264  if (RESARRAY_IS_ARRAY(resarr))
265  {
266  /* Append to linear array. */
267  idx = resarr->nitems;
268  }
269  else
270  {
271  /* Insert into first free slot at or after hash location. */
272  uint32 mask = resarr->capacity - 1;
273 
274  idx = DatumGetUInt32(hash_any((void *) &value, sizeof(value))) & mask;
275  for (;;)
276  {
277  if (resarr->itemsarr[idx] == resarr->invalidval)
278  break;
279  idx = (idx + 1) & mask;
280  }
281  }
282  resarr->lastidx = idx;
283  resarr->itemsarr[idx] = value;
284  resarr->nitems++;
285 }
286 
287 /*
288  * Remove a resource from ResourceArray
289  *
290  * Returns true on success, false if resource was not found.
291  *
292  * Note: if same resource ID appears more than once, one instance is removed.
293  */
294 static bool
296 {
297  uint32 i,
298  idx,
299  lastidx = resarr->lastidx;
300 
301  Assert(value != resarr->invalidval);
302 
303  /* Search through all items, but try lastidx first. */
304  if (RESARRAY_IS_ARRAY(resarr))
305  {
306  if (lastidx < resarr->nitems &&
307  resarr->itemsarr[lastidx] == value)
308  {
309  resarr->itemsarr[lastidx] = resarr->itemsarr[resarr->nitems - 1];
310  resarr->nitems--;
311  /* Update lastidx to make reverse-order removals fast. */
312  resarr->lastidx = resarr->nitems - 1;
313  return true;
314  }
315  for (i = 0; i < resarr->nitems; i++)
316  {
317  if (resarr->itemsarr[i] == value)
318  {
319  resarr->itemsarr[i] = resarr->itemsarr[resarr->nitems - 1];
320  resarr->nitems--;
321  /* Update lastidx to make reverse-order removals fast. */
322  resarr->lastidx = resarr->nitems - 1;
323  return true;
324  }
325  }
326  }
327  else
328  {
329  uint32 mask = resarr->capacity - 1;
330 
331  if (lastidx < resarr->capacity &&
332  resarr->itemsarr[lastidx] == value)
333  {
334  resarr->itemsarr[lastidx] = resarr->invalidval;
335  resarr->nitems--;
336  return true;
337  }
338  idx = DatumGetUInt32(hash_any((void *) &value, sizeof(value))) & mask;
339  for (i = 0; i < resarr->capacity; i++)
340  {
341  if (resarr->itemsarr[idx] == value)
342  {
343  resarr->itemsarr[idx] = resarr->invalidval;
344  resarr->nitems--;
345  return true;
346  }
347  idx = (idx + 1) & mask;
348  }
349  }
350 
351  return false;
352 }
353 
354 /*
355  * Get any convenient entry in a ResourceArray.
356  *
357  * "Convenient" is defined as "easy for ResourceArrayRemove to remove";
358  * we help that along by setting lastidx to match. This avoids O(N^2) cost
359  * when removing all ResourceArray items during ResourceOwner destruction.
360  *
361  * Returns true if we found an element, or false if the array is empty.
362  */
363 static bool
365 {
366  if (resarr->nitems == 0)
367  return false;
368 
369  if (RESARRAY_IS_ARRAY(resarr))
370  {
371  /* Linear array: just return the first element. */
372  resarr->lastidx = 0;
373  }
374  else
375  {
376  /* Hash: search forward from wherever we were last. */
377  uint32 mask = resarr->capacity - 1;
378 
379  for (;;)
380  {
381  resarr->lastidx &= mask;
382  if (resarr->itemsarr[resarr->lastidx] != resarr->invalidval)
383  break;
384  resarr->lastidx++;
385  }
386  }
387 
388  *value = resarr->itemsarr[resarr->lastidx];
389  return true;
390 }
391 
392 /*
393  * Trash a ResourceArray (we don't care about its state after this)
394  */
395 static void
397 {
398  if (resarr->itemsarr)
399  pfree(resarr->itemsarr);
400 }
401 
402 
403 /*****************************************************************************
404  * EXPORTED ROUTINES *
405  *****************************************************************************/
406 
407 
408 /*
409  * ResourceOwnerCreate
410  * Create an empty ResourceOwner.
411  *
412  * All ResourceOwner objects are kept in TopMemoryContext, since they should
413  * only be freed explicitly.
414  */
417 {
418  ResourceOwner owner;
419 
421  sizeof(ResourceOwnerData));
422  owner->name = name;
423 
424  if (parent)
425  {
426  owner->parent = parent;
427  owner->nextchild = parent->firstchild;
428  parent->firstchild = owner;
429  }
430 
438  ResourceArrayInit(&(owner->filearr), FileGetDatum(-1));
440 
441  return owner;
442 }
443 
444 /*
445  * ResourceOwnerRelease
446  * Release all resources owned by a ResourceOwner and its descendants,
447  * but don't delete the owner objects themselves.
448  *
449  * Note that this executes just one phase of release, and so typically
450  * must be called three times. We do it this way because (a) we want to
451  * do all the recursion separately for each phase, thereby preserving
452  * the needed order of operations; and (b) xact.c may have other operations
453  * to do between the phases.
454  *
455  * phase: release phase to execute
456  * isCommit: true for successful completion of a query or transaction,
457  * false for unsuccessful
458  * isTopLevel: true if completing a main transaction, else false
459  *
460  * isCommit is passed because some modules may expect that their resources
461  * were all released already if the transaction or portal finished normally.
462  * If so it is reasonable to give a warning (NOT an error) should any
463  * unreleased resources be present. When isCommit is false, such warnings
464  * are generally inappropriate.
465  *
466  * isTopLevel is passed when we are releasing TopTransactionResourceOwner
467  * at completion of a main transaction. This generally means that *all*
468  * resources will be released, and so we can optimize things a bit.
469  */
470 void
472  ResourceReleasePhase phase,
473  bool isCommit,
474  bool isTopLevel)
475 {
476  /* Rather than PG_TRY at every level of recursion, set it up once */
477  ResourceOwner save;
478 
479  save = CurrentResourceOwner;
480  PG_TRY();
481  {
482  ResourceOwnerReleaseInternal(owner, phase, isCommit, isTopLevel);
483  }
484  PG_CATCH();
485  {
486  CurrentResourceOwner = save;
487  PG_RE_THROW();
488  }
489  PG_END_TRY();
490  CurrentResourceOwner = save;
491 }
492 
493 static void
495  ResourceReleasePhase phase,
496  bool isCommit,
497  bool isTopLevel)
498 {
499  ResourceOwner child;
500  ResourceOwner save;
502  Datum foundres;
503 
504  /* Recurse to handle descendants */
505  for (child = owner->firstchild; child != NULL; child = child->nextchild)
506  ResourceOwnerReleaseInternal(child, phase, isCommit, isTopLevel);
507 
508  /*
509  * Make CurrentResourceOwner point to me, so that ReleaseBuffer etc don't
510  * get confused. We needn't PG_TRY here because the outermost level will
511  * fix it on error abort.
512  */
513  save = CurrentResourceOwner;
514  CurrentResourceOwner = owner;
515 
516  if (phase == RESOURCE_RELEASE_BEFORE_LOCKS)
517  {
518  /*
519  * Release buffer pins. Note that ReleaseBuffer will remove the
520  * buffer entry from our array, so we just have to iterate till there
521  * are none.
522  *
523  * During a commit, there shouldn't be any remaining pins --- that
524  * would indicate failure to clean up the executor correctly --- so
525  * issue warnings. In the abort case, just clean up quietly.
526  */
527  while (ResourceArrayGetAny(&(owner->bufferarr), &foundres))
528  {
529  Buffer res = DatumGetBuffer(foundres);
530 
531  if (isCommit)
533  ReleaseBuffer(res);
534  }
535 
536  /* Ditto for relcache references */
537  while (ResourceArrayGetAny(&(owner->relrefarr), &foundres))
538  {
539  Relation res = (Relation) DatumGetPointer(foundres);
540 
541  if (isCommit)
543  RelationClose(res);
544  }
545 
546  /* Ditto for dynamic shared memory segments */
547  while (ResourceArrayGetAny(&(owner->dsmarr), &foundres))
548  {
549  dsm_segment *res = (dsm_segment *) DatumGetPointer(foundres);
550 
551  if (isCommit)
552  PrintDSMLeakWarning(res);
553  dsm_detach(res);
554  }
555  }
556  else if (phase == RESOURCE_RELEASE_LOCKS)
557  {
558  if (isTopLevel)
559  {
560  /*
561  * For a top-level xact we are going to release all locks (or at
562  * least all non-session locks), so just do a single lmgr call at
563  * the top of the recursion.
564  */
565  if (owner == TopTransactionResourceOwner)
566  {
567  ProcReleaseLocks(isCommit);
568  ReleasePredicateLocks(isCommit);
569  }
570  }
571  else
572  {
573  /*
574  * Release locks retail. Note that if we are committing a
575  * subtransaction, we do NOT release its locks yet, but transfer
576  * them to the parent.
577  */
578  LOCALLOCK **locks;
579  int nlocks;
580 
581  Assert(owner->parent != NULL);
582 
583  /*
584  * Pass the list of locks owned by this resource owner to the lock
585  * manager, unless it has overflowed.
586  */
587  if (owner->nlocks > MAX_RESOWNER_LOCKS)
588  {
589  locks = NULL;
590  nlocks = 0;
591  }
592  else
593  {
594  locks = owner->locks;
595  nlocks = owner->nlocks;
596  }
597 
598  if (isCommit)
599  LockReassignCurrentOwner(locks, nlocks);
600  else
601  LockReleaseCurrentOwner(locks, nlocks);
602  }
603  }
604  else if (phase == RESOURCE_RELEASE_AFTER_LOCKS)
605  {
606  /*
607  * Release catcache references. Note that ReleaseCatCache will remove
608  * the catref entry from our array, so we just have to iterate till
609  * there are none.
610  *
611  * As with buffer pins, warn if any are left at commit time.
612  */
613  while (ResourceArrayGetAny(&(owner->catrefarr), &foundres))
614  {
615  HeapTuple res = (HeapTuple) DatumGetPointer(foundres);
616 
617  if (isCommit)
619  ReleaseCatCache(res);
620  }
621 
622  /* Ditto for catcache lists */
623  while (ResourceArrayGetAny(&(owner->catlistrefarr), &foundres))
624  {
625  CatCList *res = (CatCList *) DatumGetPointer(foundres);
626 
627  if (isCommit)
629  ReleaseCatCacheList(res);
630  }
631 
632  /* Ditto for plancache references */
633  while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
634  {
635  CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
636 
637  if (isCommit)
639  ReleaseCachedPlan(res, true);
640  }
641 
642  /* Ditto for tupdesc references */
643  while (ResourceArrayGetAny(&(owner->tupdescarr), &foundres))
644  {
645  TupleDesc res = (TupleDesc) DatumGetPointer(foundres);
646 
647  if (isCommit)
650  }
651 
652  /* Ditto for snapshot references */
653  while (ResourceArrayGetAny(&(owner->snapshotarr), &foundres))
654  {
655  Snapshot res = (Snapshot) DatumGetPointer(foundres);
656 
657  if (isCommit)
659  UnregisterSnapshot(res);
660  }
661 
662  /* Ditto for temporary files */
663  while (ResourceArrayGetAny(&(owner->filearr), &foundres))
664  {
665  File res = DatumGetFile(foundres);
666 
667  if (isCommit)
669  FileClose(res);
670  }
671  }
672 
673  /* Let add-on modules get a chance too */
674  for (item = ResourceRelease_callbacks; item; item = item->next)
675  (*item->callback) (phase, isCommit, isTopLevel, item->arg);
676 
677  CurrentResourceOwner = save;
678 }
679 
680 /*
681  * ResourceOwnerDelete
682  * Delete an owner object and its descendants.
683  *
684  * The caller must have already released all resources in the object tree.
685  */
686 void
688 {
689  /* We had better not be deleting CurrentResourceOwner ... */
690  Assert(owner != CurrentResourceOwner);
691 
692  /* And it better not own any resources, either */
693  Assert(owner->bufferarr.nitems == 0);
694  Assert(owner->catrefarr.nitems == 0);
695  Assert(owner->catlistrefarr.nitems == 0);
696  Assert(owner->relrefarr.nitems == 0);
697  Assert(owner->planrefarr.nitems == 0);
698  Assert(owner->tupdescarr.nitems == 0);
699  Assert(owner->snapshotarr.nitems == 0);
700  Assert(owner->filearr.nitems == 0);
701  Assert(owner->dsmarr.nitems == 0);
702  Assert(owner->nlocks == 0 || owner->nlocks == MAX_RESOWNER_LOCKS + 1);
703 
704  /*
705  * Delete children. The recursive call will delink the child from me, so
706  * just iterate as long as there is a child.
707  */
708  while (owner->firstchild != NULL)
710 
711  /*
712  * We delink the owner from its parent before deleting it, so that if
713  * there's an error we won't have deleted/busted owners still attached to
714  * the owner tree. Better a leak than a crash.
715  */
717 
718  /* And free the object. */
719  ResourceArrayFree(&(owner->bufferarr));
720  ResourceArrayFree(&(owner->catrefarr));
721  ResourceArrayFree(&(owner->catlistrefarr));
722  ResourceArrayFree(&(owner->relrefarr));
723  ResourceArrayFree(&(owner->planrefarr));
724  ResourceArrayFree(&(owner->tupdescarr));
725  ResourceArrayFree(&(owner->snapshotarr));
726  ResourceArrayFree(&(owner->filearr));
727  ResourceArrayFree(&(owner->dsmarr));
728 
729  pfree(owner);
730 }
731 
732 /*
733  * Fetch parent of a ResourceOwner (returns NULL if top-level owner)
734  */
737 {
738  return owner->parent;
739 }
740 
741 /*
742  * Reassign a ResourceOwner to have a new parent
743  */
744 void
746  ResourceOwner newparent)
747 {
748  ResourceOwner oldparent = owner->parent;
749 
750  if (oldparent)
751  {
752  if (owner == oldparent->firstchild)
753  oldparent->firstchild = owner->nextchild;
754  else
755  {
756  ResourceOwner child;
757 
758  for (child = oldparent->firstchild; child; child = child->nextchild)
759  {
760  if (owner == child->nextchild)
761  {
762  child->nextchild = owner->nextchild;
763  break;
764  }
765  }
766  }
767  }
768 
769  if (newparent)
770  {
771  Assert(owner != newparent);
772  owner->parent = newparent;
773  owner->nextchild = newparent->firstchild;
774  newparent->firstchild = owner;
775  }
776  else
777  {
778  owner->parent = NULL;
779  owner->nextchild = NULL;
780  }
781 }
782 
783 /*
784  * Register or deregister callback functions for resource cleanup
785  *
786  * These functions are intended for use by dynamically loaded modules.
787  * For built-in modules we generally just hardwire the appropriate calls.
788  *
789  * Note that the callback occurs post-commit or post-abort, so the callback
790  * functions can only do noncritical cleanup.
791  */
792 void
794 {
796 
797  item = (ResourceReleaseCallbackItem *)
800  item->callback = callback;
801  item->arg = arg;
803  ResourceRelease_callbacks = item;
804 }
805 
806 void
808 {
811 
812  prev = NULL;
813  for (item = ResourceRelease_callbacks; item; prev = item, item = item->next)
814  {
815  if (item->callback == callback && item->arg == arg)
816  {
817  if (prev)
818  prev->next = item->next;
819  else
820  ResourceRelease_callbacks = item->next;
821  pfree(item);
822  break;
823  }
824  }
825 }
826 
827 
828 /*
829  * Make sure there is room for at least one more entry in a ResourceOwner's
830  * buffer array.
831  *
832  * This is separate from actually inserting an entry because if we run out
833  * of memory, it's critical to do so *before* acquiring the resource.
834  *
835  * We allow the case owner == NULL because the bufmgr is sometimes invoked
836  * outside any transaction (for example, during WAL recovery).
837  */
838 void
840 {
841  if (owner == NULL)
842  return;
843  ResourceArrayEnlarge(&(owner->bufferarr));
844 }
845 
846 /*
847  * Remember that a buffer pin is owned by a ResourceOwner
848  *
849  * Caller must have previously done ResourceOwnerEnlargeBuffers()
850  *
851  * We allow the case owner == NULL because the bufmgr is sometimes invoked
852  * outside any transaction (for example, during WAL recovery).
853  */
854 void
856 {
857  if (owner == NULL)
858  return;
859  ResourceArrayAdd(&(owner->bufferarr), BufferGetDatum(buffer));
860 }
861 
862 /*
863  * Forget that a buffer pin is owned by a ResourceOwner
864  *
865  * We allow the case owner == NULL because the bufmgr is sometimes invoked
866  * outside any transaction (for example, during WAL recovery).
867  */
868 void
870 {
871  if (owner == NULL)
872  return;
873  if (!ResourceArrayRemove(&(owner->bufferarr), BufferGetDatum(buffer)))
874  elog(ERROR, "buffer %d is not owned by resource owner %s",
875  buffer, owner->name);
876 }
877 
878 /*
879  * Remember that a Local Lock is owned by a ResourceOwner
880  *
881  * This is different from the other Remember functions in that the list of
882  * locks is only a lossy cache. It can hold up to MAX_RESOWNER_LOCKS entries,
883  * and when it overflows, we stop tracking locks. The point of only remembering
884  * only up to MAX_RESOWNER_LOCKS entries is that if a lot of locks are held,
885  * ResourceOwnerForgetLock doesn't need to scan through a large array to find
886  * the entry.
887  */
888 void
890 {
891  Assert(locallock != NULL);
892 
893  if (owner->nlocks > MAX_RESOWNER_LOCKS)
894  return; /* we have already overflowed */
895 
896  if (owner->nlocks < MAX_RESOWNER_LOCKS)
897  owner->locks[owner->nlocks] = locallock;
898  else
899  {
900  /* overflowed */
901  }
902  owner->nlocks++;
903 }
904 
905 /*
906  * Forget that a Local Lock is owned by a ResourceOwner
907  */
908 void
910 {
911  int i;
912 
913  if (owner->nlocks > MAX_RESOWNER_LOCKS)
914  return; /* we have overflowed */
915 
916  Assert(owner->nlocks > 0);
917  for (i = owner->nlocks - 1; i >= 0; i--)
918  {
919  if (locallock == owner->locks[i])
920  {
921  owner->locks[i] = owner->locks[owner->nlocks - 1];
922  owner->nlocks--;
923  return;
924  }
925  }
926  elog(ERROR, "lock reference %p is not owned by resource owner %s",
927  locallock, owner->name);
928 }
929 
930 /*
931  * Make sure there is room for at least one more entry in a ResourceOwner's
932  * catcache reference array.
933  *
934  * This is separate from actually inserting an entry because if we run out
935  * of memory, it's critical to do so *before* acquiring the resource.
936  */
937 void
939 {
940  ResourceArrayEnlarge(&(owner->catrefarr));
941 }
942 
943 /*
944  * Remember that a catcache reference is owned by a ResourceOwner
945  *
946  * Caller must have previously done ResourceOwnerEnlargeCatCacheRefs()
947  */
948 void
950 {
951  ResourceArrayAdd(&(owner->catrefarr), PointerGetDatum(tuple));
952 }
953 
954 /*
955  * Forget that a catcache reference is owned by a ResourceOwner
956  */
957 void
959 {
960  if (!ResourceArrayRemove(&(owner->catrefarr), PointerGetDatum(tuple)))
961  elog(ERROR, "catcache reference %p is not owned by resource owner %s",
962  tuple, owner->name);
963 }
964 
965 /*
966  * Make sure there is room for at least one more entry in a ResourceOwner's
967  * catcache-list reference array.
968  *
969  * This is separate from actually inserting an entry because if we run out
970  * of memory, it's critical to do so *before* acquiring the resource.
971  */
972 void
974 {
976 }
977 
978 /*
979  * Remember that a catcache-list reference is owned by a ResourceOwner
980  *
981  * Caller must have previously done ResourceOwnerEnlargeCatCacheListRefs()
982  */
983 void
985 {
987 }
988 
989 /*
990  * Forget that a catcache-list reference is owned by a ResourceOwner
991  */
992 void
994 {
995  if (!ResourceArrayRemove(&(owner->catlistrefarr), PointerGetDatum(list)))
996  elog(ERROR, "catcache list reference %p is not owned by resource owner %s",
997  list, owner->name);
998 }
999 
1000 /*
1001  * Make sure there is room for at least one more entry in a ResourceOwner's
1002  * relcache reference array.
1003  *
1004  * This is separate from actually inserting an entry because if we run out
1005  * of memory, it's critical to do so *before* acquiring the resource.
1006  */
1007 void
1009 {
1010  ResourceArrayEnlarge(&(owner->relrefarr));
1011 }
1012 
1013 /*
1014  * Remember that a relcache reference is owned by a ResourceOwner
1015  *
1016  * Caller must have previously done ResourceOwnerEnlargeRelationRefs()
1017  */
1018 void
1020 {
1021  ResourceArrayAdd(&(owner->relrefarr), PointerGetDatum(rel));
1022 }
1023 
1024 /*
1025  * Forget that a relcache reference is owned by a ResourceOwner
1026  */
1027 void
1029 {
1030  if (!ResourceArrayRemove(&(owner->relrefarr), PointerGetDatum(rel)))
1031  elog(ERROR, "relcache reference %s is not owned by resource owner %s",
1032  RelationGetRelationName(rel), owner->name);
1033 }
1034 
1035 /*
1036  * Debugging subroutine
1037  */
1038 static void
1040 {
1041  elog(WARNING, "relcache reference leak: relation \"%s\" not closed",
1043 }
1044 
1045 /*
1046  * Make sure there is room for at least one more entry in a ResourceOwner's
1047  * plancache reference array.
1048  *
1049  * This is separate from actually inserting an entry because if we run out
1050  * of memory, it's critical to do so *before* acquiring the resource.
1051  */
1052 void
1054 {
1055  ResourceArrayEnlarge(&(owner->planrefarr));
1056 }
1057 
1058 /*
1059  * Remember that a plancache reference is owned by a ResourceOwner
1060  *
1061  * Caller must have previously done ResourceOwnerEnlargePlanCacheRefs()
1062  */
1063 void
1065 {
1066  ResourceArrayAdd(&(owner->planrefarr), PointerGetDatum(plan));
1067 }
1068 
1069 /*
1070  * Forget that a plancache reference is owned by a ResourceOwner
1071  */
1072 void
1074 {
1075  if (!ResourceArrayRemove(&(owner->planrefarr), PointerGetDatum(plan)))
1076  elog(ERROR, "plancache reference %p is not owned by resource owner %s",
1077  plan, owner->name);
1078 }
1079 
1080 /*
1081  * Debugging subroutine
1082  */
1083 static void
1085 {
1086  elog(WARNING, "plancache reference leak: plan %p not closed", plan);
1087 }
1088 
1089 /*
1090  * Make sure there is room for at least one more entry in a ResourceOwner's
1091  * tupdesc reference array.
1092  *
1093  * This is separate from actually inserting an entry because if we run out
1094  * of memory, it's critical to do so *before* acquiring the resource.
1095  */
1096 void
1098 {
1099  ResourceArrayEnlarge(&(owner->tupdescarr));
1100 }
1101 
1102 /*
1103  * Remember that a tupdesc reference is owned by a ResourceOwner
1104  *
1105  * Caller must have previously done ResourceOwnerEnlargeTupleDescs()
1106  */
1107 void
1109 {
1110  ResourceArrayAdd(&(owner->tupdescarr), PointerGetDatum(tupdesc));
1111 }
1112 
1113 /*
1114  * Forget that a tupdesc reference is owned by a ResourceOwner
1115  */
1116 void
1118 {
1119  if (!ResourceArrayRemove(&(owner->tupdescarr), PointerGetDatum(tupdesc)))
1120  elog(ERROR, "tupdesc reference %p is not owned by resource owner %s",
1121  tupdesc, owner->name);
1122 }
1123 
1124 /*
1125  * Debugging subroutine
1126  */
1127 static void
1129 {
1130  elog(WARNING,
1131  "TupleDesc reference leak: TupleDesc %p (%u,%d) still referenced",
1132  tupdesc, tupdesc->tdtypeid, tupdesc->tdtypmod);
1133 }
1134 
1135 /*
1136  * Make sure there is room for at least one more entry in a ResourceOwner's
1137  * snapshot reference array.
1138  *
1139  * This is separate from actually inserting an entry because if we run out
1140  * of memory, it's critical to do so *before* acquiring the resource.
1141  */
1142 void
1144 {
1145  ResourceArrayEnlarge(&(owner->snapshotarr));
1146 }
1147 
1148 /*
1149  * Remember that a snapshot reference is owned by a ResourceOwner
1150  *
1151  * Caller must have previously done ResourceOwnerEnlargeSnapshots()
1152  */
1153 void
1155 {
1156  ResourceArrayAdd(&(owner->snapshotarr), PointerGetDatum(snapshot));
1157 }
1158 
1159 /*
1160  * Forget that a snapshot reference is owned by a ResourceOwner
1161  */
1162 void
1164 {
1165  if (!ResourceArrayRemove(&(owner->snapshotarr), PointerGetDatum(snapshot)))
1166  elog(ERROR, "snapshot reference %p is not owned by resource owner %s",
1167  snapshot, owner->name);
1168 }
1169 
1170 /*
1171  * Debugging subroutine
1172  */
1173 static void
1175 {
1176  elog(WARNING, "Snapshot reference leak: Snapshot %p still referenced",
1177  snapshot);
1178 }
1179 
1180 
1181 /*
1182  * Make sure there is room for at least one more entry in a ResourceOwner's
1183  * files reference array.
1184  *
1185  * This is separate from actually inserting an entry because if we run out
1186  * of memory, it's critical to do so *before* acquiring the resource.
1187  */
1188 void
1190 {
1191  ResourceArrayEnlarge(&(owner->filearr));
1192 }
1193 
1194 /*
1195  * Remember that a temporary file is owned by a ResourceOwner
1196  *
1197  * Caller must have previously done ResourceOwnerEnlargeFiles()
1198  */
1199 void
1201 {
1202  ResourceArrayAdd(&(owner->filearr), FileGetDatum(file));
1203 }
1204 
1205 /*
1206  * Forget that a temporary file is owned by a ResourceOwner
1207  */
1208 void
1210 {
1211  if (!ResourceArrayRemove(&(owner->filearr), FileGetDatum(file)))
1212  elog(ERROR, "temporary file %d is not owned by resource owner %s",
1213  file, owner->name);
1214 }
1215 
1216 /*
1217  * Debugging subroutine
1218  */
1219 static void
1221 {
1222  elog(WARNING, "temporary file leak: File %d still referenced",
1223  file);
1224 }
1225 
1226 /*
1227  * Make sure there is room for at least one more entry in a ResourceOwner's
1228  * dynamic shmem segment reference array.
1229  *
1230  * This is separate from actually inserting an entry because if we run out
1231  * of memory, it's critical to do so *before* acquiring the resource.
1232  */
1233 void
1235 {
1236  ResourceArrayEnlarge(&(owner->dsmarr));
1237 }
1238 
1239 /*
1240  * Remember that a dynamic shmem segment is owned by a ResourceOwner
1241  *
1242  * Caller must have previously done ResourceOwnerEnlargeDSMs()
1243  */
1244 void
1246 {
1247  ResourceArrayAdd(&(owner->dsmarr), PointerGetDatum(seg));
1248 }
1249 
1250 /*
1251  * Forget that a dynamic shmem segment is owned by a ResourceOwner
1252  */
1253 void
1255 {
1256  if (!ResourceArrayRemove(&(owner->dsmarr), PointerGetDatum(seg)))
1257  elog(ERROR, "dynamic shared memory segment %u is not owned by resource owner %s",
1258  dsm_segment_handle(seg), owner->name);
1259 }
1260 
1261 /*
1262  * Debugging subroutine
1263  */
1264 static void
1266 {
1267  elog(WARNING, "dynamic shared memory leak: segment %u still referenced",
1268  dsm_segment_handle(seg));
1269 }
#define DatumGetUInt32(X)
Definition: postgres.h:492
#define RESARRAY_INIT_SIZE
Definition: resowner.c:74
static void ResourceArrayEnlarge(ResourceArray *resarr)
Definition: resowner.c:202
ResourceArray relrefarr
Definition: resowner.c:121
void UnregisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg)
Definition: resowner.c:807
void ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg)
Definition: resowner.c:1245
void PrintCatCacheListLeakWarning(CatCList *list)
Definition: catcache.c:1912
void ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
Definition: resowner.c:949
HeapTupleData * HeapTuple
Definition: htup.h:70
Oid tdtypeid
Definition: tupdesc.h:77
void PrintBufferLeakWarning(Buffer buffer)
Definition: bufmgr.c:2531
ResourceArray catrefarr
Definition: resowner.c:119
#define DatumGetFile(datum)
Definition: resowner.c:40
ResourceOwner TopTransactionResourceOwner
Definition: resowner.c:140
#define PointerGetDatum(X)
Definition: postgres.h:562
ResourceOwner CurTransactionResourceOwner
Definition: resowner.c:139
ResourceOwner CurrentResourceOwner
Definition: resowner.c:138
uint32 lastidx
Definition: resowner.c:67
void ResourceOwnerEnlargeSnapshots(ResourceOwner owner)
Definition: resowner.c:1143
void ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel)
Definition: resowner.c:1019
void ResourceOwnerDelete(ResourceOwner owner)
Definition: resowner.c:687
#define InvalidBuffer
Definition: buf.h:25
dsm_handle dsm_segment_handle(dsm_segment *seg)
Definition: dsm.c:1028
void ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner)
Definition: resowner.c:973
void ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
Definition: resowner.c:958
void ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
Definition: resowner.c:1064
uint32 capacity
Definition: resowner.c:64
void ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner)
Definition: resowner.c:1053
Datum idx(PG_FUNCTION_ARGS)
Definition: _int_op.c:264
void ReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:3309
ResourceArray bufferarr
Definition: resowner.c:118
static bool ResourceArrayGetAny(ResourceArray *resarr, Datum *value)
Definition: resowner.c:364
ResourceArray snapshotarr
Definition: resowner.c:124
struct SnapshotData * Snapshot
Definition: snapshot.h:23
static void ResourceOwnerReleaseInternal(ResourceOwner owner, ResourceReleasePhase phase, bool isCommit, bool isTopLevel)
Definition: resowner.c:494
void ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
Definition: resowner.c:855
void ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
Definition: resowner.c:1108
int32 tdtypmod
Definition: tupdesc.h:78
static ResourceReleaseCallbackItem * ResourceRelease_callbacks
Definition: resowner.c:152
void ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
Definition: resowner.c:1073
void ReleaseCatCacheList(CatCList *list)
Definition: catcache.c:1651
struct ResourceOwnerData * ResourceOwner
Definition: resowner.h:27
#define MAX_RESOWNER_LOCKS
Definition: resowner.c:105
void LockReleaseCurrentOwner(LOCALLOCK **locallocks, int nlocks)
Definition: lock.c:2318
struct RelationData * Relation
Definition: relcache.h:21
#define RESARRAY_MAX_ITEMS(capacity)
Definition: resowner.c:86
const char * name
Definition: resowner.c:115
void pfree(void *pointer)
Definition: mcxt.c:950
struct ResourceArray ResourceArray
#define ERROR
Definition: elog.h:43
Datum * itemsarr
Definition: resowner.c:62
static void PrintSnapshotLeakWarning(Snapshot snapshot)
Definition: resowner.c:1174
void ResourceOwnerForgetRelationRef(ResourceOwner owner, Relation rel)
Definition: resowner.c:1028
Datum invalidval
Definition: resowner.c:63
ResourceArray planrefarr
Definition: resowner.c:122
static struct @121 value
static void callback(struct sockaddr *addr, struct sockaddr *mask, void *unused)
Definition: test_ifaddrs.c:48
static void ResourceArrayFree(ResourceArray *resarr)
Definition: resowner.c:396
void LockReassignCurrentOwner(LOCALLOCK **locallocks, int nlocks)
Definition: lock.c:2413
static void PrintPlanCacheLeakWarning(CachedPlan *plan)
Definition: resowner.c:1084
void ResourceOwnerEnlargeDSMs(ResourceOwner owner)
Definition: resowner.c:1234
void ResourceOwnerRememberFile(ResourceOwner owner, File file)
Definition: resowner.c:1200
void ResourceOwnerForgetSnapshot(ResourceOwner owner, Snapshot snapshot)
Definition: resowner.c:1163
void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
Definition: plancache.c:1256
ResourceArray tupdescarr
Definition: resowner.c:123
#define RelationGetRelationName(relation)
Definition: rel.h:436
void ProcReleaseLocks(bool isCommit)
Definition: proc.c:753
unsigned int uint32
Definition: c.h:268
void ResourceOwnerEnlargeRelationRefs(ResourceOwner owner)
Definition: resowner.c:1008
ResourceReleasePhase
Definition: resowner.h:45
struct ResourceReleaseCallbackItem ResourceReleaseCallbackItem
void RelationClose(Relation relation)
Definition: relcache.c:2160
void ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
Definition: resowner.c:1117
void ResourceOwnerEnlargeTupleDescs(ResourceOwner owner)
Definition: resowner.c:1097
static void ResourceArrayInit(ResourceArray *resarr, Datum invalidval)
Definition: resowner.c:183
MemoryContext TopMemoryContext
Definition: mcxt.c:43
void UnregisterSnapshot(Snapshot snapshot)
Definition: snapmgr.c:905
void ResourceOwnerEnlargeBuffers(ResourceOwner owner)
Definition: resowner.c:839
#define WARNING
Definition: elog.h:40
static void PrintRelCacheLeakWarning(Relation rel)
Definition: resowner.c:1039
struct ResourceReleaseCallbackItem * next
Definition: resowner.c:147
uint32 maxitems
Definition: resowner.c:66
ResourceOwner ResourceOwnerGetParent(ResourceOwner owner)
Definition: resowner.c:736
uintptr_t Datum
Definition: postgres.h:372
void ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock)
Definition: resowner.c:909
ResourceReleaseCallback callback
Definition: resowner.c:148
ResourceOwner firstchild
Definition: resowner.c:113
void PrintCatCacheLeakWarning(HeapTuple tuple)
Definition: catcache.c:1896
void * MemoryContextAllocZero(MemoryContext context, Size size)
Definition: mcxt.c:742
static void PrintTupleDescLeakWarning(TupleDesc tupdesc)
Definition: resowner.c:1128
void ReleaseCatCache(HeapTuple tuple)
Definition: catcache.c:1303
void ReleasePredicateLocks(bool isCommit)
Definition: predicate.c:3266
struct tupleDesc * TupleDesc
void ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
Definition: resowner.c:993
#define PG_CATCH()
Definition: elog.h:293
void FileClose(File file)
Definition: fd.c:1493
#define BufferGetDatum(buffer)
Definition: resowner.c:41
#define FileGetDatum(file)
Definition: resowner.c:39
#define NULL
Definition: c.h:229
#define Assert(condition)
Definition: c.h:675
void ResourceOwnerRelease(ResourceOwner owner, ResourceReleasePhase phase, bool isCommit, bool isTopLevel)
Definition: resowner.c:471
Datum hash_any(register const unsigned char *k, register int keylen)
Definition: hashfunc.c:307
void DecrTupleDescRefCount(TupleDesc tupdesc)
Definition: tupdesc.c:336
WalTimeSample buffer[LAG_TRACKER_BUFFER_SIZE]
Definition: walsender.c:214
void ResourceOwnerForgetDSM(ResourceOwner owner, dsm_segment *seg)
Definition: resowner.c:1254
#define PG_RE_THROW()
Definition: elog.h:314
static void PrintFileLeakWarning(File file)
Definition: resowner.c:1220
ResourceOwner parent
Definition: resowner.c:112
void(* ResourceReleaseCallback)(ResourceReleasePhase phase, bool isCommit, bool isTopLevel, void *arg)
Definition: resowner.h:56
const char * name
Definition: encode.c:521
static bool ResourceArrayRemove(ResourceArray *resarr, Datum value)
Definition: resowner.c:295
#define DatumGetPointer(X)
Definition: postgres.h:555
void ResourceOwnerNewParent(ResourceOwner owner, ResourceOwner newparent)
Definition: resowner.c:745
tuple list
Definition: sort-test.py:11
void dsm_detach(dsm_segment *seg)
Definition: dsm.c:726
#define DatumGetBuffer(datum)
Definition: resowner.c:42
void RegisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg)
Definition: resowner.c:793
void ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot)
Definition: resowner.c:1154
LOCALLOCK * locks[MAX_RESOWNER_LOCKS]
Definition: resowner.c:130
void ResourceOwnerEnlargeFiles(ResourceOwner owner)
Definition: resowner.c:1189
void * MemoryContextAlloc(MemoryContext context, Size size)
Definition: mcxt.c:707
int i
void * arg
ResourceArray catlistrefarr
Definition: resowner.c:120
struct ResourceOwnerData ResourceOwnerData
ResourceArray dsmarr
Definition: resowner.c:126
#define elog
Definition: elog.h:219
ResourceOwner nextchild
Definition: resowner.c:114
static void PrintDSMLeakWarning(dsm_segment *seg)
Definition: resowner.c:1265
static void ResourceArrayAdd(ResourceArray *resarr, Datum value)
Definition: resowner.c:257
#define PG_TRY()
Definition: elog.h:284
void ResourceOwnerForgetFile(ResourceOwner owner, File file)
Definition: resowner.c:1209
#define RESARRAY_IS_ARRAY(resarr)
Definition: resowner.c:80
int Buffer
Definition: buf.h:23
void ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK *locallock)
Definition: resowner.c:889
void ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner)
Definition: resowner.c:938
#define PG_END_TRY()
Definition: elog.h:300
int File
Definition: fd.h:51
uint32 nitems
Definition: resowner.c:65
ResourceArray filearr
Definition: resowner.c:125
ResourceOwner ResourceOwnerCreate(ResourceOwner parent, const char *name)
Definition: resowner.c:416
void ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer)
Definition: resowner.c:869
void ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
Definition: resowner.c:984