Line data Source code
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 */
68 : } ResourceArray;
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 */
131 : } ResourceOwnerData;
132 :
133 :
134 : /*****************************************************************************
135 : * GLOBAL MEMORY *
136 : *****************************************************************************/
137 :
138 : ResourceOwner CurrentResourceOwner = NULL;
139 : ResourceOwner CurTransactionResourceOwner = NULL;
140 : ResourceOwner TopTransactionResourceOwner = NULL;
141 :
142 : /*
143 : * List of add-on callbacks for resource releasing
144 : */
145 : typedef struct ResourceReleaseCallbackItem
146 : {
147 : struct ResourceReleaseCallbackItem *next;
148 : ResourceReleaseCallback callback;
149 : void *arg;
150 : } ResourceReleaseCallbackItem;
151 :
152 : static ResourceReleaseCallbackItem *ResourceRelease_callbacks = NULL;
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);
162 : static void ResourceOwnerReleaseInternal(ResourceOwner owner,
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
183 486774 : ResourceArrayInit(ResourceArray *resarr, Datum invalidval)
184 : {
185 : /* Assert it's empty */
186 486774 : Assert(resarr->itemsarr == NULL);
187 486774 : Assert(resarr->capacity == 0);
188 486774 : Assert(resarr->nitems == 0);
189 486774 : Assert(resarr->maxitems == 0);
190 : /* Remember the appropriate "invalid" value */
191 486774 : resarr->invalidval = invalidval;
192 : /* We don't allocate any storage until needed */
193 486774 : }
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
202 7555072 : ResourceArrayEnlarge(ResourceArray *resarr)
203 : {
204 : uint32 i,
205 : oldcap,
206 : newcap;
207 : Datum *olditemsarr;
208 : Datum *newitemsarr;
209 :
210 7555072 : if (resarr->nitems < resarr->maxitems)
211 14941258 : return; /* no work needed */
212 :
213 168886 : olditemsarr = resarr->itemsarr;
214 168886 : oldcap = resarr->capacity;
215 :
216 : /* Double the capacity of the array (capacity must stay a power of 2!) */
217 168886 : newcap = (oldcap > 0) ? oldcap * 2 : RESARRAY_INIT_SIZE;
218 168886 : newitemsarr = (Datum *) MemoryContextAlloc(TopMemoryContext,
219 : newcap * sizeof(Datum));
220 2889254 : for (i = 0; i < newcap; i++)
221 2720368 : newitemsarr[i] = resarr->invalidval;
222 :
223 : /* We assume we can't fail below this point, so OK to scribble on resarr */
224 168886 : resarr->itemsarr = newitemsarr;
225 168886 : resarr->capacity = newcap;
226 168886 : resarr->maxitems = RESARRAY_MAX_ITEMS(newcap);
227 168886 : resarr->nitems = 0;
228 :
229 168886 : 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 10167 : for (i = 0; i < oldcap; i++)
239 : {
240 10048 : if (olditemsarr[i] != resarr->invalidval)
241 8032 : ResourceArrayAdd(resarr, olditemsarr[i]);
242 : }
243 :
244 : /* And release old array. */
245 119 : pfree(olditemsarr);
246 : }
247 :
248 168886 : 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
257 7567279 : ResourceArrayAdd(ResourceArray *resarr, Datum value)
258 : {
259 : uint32 idx;
260 :
261 7567279 : Assert(value != resarr->invalidval);
262 7567279 : Assert(resarr->nitems < resarr->maxitems);
263 :
264 7567279 : if (RESARRAY_IS_ARRAY(resarr))
265 : {
266 : /* Append to linear array. */
267 7556643 : idx = resarr->nitems;
268 : }
269 : else
270 : {
271 : /* Insert into first free slot at or after hash location. */
272 10636 : uint32 mask = resarr->capacity - 1;
273 :
274 10636 : idx = DatumGetUInt32(hash_any((void *) &value, sizeof(value))) & mask;
275 : for (;;)
276 : {
277 41146 : if (resarr->itemsarr[idx] == resarr->invalidval)
278 10636 : break;
279 30510 : idx = (idx + 1) & mask;
280 30510 : }
281 : }
282 7567279 : resarr->lastidx = idx;
283 7567279 : resarr->itemsarr[idx] = value;
284 7567279 : resarr->nitems++;
285 7567279 : }
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
295 7559247 : ResourceArrayRemove(ResourceArray *resarr, Datum value)
296 : {
297 : uint32 i,
298 : idx,
299 7559247 : lastidx = resarr->lastidx;
300 :
301 7559247 : Assert(value != resarr->invalidval);
302 :
303 : /* Search through all items, but try lastidx first. */
304 7559247 : if (RESARRAY_IS_ARRAY(resarr))
305 : {
306 15109318 : if (lastidx < resarr->nitems &&
307 7554659 : resarr->itemsarr[lastidx] == value)
308 : {
309 7273434 : resarr->itemsarr[lastidx] = resarr->itemsarr[resarr->nitems - 1];
310 7273434 : resarr->nitems--;
311 : /* Update lastidx to make reverse-order removals fast. */
312 7273434 : resarr->lastidx = resarr->nitems - 1;
313 7273434 : return true;
314 : }
315 559479 : for (i = 0; i < resarr->nitems; i++)
316 : {
317 559479 : if (resarr->itemsarr[i] == value)
318 : {
319 281225 : resarr->itemsarr[i] = resarr->itemsarr[resarr->nitems - 1];
320 281225 : resarr->nitems--;
321 : /* Update lastidx to make reverse-order removals fast. */
322 281225 : resarr->lastidx = resarr->nitems - 1;
323 281225 : return true;
324 : }
325 : }
326 : }
327 : else
328 : {
329 4588 : uint32 mask = resarr->capacity - 1;
330 :
331 9176 : if (lastidx < resarr->capacity &&
332 4588 : resarr->itemsarr[lastidx] == value)
333 : {
334 4588 : resarr->itemsarr[lastidx] = resarr->invalidval;
335 4588 : resarr->nitems--;
336 4588 : return true;
337 : }
338 0 : idx = DatumGetUInt32(hash_any((void *) &value, sizeof(value))) & mask;
339 0 : for (i = 0; i < resarr->capacity; i++)
340 : {
341 0 : if (resarr->itemsarr[idx] == value)
342 : {
343 0 : resarr->itemsarr[idx] = resarr->invalidval;
344 0 : resarr->nitems--;
345 0 : return true;
346 : }
347 0 : idx = (idx + 1) & mask;
348 : }
349 : }
350 :
351 0 : 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
364 497204 : ResourceArrayGetAny(ResourceArray *resarr, Datum *value)
365 : {
366 497204 : if (resarr->nitems == 0)
367 485703 : return false;
368 :
369 11501 : if (RESARRAY_IS_ARRAY(resarr))
370 : {
371 : /* Linear array: just return the first element. */
372 6913 : resarr->lastidx = 0;
373 : }
374 : else
375 : {
376 : /* Hash: search forward from wherever we were last. */
377 4588 : uint32 mask = resarr->capacity - 1;
378 :
379 : for (;;)
380 : {
381 12779 : resarr->lastidx &= mask;
382 12779 : if (resarr->itemsarr[resarr->lastidx] != resarr->invalidval)
383 4588 : break;
384 8191 : resarr->lastidx++;
385 8191 : }
386 : }
387 :
388 11501 : *value = resarr->itemsarr[resarr->lastidx];
389 11501 : return true;
390 : }
391 :
392 : /*
393 : * Trash a ResourceArray (we don't care about its state after this)
394 : */
395 : static void
396 485712 : ResourceArrayFree(ResourceArray *resarr)
397 : {
398 485712 : if (resarr->itemsarr)
399 168650 : pfree(resarr->itemsarr);
400 485712 : }
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 : */
415 : ResourceOwner
416 54086 : ResourceOwnerCreate(ResourceOwner parent, const char *name)
417 : {
418 : ResourceOwner owner;
419 :
420 54086 : owner = (ResourceOwner) MemoryContextAllocZero(TopMemoryContext,
421 : sizeof(ResourceOwnerData));
422 54086 : owner->name = name;
423 :
424 54086 : if (parent)
425 : {
426 27750 : owner->parent = parent;
427 27750 : owner->nextchild = parent->firstchild;
428 27750 : parent->firstchild = owner;
429 : }
430 :
431 54086 : ResourceArrayInit(&(owner->bufferarr), BufferGetDatum(InvalidBuffer));
432 54086 : ResourceArrayInit(&(owner->catrefarr), PointerGetDatum(NULL));
433 54086 : ResourceArrayInit(&(owner->catlistrefarr), PointerGetDatum(NULL));
434 54086 : ResourceArrayInit(&(owner->relrefarr), PointerGetDatum(NULL));
435 54086 : ResourceArrayInit(&(owner->planrefarr), PointerGetDatum(NULL));
436 54086 : ResourceArrayInit(&(owner->tupdescarr), PointerGetDatum(NULL));
437 54086 : ResourceArrayInit(&(owner->snapshotarr), PointerGetDatum(NULL));
438 54086 : ResourceArrayInit(&(owner->filearr), FileGetDatum(-1));
439 54086 : ResourceArrayInit(&(owner->dsmarr), PointerGetDatum(NULL));
440 :
441 54086 : 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
471 155322 : ResourceOwnerRelease(ResourceOwner owner,
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 155322 : save = CurrentResourceOwner;
480 155322 : PG_TRY();
481 : {
482 155322 : ResourceOwnerReleaseInternal(owner, phase, isCommit, isTopLevel);
483 : }
484 0 : PG_CATCH();
485 : {
486 0 : CurrentResourceOwner = save;
487 0 : PG_RE_THROW();
488 : }
489 155322 : PG_END_TRY();
490 155322 : CurrentResourceOwner = save;
491 155322 : }
492 :
493 : static void
494 161901 : ResourceOwnerReleaseInternal(ResourceOwner owner,
495 : ResourceReleasePhase phase,
496 : bool isCommit,
497 : bool isTopLevel)
498 : {
499 : ResourceOwner child;
500 : ResourceOwner save;
501 : ResourceReleaseCallbackItem *item;
502 : Datum foundres;
503 :
504 : /* Recurse to handle descendants */
505 168480 : for (child = owner->firstchild; child != NULL; child = child->nextchild)
506 6579 : 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 161901 : save = CurrentResourceOwner;
514 161901 : CurrentResourceOwner = owner;
515 :
516 161901 : 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 108365 : while (ResourceArrayGetAny(&(owner->bufferarr), &foundres))
528 : {
529 431 : Buffer res = DatumGetBuffer(foundres);
530 :
531 431 : if (isCommit)
532 0 : PrintBufferLeakWarning(res);
533 431 : ReleaseBuffer(res);
534 : }
535 :
536 : /* Ditto for relcache references */
537 110696 : while (ResourceArrayGetAny(&(owner->relrefarr), &foundres))
538 : {
539 2762 : Relation res = (Relation) DatumGetPointer(foundres);
540 :
541 2762 : if (isCommit)
542 0 : PrintRelCacheLeakWarning(res);
543 2762 : RelationClose(res);
544 : }
545 :
546 : /* Ditto for dynamic shared memory segments */
547 107934 : while (ResourceArrayGetAny(&(owner->dsmarr), &foundres))
548 : {
549 0 : dsm_segment *res = (dsm_segment *) DatumGetPointer(foundres);
550 :
551 0 : if (isCommit)
552 0 : PrintDSMLeakWarning(res);
553 0 : dsm_detach(res);
554 : }
555 : }
556 107934 : else if (phase == RESOURCE_RELEASE_LOCKS)
557 : {
558 53967 : 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 28385 : if (owner == TopTransactionResourceOwner)
566 : {
567 26218 : ProcReleaseLocks(isCommit);
568 26218 : 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 25582 : 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 25582 : if (owner->nlocks > MAX_RESOWNER_LOCKS)
588 : {
589 198 : locks = NULL;
590 198 : nlocks = 0;
591 : }
592 : else
593 : {
594 25384 : locks = owner->locks;
595 25384 : nlocks = owner->nlocks;
596 : }
597 :
598 25582 : if (isCommit)
599 25233 : LockReassignCurrentOwner(locks, nlocks);
600 : else
601 349 : LockReleaseCurrentOwner(locks, nlocks);
602 : }
603 : }
604 53967 : 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 108817 : while (ResourceArrayGetAny(&(owner->catrefarr), &foundres))
614 : {
615 883 : HeapTuple res = (HeapTuple) DatumGetPointer(foundres);
616 :
617 883 : if (isCommit)
618 0 : PrintCatCacheLeakWarning(res);
619 883 : ReleaseCatCache(res);
620 : }
621 :
622 : /* Ditto for catcache lists */
623 107937 : while (ResourceArrayGetAny(&(owner->catlistrefarr), &foundres))
624 : {
625 3 : CatCList *res = (CatCList *) DatumGetPointer(foundres);
626 :
627 3 : if (isCommit)
628 0 : PrintCatCacheListLeakWarning(res);
629 3 : ReleaseCatCacheList(res);
630 : }
631 :
632 : /* Ditto for plancache references */
633 108167 : while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
634 : {
635 233 : CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
636 :
637 233 : if (isCommit)
638 0 : PrintPlanCacheLeakWarning(res);
639 233 : ReleaseCachedPlan(res, true);
640 : }
641 :
642 : /* Ditto for tupdesc references */
643 108306 : while (ResourceArrayGetAny(&(owner->tupdescarr), &foundres))
644 : {
645 372 : TupleDesc res = (TupleDesc) DatumGetPointer(foundres);
646 :
647 372 : if (isCommit)
648 0 : PrintTupleDescLeakWarning(res);
649 372 : DecrTupleDescRefCount(res);
650 : }
651 :
652 : /* Ditto for snapshot references */
653 114751 : while (ResourceArrayGetAny(&(owner->snapshotarr), &foundres))
654 : {
655 6817 : Snapshot res = (Snapshot) DatumGetPointer(foundres);
656 :
657 6817 : if (isCommit)
658 0 : PrintSnapshotLeakWarning(res);
659 6817 : UnregisterSnapshot(res);
660 : }
661 :
662 : /* Ditto for temporary files */
663 107934 : while (ResourceArrayGetAny(&(owner->filearr), &foundres))
664 : {
665 0 : File res = DatumGetFile(foundres);
666 :
667 0 : if (isCommit)
668 0 : PrintFileLeakWarning(res);
669 0 : FileClose(res);
670 : }
671 : }
672 :
673 : /* Let add-on modules get a chance too */
674 161901 : for (item = ResourceRelease_callbacks; item; item = item->next)
675 0 : (*item->callback) (phase, isCommit, isTopLevel, item->arg);
676 :
677 161901 : CurrentResourceOwner = save;
678 161901 : }
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
687 53968 : ResourceOwnerDelete(ResourceOwner owner)
688 : {
689 : /* We had better not be deleting CurrentResourceOwner ... */
690 53968 : Assert(owner != CurrentResourceOwner);
691 :
692 : /* And it better not own any resources, either */
693 53968 : Assert(owner->bufferarr.nitems == 0);
694 53968 : Assert(owner->catrefarr.nitems == 0);
695 53968 : Assert(owner->catlistrefarr.nitems == 0);
696 53968 : Assert(owner->relrefarr.nitems == 0);
697 53968 : Assert(owner->planrefarr.nitems == 0);
698 53968 : Assert(owner->tupdescarr.nitems == 0);
699 53968 : Assert(owner->snapshotarr.nitems == 0);
700 53968 : Assert(owner->filearr.nitems == 0);
701 53968 : Assert(owner->dsmarr.nitems == 0);
702 53968 : 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 110130 : while (owner->firstchild != NULL)
709 2194 : ResourceOwnerDelete(owner->firstchild);
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 : */
716 53968 : ResourceOwnerNewParent(owner, NULL);
717 :
718 : /* And free the object. */
719 53968 : ResourceArrayFree(&(owner->bufferarr));
720 53968 : ResourceArrayFree(&(owner->catrefarr));
721 53968 : ResourceArrayFree(&(owner->catlistrefarr));
722 53968 : ResourceArrayFree(&(owner->relrefarr));
723 53968 : ResourceArrayFree(&(owner->planrefarr));
724 53968 : ResourceArrayFree(&(owner->tupdescarr));
725 53968 : ResourceArrayFree(&(owner->snapshotarr));
726 53968 : ResourceArrayFree(&(owner->filearr));
727 53968 : ResourceArrayFree(&(owner->dsmarr));
728 :
729 53968 : pfree(owner);
730 53968 : }
731 :
732 : /*
733 : * Fetch parent of a ResourceOwner (returns NULL if top-level owner)
734 : */
735 : ResourceOwner
736 25233 : ResourceOwnerGetParent(ResourceOwner owner)
737 : {
738 25233 : return owner->parent;
739 : }
740 :
741 : /*
742 : * Reassign a ResourceOwner to have a new parent
743 : */
744 : void
745 53970 : ResourceOwnerNewParent(ResourceOwner owner,
746 : ResourceOwner newparent)
747 : {
748 53970 : ResourceOwner oldparent = owner->parent;
749 :
750 53970 : if (oldparent)
751 : {
752 27752 : if (owner == oldparent->firstchild)
753 27609 : oldparent->firstchild = owner->nextchild;
754 : else
755 : {
756 : ResourceOwner child;
757 :
758 420 : for (child = oldparent->firstchild; child; child = child->nextchild)
759 : {
760 420 : if (owner == child->nextchild)
761 : {
762 143 : child->nextchild = owner->nextchild;
763 143 : break;
764 : }
765 : }
766 : }
767 : }
768 :
769 53970 : if (newparent)
770 : {
771 2 : Assert(owner != newparent);
772 2 : owner->parent = newparent;
773 2 : owner->nextchild = newparent->firstchild;
774 2 : newparent->firstchild = owner;
775 : }
776 : else
777 : {
778 53968 : owner->parent = NULL;
779 53968 : owner->nextchild = NULL;
780 : }
781 53970 : }
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
793 0 : RegisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg)
794 : {
795 : ResourceReleaseCallbackItem *item;
796 :
797 0 : item = (ResourceReleaseCallbackItem *)
798 0 : MemoryContextAlloc(TopMemoryContext,
799 : sizeof(ResourceReleaseCallbackItem));
800 0 : item->callback = callback;
801 0 : item->arg = arg;
802 0 : item->next = ResourceRelease_callbacks;
803 0 : ResourceRelease_callbacks = item;
804 0 : }
805 :
806 : void
807 0 : UnregisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg)
808 : {
809 : ResourceReleaseCallbackItem *item;
810 : ResourceReleaseCallbackItem *prev;
811 :
812 0 : prev = NULL;
813 0 : for (item = ResourceRelease_callbacks; item; prev = item, item = item->next)
814 : {
815 0 : if (item->callback == callback && item->arg == arg)
816 : {
817 0 : if (prev)
818 0 : prev->next = item->next;
819 : else
820 0 : ResourceRelease_callbacks = item->next;
821 0 : pfree(item);
822 0 : break;
823 : }
824 : }
825 0 : }
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
839 3577723 : ResourceOwnerEnlargeBuffers(ResourceOwner owner)
840 : {
841 3577723 : if (owner == NULL)
842 3577725 : return;
843 3577721 : 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
855 3585807 : ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
856 : {
857 3585807 : if (owner == NULL)
858 3585970 : return;
859 3585644 : 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
869 3585807 : ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer)
870 : {
871 3585807 : if (owner == NULL)
872 3585970 : return;
873 3585644 : if (!ResourceArrayRemove(&(owner->bufferarr), BufferGetDatum(buffer)))
874 0 : 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
889 928439 : ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK *locallock)
890 : {
891 928439 : Assert(locallock != NULL);
892 :
893 928439 : if (owner->nlocks > MAX_RESOWNER_LOCKS)
894 1030930 : return; /* we have already overflowed */
895 :
896 825948 : if (owner->nlocks < MAX_RESOWNER_LOCKS)
897 825496 : owner->locks[owner->nlocks] = locallock;
898 : else
899 : {
900 : /* overflowed */
901 : }
902 825948 : owner->nlocks++;
903 : }
904 :
905 : /*
906 : * Forget that a Local Lock is owned by a ResourceOwner
907 : */
908 : void
909 928439 : ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock)
910 : {
911 : int i;
912 :
913 928439 : if (owner->nlocks > MAX_RESOWNER_LOCKS)
914 109723 : return; /* we have overflowed */
915 :
916 818716 : Assert(owner->nlocks > 0);
917 956269 : for (i = owner->nlocks - 1; i >= 0; i--)
918 : {
919 956269 : if (locallock == owner->locks[i])
920 : {
921 818716 : owner->locks[i] = owner->locks[owner->nlocks - 1];
922 818716 : owner->nlocks--;
923 818716 : return;
924 : }
925 : }
926 0 : 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
938 1940857 : ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner)
939 : {
940 1940857 : ResourceArrayEnlarge(&(owner->catrefarr));
941 1940857 : }
942 :
943 : /*
944 : * Remember that a catcache reference is owned by a ResourceOwner
945 : *
946 : * Caller must have previously done ResourceOwnerEnlargeCatCacheRefs()
947 : */
948 : void
949 1940857 : ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
950 : {
951 1940857 : ResourceArrayAdd(&(owner->catrefarr), PointerGetDatum(tuple));
952 1940857 : }
953 :
954 : /*
955 : * Forget that a catcache reference is owned by a ResourceOwner
956 : */
957 : void
958 1940857 : ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
959 : {
960 1940857 : if (!ResourceArrayRemove(&(owner->catrefarr), PointerGetDatum(tuple)))
961 0 : elog(ERROR, "catcache reference %p is not owned by resource owner %s",
962 : tuple, owner->name);
963 1940857 : }
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
973 101636 : ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner)
974 : {
975 101636 : ResourceArrayEnlarge(&(owner->catlistrefarr));
976 101636 : }
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
984 101636 : ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
985 : {
986 101636 : ResourceArrayAdd(&(owner->catlistrefarr), PointerGetDatum(list));
987 101636 : }
988 :
989 : /*
990 : * Forget that a catcache-list reference is owned by a ResourceOwner
991 : */
992 : void
993 101636 : ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
994 : {
995 101636 : if (!ResourceArrayRemove(&(owner->catlistrefarr), PointerGetDatum(list)))
996 0 : elog(ERROR, "catcache list reference %p is not owned by resource owner %s",
997 : list, owner->name);
998 101636 : }
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
1008 1443625 : ResourceOwnerEnlargeRelationRefs(ResourceOwner owner)
1009 : {
1010 1443625 : ResourceArrayEnlarge(&(owner->relrefarr));
1011 1443625 : }
1012 :
1013 : /*
1014 : * Remember that a relcache reference is owned by a ResourceOwner
1015 : *
1016 : * Caller must have previously done ResourceOwnerEnlargeRelationRefs()
1017 : */
1018 : void
1019 1439877 : ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel)
1020 : {
1021 1439877 : ResourceArrayAdd(&(owner->relrefarr), PointerGetDatum(rel));
1022 1439877 : }
1023 :
1024 : /*
1025 : * Forget that a relcache reference is owned by a ResourceOwner
1026 : */
1027 : void
1028 1439877 : ResourceOwnerForgetRelationRef(ResourceOwner owner, Relation rel)
1029 : {
1030 1439877 : if (!ResourceArrayRemove(&(owner->relrefarr), PointerGetDatum(rel)))
1031 0 : elog(ERROR, "relcache reference %s is not owned by resource owner %s",
1032 : RelationGetRelationName(rel), owner->name);
1033 1439877 : }
1034 :
1035 : /*
1036 : * Debugging subroutine
1037 : */
1038 : static void
1039 0 : PrintRelCacheLeakWarning(Relation rel)
1040 : {
1041 0 : elog(WARNING, "relcache reference leak: relation \"%s\" not closed",
1042 : RelationGetRelationName(rel));
1043 0 : }
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
1053 32025 : ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner)
1054 : {
1055 32025 : ResourceArrayEnlarge(&(owner->planrefarr));
1056 32025 : }
1057 :
1058 : /*
1059 : * Remember that a plancache reference is owned by a ResourceOwner
1060 : *
1061 : * Caller must have previously done ResourceOwnerEnlargePlanCacheRefs()
1062 : */
1063 : void
1064 32025 : ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
1065 : {
1066 32025 : ResourceArrayAdd(&(owner->planrefarr), PointerGetDatum(plan));
1067 32025 : }
1068 :
1069 : /*
1070 : * Forget that a plancache reference is owned by a ResourceOwner
1071 : */
1072 : void
1073 32025 : ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
1074 : {
1075 32025 : if (!ResourceArrayRemove(&(owner->planrefarr), PointerGetDatum(plan)))
1076 0 : elog(ERROR, "plancache reference %p is not owned by resource owner %s",
1077 : plan, owner->name);
1078 32025 : }
1079 :
1080 : /*
1081 : * Debugging subroutine
1082 : */
1083 : static void
1084 0 : PrintPlanCacheLeakWarning(CachedPlan *plan)
1085 : {
1086 0 : elog(WARNING, "plancache reference leak: plan %p not closed", plan);
1087 0 : }
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
1097 88542 : ResourceOwnerEnlargeTupleDescs(ResourceOwner owner)
1098 : {
1099 88542 : ResourceArrayEnlarge(&(owner->tupdescarr));
1100 88542 : }
1101 :
1102 : /*
1103 : * Remember that a tupdesc reference is owned by a ResourceOwner
1104 : *
1105 : * Caller must have previously done ResourceOwnerEnlargeTupleDescs()
1106 : */
1107 : void
1108 88542 : ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
1109 : {
1110 88542 : ResourceArrayAdd(&(owner->tupdescarr), PointerGetDatum(tupdesc));
1111 88542 : }
1112 :
1113 : /*
1114 : * Forget that a tupdesc reference is owned by a ResourceOwner
1115 : */
1116 : void
1117 88542 : ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
1118 : {
1119 88542 : if (!ResourceArrayRemove(&(owner->tupdescarr), PointerGetDatum(tupdesc)))
1120 0 : elog(ERROR, "tupdesc reference %p is not owned by resource owner %s",
1121 : tupdesc, owner->name);
1122 88542 : }
1123 :
1124 : /*
1125 : * Debugging subroutine
1126 : */
1127 : static void
1128 0 : PrintTupleDescLeakWarning(TupleDesc tupdesc)
1129 : {
1130 0 : elog(WARNING,
1131 : "TupleDesc reference leak: TupleDesc %p (%u,%d) still referenced",
1132 : tupdesc, tupdesc->tdtypeid, tupdesc->tdtypmod);
1133 0 : }
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
1143 370464 : ResourceOwnerEnlargeSnapshots(ResourceOwner owner)
1144 : {
1145 370464 : ResourceArrayEnlarge(&(owner->snapshotarr));
1146 370464 : }
1147 :
1148 : /*
1149 : * Remember that a snapshot reference is owned by a ResourceOwner
1150 : *
1151 : * Caller must have previously done ResourceOwnerEnlargeSnapshots()
1152 : */
1153 : void
1154 370464 : ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot)
1155 : {
1156 370464 : ResourceArrayAdd(&(owner->snapshotarr), PointerGetDatum(snapshot));
1157 370464 : }
1158 :
1159 : /*
1160 : * Forget that a snapshot reference is owned by a ResourceOwner
1161 : */
1162 : void
1163 370464 : ResourceOwnerForgetSnapshot(ResourceOwner owner, Snapshot snapshot)
1164 : {
1165 370464 : if (!ResourceArrayRemove(&(owner->snapshotarr), PointerGetDatum(snapshot)))
1166 0 : elog(ERROR, "snapshot reference %p is not owned by resource owner %s",
1167 : snapshot, owner->name);
1168 370464 : }
1169 :
1170 : /*
1171 : * Debugging subroutine
1172 : */
1173 : static void
1174 0 : PrintSnapshotLeakWarning(Snapshot snapshot)
1175 : {
1176 0 : elog(WARNING, "Snapshot reference leak: Snapshot %p still referenced",
1177 : snapshot);
1178 0 : }
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
1189 24 : ResourceOwnerEnlargeFiles(ResourceOwner owner)
1190 : {
1191 24 : ResourceArrayEnlarge(&(owner->filearr));
1192 24 : }
1193 :
1194 : /*
1195 : * Remember that a temporary file is owned by a ResourceOwner
1196 : *
1197 : * Caller must have previously done ResourceOwnerEnlargeFiles()
1198 : */
1199 : void
1200 24 : ResourceOwnerRememberFile(ResourceOwner owner, File file)
1201 : {
1202 24 : ResourceArrayAdd(&(owner->filearr), FileGetDatum(file));
1203 24 : }
1204 :
1205 : /*
1206 : * Forget that a temporary file is owned by a ResourceOwner
1207 : */
1208 : void
1209 24 : ResourceOwnerForgetFile(ResourceOwner owner, File file)
1210 : {
1211 24 : if (!ResourceArrayRemove(&(owner->filearr), FileGetDatum(file)))
1212 0 : elog(ERROR, "temporary file %d is not owned by resource owner %s",
1213 : file, owner->name);
1214 24 : }
1215 :
1216 : /*
1217 : * Debugging subroutine
1218 : */
1219 : static void
1220 0 : PrintFileLeakWarning(File file)
1221 : {
1222 0 : elog(WARNING, "temporary file leak: File %d still referenced",
1223 : file);
1224 0 : }
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
1234 178 : ResourceOwnerEnlargeDSMs(ResourceOwner owner)
1235 : {
1236 178 : ResourceArrayEnlarge(&(owner->dsmarr));
1237 178 : }
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
1245 178 : ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg)
1246 : {
1247 178 : ResourceArrayAdd(&(owner->dsmarr), PointerGetDatum(seg));
1248 178 : }
1249 :
1250 : /*
1251 : * Forget that a dynamic shmem segment is owned by a ResourceOwner
1252 : */
1253 : void
1254 178 : ResourceOwnerForgetDSM(ResourceOwner owner, dsm_segment *seg)
1255 : {
1256 178 : if (!ResourceArrayRemove(&(owner->dsmarr), PointerGetDatum(seg)))
1257 0 : elog(ERROR, "dynamic shared memory segment %u is not owned by resource owner %s",
1258 : dsm_segment_handle(seg), owner->name);
1259 178 : }
1260 :
1261 : /*
1262 : * Debugging subroutine
1263 : */
1264 : static void
1265 0 : PrintDSMLeakWarning(dsm_segment *seg)
1266 : {
1267 0 : elog(WARNING, "dynamic shared memory leak: segment %u still referenced",
1268 : dsm_segment_handle(seg));
1269 0 : }
|