Age Owner Branch data TLA 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 on how to use it.
10 : : *
11 : : * The implementation consists of a small fixed-size array and a hash table.
12 : : * New entries are inserted to the fixed-size array, and when the array
13 : : * fills up, all the entries are moved to the hash table. This way, the
14 : : * array always contains a few most recently remembered references. To find
15 : : * a particular reference, you need to search both the array and the hash
16 : : * table.
17 : : *
18 : : * The most frequent usage is that a resource is remembered, and forgotten
19 : : * shortly thereafter. For example, pin a buffer, read one tuple from it,
20 : : * release the pin. Linearly scanning the small array handles that case
21 : : * efficiently. However, some resources are held for a longer time, and
22 : : * sometimes a lot of resources need to be held simultaneously. The hash
23 : : * table handles those cases.
24 : : *
25 : : * When it's time to release the resources, we sort them according to the
26 : : * release-priority of each resource, and release them in that order.
27 : : *
28 : : * Local lock references are special, they are not stored in the array or
29 : : * the hash table. Instead, each resource owner has a separate small cache
30 : : * of locks it owns. The lock manager has the same information in its local
31 : : * lock hash table, and we fall back on that if the cache overflows, but
32 : : * traversing the hash table is slower when there are a lot of locks
33 : : * belonging to other resource owners. This is to speed up bulk releasing
34 : : * or reassigning locks from a resource owner to its parent.
35 : : *
36 : : *
37 : : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
38 : : * Portions Copyright (c) 1994, Regents of the University of California
39 : : *
40 : : *
41 : : * IDENTIFICATION
42 : : * src/backend/utils/resowner/resowner.c
43 : : *
44 : : *-------------------------------------------------------------------------
45 : : */
46 : : #include "postgres.h"
47 : :
48 : : #include "common/hashfn.h"
49 : : #include "common/int.h"
50 : : #include "storage/ipc.h"
51 : : #include "storage/predicate.h"
52 : : #include "storage/proc.h"
53 : : #include "utils/memutils.h"
54 : : #include "utils/resowner.h"
55 : :
56 : : /*
57 : : * ResourceElem represents a reference associated with a resource owner.
58 : : *
59 : : * All objects managed by this code are required to fit into a Datum,
60 : : * which is fine since they are generally pointers or integers.
61 : : */
62 : : typedef struct ResourceElem
63 : : {
64 : : Datum item;
65 : : const ResourceOwnerDesc *kind; /* NULL indicates a free hash table slot */
66 : : } ResourceElem;
67 : :
68 : : /*
69 : : * Size of the fixed-size array to hold most-recently remembered resources.
70 : : */
71 : : #define RESOWNER_ARRAY_SIZE 32
72 : :
73 : : /*
74 : : * Initially allocated size of a ResourceOwner's hash table. Must be power of
75 : : * two because we use (capacity - 1) as mask for hashing.
76 : : */
77 : : #define RESOWNER_HASH_INIT_SIZE 64
78 : :
79 : : /*
80 : : * How many items may be stored in a hash table of given capacity. When this
81 : : * number is reached, we must resize.
82 : : *
83 : : * The hash table must always have enough free space that we can copy the
84 : : * entries from the array to it, in ResourceOwnerSort. We also insist that
85 : : * the initial size is large enough that we don't hit the max size immediately
86 : : * when it's created. Aside from those limitations, 0.75 is a reasonable fill
87 : : * factor.
88 : : */
89 : : #define RESOWNER_HASH_MAX_ITEMS(capacity) \
90 : : Min(capacity - RESOWNER_ARRAY_SIZE, (capacity)/4 * 3)
91 : :
92 : : StaticAssertDecl(RESOWNER_HASH_MAX_ITEMS(RESOWNER_HASH_INIT_SIZE) >= RESOWNER_ARRAY_SIZE,
93 : : "initial hash size too small compared to array size");
94 : :
95 : : /*
96 : : * MAX_RESOWNER_LOCKS is the size of the per-resource owner locks 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 : : /*
118 : : * When ResourceOwnerRelease is called, we sort the 'hash' and 'arr' by
119 : : * the release priority. After that, no new resources can be remembered
120 : : * or forgotten in retail. We have separate flags because
121 : : * ResourceOwnerReleaseAllOfKind() temporarily sets 'releasing' without
122 : : * sorting the arrays.
123 : : */
124 : : bool releasing;
125 : : bool sorted; /* are 'hash' and 'arr' sorted by priority? */
126 : :
127 : : /*
128 : : * Number of items in the locks cache, array, and hash table respectively.
129 : : * (These are packed together to avoid padding in the struct.)
130 : : */
131 : : uint8 nlocks; /* number of owned locks */
132 : : uint8 narr; /* how many items are stored in the array */
133 : : uint32 nhash; /* how many items are stored in the hash */
134 : :
135 : : /*
136 : : * The fixed-size array for recent resources.
137 : : *
138 : : * If 'sorted' is set, the contents are sorted by release priority.
139 : : */
140 : : ResourceElem arr[RESOWNER_ARRAY_SIZE];
141 : :
142 : : /*
143 : : * The hash table. Uses open-addressing. 'nhash' is the number of items
144 : : * present; if it would exceed 'grow_at', we enlarge it and re-hash.
145 : : * 'grow_at' should be rather less than 'capacity' so that we don't waste
146 : : * too much time searching for empty slots.
147 : : *
148 : : * If 'sorted' is set, the contents are no longer hashed, but sorted by
149 : : * release priority. The first 'nhash' elements are occupied, the rest
150 : : * are empty.
151 : : */
152 : : ResourceElem *hash;
153 : : uint32 capacity; /* allocated length of hash[] */
154 : : uint32 grow_at; /* grow hash when reach this */
155 : :
156 : : /* The local locks cache. */
157 : : LOCALLOCK *locks[MAX_RESOWNER_LOCKS]; /* list of owned locks */
158 : : } ResourceOwnerData;
159 : :
160 : :
161 : : /*****************************************************************************
162 : : * GLOBAL MEMORY *
163 : : *****************************************************************************/
164 : :
165 : : ResourceOwner CurrentResourceOwner = NULL;
166 : : ResourceOwner CurTransactionResourceOwner = NULL;
167 : : ResourceOwner TopTransactionResourceOwner = NULL;
168 : : ResourceOwner AuxProcessResourceOwner = NULL;
169 : :
170 : : /* #define RESOWNER_STATS */
171 : :
172 : : #ifdef RESOWNER_STATS
173 : : static int narray_lookups = 0;
174 : : static int nhash_lookups = 0;
175 : : #endif
176 : :
177 : : /*
178 : : * List of add-on callbacks for resource releasing
179 : : */
180 : : typedef struct ResourceReleaseCallbackItem
181 : : {
182 : : struct ResourceReleaseCallbackItem *next;
183 : : ResourceReleaseCallback callback;
184 : : void *arg;
185 : : } ResourceReleaseCallbackItem;
186 : :
187 : : static ResourceReleaseCallbackItem *ResourceRelease_callbacks = NULL;
188 : :
189 : :
190 : : /* Internal routines */
191 : : static inline uint32 hash_resource_elem(Datum value, const ResourceOwnerDesc *kind);
192 : : static void ResourceOwnerAddToHash(ResourceOwner owner, Datum value,
193 : : const ResourceOwnerDesc *kind);
194 : : static int resource_priority_cmp(const void *a, const void *b);
195 : : static void ResourceOwnerSort(ResourceOwner owner);
196 : : static void ResourceOwnerReleaseAll(ResourceOwner owner,
197 : : ResourceReleasePhase phase,
198 : : bool printLeakWarnings);
199 : : static void ResourceOwnerReleaseInternal(ResourceOwner owner,
200 : : ResourceReleasePhase phase,
201 : : bool isCommit,
202 : : bool isTopLevel);
203 : : static void ReleaseAuxProcessResourcesCallback(int code, Datum arg);
204 : :
205 : :
206 : : /*****************************************************************************
207 : : * INTERNAL ROUTINES *
208 : : *****************************************************************************/
209 : :
210 : : /*
211 : : * Hash function for value+kind combination.
212 : : */
213 : : static inline uint32
158 heikki.linnakangas@i 214 :GNC 1647926 : hash_resource_elem(Datum value, const ResourceOwnerDesc *kind)
215 : : {
216 : : /*
217 : : * Most resource kinds store a pointer in 'value', and pointers are unique
218 : : * all on their own. But some resources store plain integers (Files and
219 : : * Buffers as of this writing), so we want to incorporate the 'kind' in
220 : : * the hash too, otherwise those resources will collide a lot. But
221 : : * because there are only a few resource kinds like that - and only a few
222 : : * resource kinds to begin with - we don't need to work too hard to mix
223 : : * 'kind' into the hash. Just add it with hash_combine(), it perturbs the
224 : : * result enough for our purposes.
225 : : */
226 : : #if SIZEOF_DATUM == 8
227 : 1647926 : return hash_combine64(murmurhash64((uint64) value), (uint64) kind);
228 : : #else
229 : : return hash_combine(murmurhash32((uint32) value), (uint32) kind);
230 : : #endif
3001 tgl@sss.pgh.pa.us 231 :ECB (13399724) : }
232 : :
233 : : /*
234 : : * Adds 'value' of given 'kind' to the ResourceOwner's hash table
235 : : */
236 : : static void
158 heikki.linnakangas@i 237 :GNC 1300960 : ResourceOwnerAddToHash(ResourceOwner owner, Datum value, const ResourceOwnerDesc *kind)
238 : : {
239 : 1300960 : uint32 mask = owner->capacity - 1;
240 : : uint32 idx;
241 : :
242 [ - + ]: 1300960 : Assert(kind != NULL);
243 : :
244 : : /* Insert into first free slot at or after hash location. */
245 : 1300960 : idx = hash_resource_elem(value, kind) & mask;
246 : : for (;;)
247 : : {
248 [ + + ]: 181030256 : if (owner->hash[idx].kind == NULL)
249 : 1300960 : break; /* found a free slot */
250 : 179729296 : idx = (idx + 1) & mask;
251 : : }
252 : 1300960 : owner->hash[idx].item = value;
253 : 1300960 : owner->hash[idx].kind = kind;
254 : 1300960 : owner->nhash++;
3001 tgl@sss.pgh.pa.us 255 :CBC 1300960 : }
256 : :
257 : : /*
258 : : * Comparison function to sort by release phase and priority
259 : : */
260 : : static int
158 heikki.linnakangas@i 261 :GNC 544579 : resource_priority_cmp(const void *a, const void *b)
262 : : {
263 : 544579 : const ResourceElem *ra = (const ResourceElem *) a;
264 : 544579 : const ResourceElem *rb = (const ResourceElem *) b;
265 : :
266 : : /* Note: reverse order */
267 [ + + ]: 544579 : if (ra->kind->release_phase == rb->kind->release_phase)
58 nathan@postgresql.or 268 : 425432 : return pg_cmp_u32(rb->kind->release_priority, ra->kind->release_priority);
158 heikki.linnakangas@i 269 [ + + ]: 119147 : else if (ra->kind->release_phase > rb->kind->release_phase)
270 : 9235 : return -1;
271 : : else
272 : 109912 : return 1;
273 : : }
274 : :
275 : : /*
276 : : * Sort resources in reverse release priority.
277 : : *
278 : : * If the hash table is in use, all the elements from the fixed-size array are
279 : : * moved to the hash table, and then the hash table is sorted. If there is no
280 : : * hash table, then the fixed-size array is sorted directly. In either case,
281 : : * the result is one sorted array that contains all the resources.
282 : : */
283 : : static void
284 : 778534 : ResourceOwnerSort(ResourceOwner owner)
285 : : {
286 : : ResourceElem *items;
287 : : uint32 nitems;
288 : :
289 [ + + ]: 778534 : if (owner->nhash == 0)
290 : : {
291 : 778453 : items = owner->arr;
292 : 778453 : nitems = owner->narr;
293 : : }
294 : : else
295 : : {
296 : : /*
297 : : * Compact the hash table, so that all the elements are in the
298 : : * beginning of the 'hash' array, with no empty elements.
299 : : */
300 : 81 : uint32 dst = 0;
301 : :
302 [ + + ]: 541585 : for (int idx = 0; idx < owner->capacity; idx++)
303 : : {
304 [ + + ]: 541504 : if (owner->hash[idx].kind != NULL)
305 : : {
306 [ + + ]: 209258 : if (dst != idx)
307 : 209179 : owner->hash[dst] = owner->hash[idx];
308 : 209258 : dst++;
309 : : }
310 : : }
311 : :
312 : : /*
313 : : * Move all entries from the fixed-size array to 'hash'.
314 : : *
315 : : * RESOWNER_HASH_MAX_ITEMS is defined so that there is always enough
316 : : * free space to move all the elements from the fixed-size array to
317 : : * the hash.
318 : : */
319 [ - + ]: 81 : Assert(dst + owner->narr <= owner->capacity);
320 [ + + ]: 452 : for (int idx = 0; idx < owner->narr; idx++)
321 : : {
322 : 371 : owner->hash[dst] = owner->arr[idx];
323 : 371 : dst++;
324 : : }
325 [ - + ]: 81 : Assert(dst == owner->nhash + owner->narr);
326 : 81 : owner->narr = 0;
327 : 81 : owner->nhash = dst;
328 : :
329 : 81 : items = owner->hash;
330 : 81 : nitems = owner->nhash;
331 : : }
332 : :
333 : 778534 : qsort(items, nitems, sizeof(ResourceElem), resource_priority_cmp);
3001 tgl@sss.pgh.pa.us 334 : 778534 : }
335 : :
336 : : /*
337 : : * Call the ReleaseResource callback on entries with given 'phase'.
338 : : */
339 : : static void
158 heikki.linnakangas@i 340 : 1557070 : ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase,
341 : : bool printLeakWarnings)
342 : : {
343 : : ResourceElem *items;
344 : : uint32 nitems;
345 : :
346 : : /*
347 : : * ResourceOwnerSort must've been called already. All the resources are
348 : : * either in the array or the hash.
349 : : */
350 [ - + ]: 1557070 : Assert(owner->releasing);
351 [ - + ]: 1557070 : Assert(owner->sorted);
157 352 [ + + ]: 1557070 : if (owner->nhash == 0)
353 : : {
158 354 : 1556908 : items = owner->arr;
355 : 1556908 : nitems = owner->narr;
356 : : }
357 : : else
358 : : {
359 [ - + ]: 162 : Assert(owner->narr == 0);
360 : 162 : items = owner->hash;
361 : 162 : nitems = owner->nhash;
362 : : }
363 : :
364 : : /*
365 : : * The resources are sorted in reverse priority order. Release them
366 : : * starting from the end, until we hit the end of the phase that we are
367 : : * releasing now. We will continue from there when called again for the
368 : : * next phase.
369 : : */
370 [ + + ]: 1821895 : while (nitems > 0)
371 : : {
372 : 279991 : uint32 idx = nitems - 1;
373 : 279991 : Datum value = items[idx].item;
374 : 279991 : const ResourceOwnerDesc *kind = items[idx].kind;
375 : :
376 [ + + ]: 279991 : if (kind->release_phase > phase)
377 : 15166 : break;
378 [ - + ]: 264825 : Assert(kind->release_phase == phase);
379 : :
380 [ + + ]: 264825 : if (printLeakWarnings)
381 : : {
382 : : char *res_str;
383 : :
384 : 2 : res_str = kind->DebugPrint ?
385 : 1 : kind->DebugPrint(value)
386 [ + - ]: 1 : : psprintf("%s %p", kind->name, DatumGetPointer(value));
387 [ + - ]: 1 : elog(WARNING, "resource was not closed: %s", res_str);
388 : 1 : pfree(res_str);
389 : : }
390 : 264825 : kind->ReleaseResource(value);
391 : 264825 : nitems--;
392 : : }
157 393 [ + + ]: 1557070 : if (owner->nhash == 0)
158 394 : 1556908 : owner->narr = nitems;
395 : : else
396 : 162 : owner->nhash = nitems;
3001 tgl@sss.pgh.pa.us 397 :CBC 1557070 : }
398 : :
399 : :
400 : : /*****************************************************************************
401 : : * EXPORTED ROUTINES *
402 : : *****************************************************************************/
403 : :
404 : :
405 : : /*
406 : : * ResourceOwnerCreate
407 : : * Create an empty ResourceOwner.
408 : : *
409 : : * All ResourceOwner objects are kept in TopMemoryContext, since they should
410 : : * only be freed explicitly.
411 : : */
412 : : ResourceOwner
7211 413 : 779928 : ResourceOwnerCreate(ResourceOwner parent, const char *name)
414 : : {
415 : : ResourceOwner owner;
416 : :
417 : 779928 : owner = (ResourceOwner) MemoryContextAllocZero(TopMemoryContext,
418 : : sizeof(ResourceOwnerData));
419 : 779928 : owner->name = name;
420 : :
421 [ + + ]: 779928 : if (parent)
422 : : {
423 : 342206 : owner->parent = parent;
424 : 342206 : owner->nextchild = parent->firstchild;
425 : 342206 : parent->firstchild = owner;
426 : : }
427 : :
428 : 779928 : return owner;
429 : : }
430 : :
431 : : /*
432 : : * Make sure there is room for at least one more resource in an array.
433 : : *
434 : : * This is separate from actually inserting a resource because if we run out
435 : : * of memory, it's critical to do so *before* acquiring the resource.
436 : : *
437 : : * NB: Make sure there are no unrelated ResourceOwnerRemember() calls between
438 : : * your ResourceOwnerEnlarge() call and the ResourceOwnerRemember() call that
439 : : * you reserved the space for!
440 : : */
441 : : void
158 heikki.linnakangas@i 442 :GNC 154777624 : ResourceOwnerEnlarge(ResourceOwner owner)
443 : : {
444 : : /*
445 : : * Mustn't try to remember more resources after we have already started
446 : : * releasing
447 : : */
448 [ + + ]: 154777624 : if (owner->releasing)
449 [ + - ]: 1 : elog(ERROR, "ResourceOwnerEnlarge called after release started");
450 : :
451 [ + + ]: 154777623 : if (owner->narr < RESOWNER_ARRAY_SIZE)
452 : 154759978 : return; /* no work needed */
453 : :
454 : : /*
455 : : * Is there space in the hash? If not, enlarge it.
456 : : */
457 [ + + ]: 17645 : if (owner->narr + owner->nhash >= owner->grow_at)
458 : : {
459 : : uint32 i,
460 : : oldcap,
461 : : newcap;
462 : : ResourceElem *oldhash;
463 : : ResourceElem *newhash;
464 : :
465 : 5016 : oldhash = owner->hash;
466 : 5016 : oldcap = owner->capacity;
467 : :
468 : : /* Double the capacity (it must stay a power of 2!) */
469 [ + + ]: 5016 : newcap = (oldcap > 0) ? oldcap * 2 : RESOWNER_HASH_INIT_SIZE;
470 : 5016 : newhash = (ResourceElem *) MemoryContextAllocZero(TopMemoryContext,
471 : : newcap * sizeof(ResourceElem));
472 : :
473 : : /*
474 : : * We assume we can't fail below this point, so OK to scribble on the
475 : : * owner
476 : : */
477 : 5016 : owner->hash = newhash;
478 : 5016 : owner->capacity = newcap;
479 : 5016 : owner->grow_at = RESOWNER_HASH_MAX_ITEMS(newcap);
480 : 5016 : owner->nhash = 0;
481 : :
482 [ + + ]: 5016 : if (oldhash != NULL)
483 : : {
484 : : /*
485 : : * Transfer any pre-existing entries into the new hash table; they
486 : : * don't necessarily go where they were before, so this simple
487 : : * logic is the best way.
488 : : */
489 [ + + ]: 1048488 : for (i = 0; i < oldcap; i++)
490 : : {
491 [ + + ]: 1046848 : if (oldhash[i].kind != NULL)
492 : 736320 : ResourceOwnerAddToHash(owner, oldhash[i].item, oldhash[i].kind);
493 : : }
494 : :
495 : : /* And release old hash table. */
496 : 1640 : pfree(oldhash);
497 : : }
498 : : }
499 : :
500 : : /* Move items from the array to the hash */
501 [ + + ]: 582285 : for (int i = 0; i < owner->narr; i++)
502 : 564640 : ResourceOwnerAddToHash(owner, owner->arr[i].item, owner->arr[i].kind);
503 : 17645 : owner->narr = 0;
504 : :
505 [ - + ]: 17645 : Assert(owner->nhash <= owner->grow_at);
506 : : }
507 : :
508 : : /*
509 : : * Remember that an object is owned by a ResourceOwner
510 : : *
511 : : * Caller must have previously done ResourceOwnerEnlarge()
512 : : */
513 : : void
514 : 151315059 : ResourceOwnerRemember(ResourceOwner owner, Datum value, const ResourceOwnerDesc *kind)
515 : : {
516 : : uint32 idx;
517 : :
518 : : /* sanity check the ResourceOwnerDesc */
519 [ - + ]: 151315059 : Assert(kind->release_phase != 0);
520 [ - + ]: 151315059 : Assert(kind->release_priority != 0);
521 : :
522 : : /*
523 : : * Mustn't try to remember more resources after we have already started
524 : : * releasing. We already checked this in ResourceOwnerEnlarge.
525 : : */
526 [ - + ]: 151315059 : Assert(!owner->releasing);
527 [ - + ]: 151315059 : Assert(!owner->sorted);
528 : :
529 [ - + ]: 151315059 : if (owner->narr >= RESOWNER_ARRAY_SIZE)
530 : : {
531 : : /* forgot to call ResourceOwnerEnlarge? */
158 heikki.linnakangas@i 532 [ # # ]:UNC 0 : elog(ERROR, "ResourceOwnerRemember called but array was full");
533 : : }
534 : :
535 : : /* Append to the array. */
158 heikki.linnakangas@i 536 :GNC 151315059 : idx = owner->narr;
537 : 151315059 : owner->arr[idx].item = value;
538 : 151315059 : owner->arr[idx].kind = kind;
539 : 151315059 : owner->narr++;
540 : 151315059 : }
541 : :
542 : : /*
543 : : * Forget that an object is owned by a ResourceOwner
544 : : *
545 : : * Note: If same resource ID is associated with the ResourceOwner more than
546 : : * once, one instance is removed.
547 : : *
548 : : * Note: Forgetting a resource does not guarantee that there is room to
549 : : * remember a new resource. One exception is when you forget the most
550 : : * recently remembered resource; that does make room for a new remember call.
551 : : * Some code callers rely on that exception.
552 : : */
553 : : void
554 : 151014599 : ResourceOwnerForget(ResourceOwner owner, Datum value, const ResourceOwnerDesc *kind)
555 : : {
556 : : /*
557 : : * Mustn't call this after we have already started releasing resources.
558 : : * (Release callback functions are not allowed to release additional
559 : : * resources.)
560 : : */
561 [ + + ]: 151014599 : if (owner->releasing)
562 [ + - ]: 1 : elog(ERROR, "ResourceOwnerForget called for %s after release started", kind->name);
563 [ - + ]: 151014598 : Assert(!owner->sorted);
564 : :
565 : : /* Search through all items in the array first. */
566 [ + + ]: 196446613 : for (int i = owner->narr - 1; i >= 0; i--)
567 : : {
568 [ + + ]: 196099647 : if (owner->arr[i].item == value &&
569 [ + + ]: 150668035 : owner->arr[i].kind == kind)
570 : : {
571 : 150667632 : owner->arr[i] = owner->arr[owner->narr - 1];
572 : 150667632 : owner->narr--;
573 : :
574 : : #ifdef RESOWNER_STATS
575 : : narray_lookups++;
576 : : #endif
577 : 150667632 : return;
578 : : }
579 : : }
580 : :
581 : : /* Search hash */
582 [ + - ]: 346966 : if (owner->nhash > 0)
583 : : {
584 : 346966 : uint32 mask = owner->capacity - 1;
585 : : uint32 idx;
586 : :
587 : 346966 : idx = hash_resource_elem(value, kind) & mask;
588 [ + - ]: 98352106 : for (uint32 i = 0; i < owner->capacity; i++)
589 : : {
590 [ + + ]: 98352106 : if (owner->hash[idx].item == value &&
591 [ + + ]: 349052 : owner->hash[idx].kind == kind)
592 : : {
593 : 346966 : owner->hash[idx].item = (Datum) 0;
594 : 346966 : owner->hash[idx].kind = NULL;
595 : 346966 : owner->nhash--;
596 : :
597 : : #ifdef RESOWNER_STATS
598 : : nhash_lookups++;
599 : : #endif
600 : 346966 : return;
601 : : }
602 : 98005140 : idx = (idx + 1) & mask;
603 : : }
604 : : }
605 : :
606 : : /*
607 : : * Use %p to print the reference, since most objects tracked by a resource
608 : : * owner are pointers. It's a bit misleading if it's not a pointer, but
609 : : * this is a programmer error, anyway.
610 : : */
158 heikki.linnakangas@i 611 [ # # ]:UNC 0 : elog(ERROR, "%s %p is not owned by resource owner %s",
612 : : kind->name, DatumGetPointer(value), owner->name);
613 : : }
614 : :
615 : : /*
616 : : * ResourceOwnerRelease
617 : : * Release all resources owned by a ResourceOwner and its descendants,
618 : : * but don't delete the owner objects themselves.
619 : : *
620 : : * Note that this executes just one phase of release, and so typically
621 : : * must be called three times. We do it this way because (a) we want to
622 : : * do all the recursion separately for each phase, thereby preserving
623 : : * the needed order of operations; and (b) xact.c may have other operations
624 : : * to do between the phases.
625 : : *
626 : : * phase: release phase to execute
627 : : * isCommit: true for successful completion of a query or transaction,
628 : : * false for unsuccessful
629 : : * isTopLevel: true if completing a main transaction, else false
630 : : *
631 : : * isCommit is passed because some modules may expect that their resources
632 : : * were all released already if the transaction or portal finished normally.
633 : : * If so it is reasonable to give a warning (NOT an error) should any
634 : : * unreleased resources be present. When isCommit is false, such warnings
635 : : * are generally inappropriate.
636 : : *
637 : : * isTopLevel is passed when we are releasing TopTransactionResourceOwner
638 : : * at completion of a main transaction. This generally means that *all*
639 : : * resources will be released, and so we can optimize things a bit.
640 : : *
641 : : * NOTE: After starting the release process, by calling this function, no new
642 : : * resources can be remembered in the resource owner. You also cannot call
643 : : * ResourceOwnerForget on any previously remembered resources to release
644 : : * resources "in retail" after that, you must let the bulk release take care
645 : : * of them.
646 : : */
647 : : void
7211 tgl@sss.pgh.pa.us 648 :CBC 2245853 : ResourceOwnerRelease(ResourceOwner owner,
649 : : ResourceReleasePhase phase,
650 : : bool isCommit,
651 : : bool isTopLevel)
652 : : {
653 : : /* There's not currently any setup needed before recursing */
2377 654 : 2245853 : ResourceOwnerReleaseInternal(owner, phase, isCommit, isTopLevel);
655 : :
656 : : #ifdef RESOWNER_STATS
657 : : if (isTopLevel)
658 : : {
659 : : elog(LOG, "RESOWNER STATS: lookups: array %d, hash %d",
660 : : narray_lookups, nhash_lookups);
661 : : narray_lookups = 0;
662 : : nhash_lookups = 0;
663 : : }
664 : : #endif
7197 665 : 2245853 : }
666 : :
667 : : static void
668 : 2335604 : ResourceOwnerReleaseInternal(ResourceOwner owner,
669 : : ResourceReleasePhase phase,
670 : : bool isCommit,
671 : : bool isTopLevel)
672 : : {
673 : : ResourceOwner child;
674 : : ResourceOwner save;
675 : : ResourceReleaseCallbackItem *item;
676 : : ResourceReleaseCallbackItem *next;
677 : :
678 : : /* Recurse to handle descendants */
7211 679 [ + + ]: 2425355 : for (child = owner->firstchild; child != NULL; child = child->nextchild)
7197 680 : 89751 : ResourceOwnerReleaseInternal(child, phase, isCommit, isTopLevel);
681 : :
682 : : /*
683 : : * To release the resources in the right order, sort them by phase and
684 : : * priority.
685 : : *
686 : : * The ReleaseResource callback functions are not allowed to remember or
687 : : * forget any other resources after this. Otherwise we lose track of where
688 : : * we are in processing the hash/array.
689 : : */
158 heikki.linnakangas@i 690 [ + + ]:GNC 2335604 : if (!owner->releasing)
691 : : {
692 [ - + ]: 778534 : Assert(phase == RESOURCE_RELEASE_BEFORE_LOCKS);
693 [ - + ]: 778534 : Assert(!owner->sorted);
694 : 778534 : owner->releasing = true;
695 : : }
696 : : else
697 : : {
698 : : /*
699 : : * Phase is normally > RESOURCE_RELEASE_BEFORE_LOCKS, if this is not
700 : : * the first call to ResourceOwnerRelease. But if an error happens
701 : : * between the release phases, we might get called again for the same
702 : : * ResourceOwner from AbortTransaction.
703 : : */
704 : : }
705 [ + + ]: 2335604 : if (!owner->sorted)
706 : : {
707 : 778534 : ResourceOwnerSort(owner);
708 : 778534 : owner->sorted = true;
709 : : }
710 : :
711 : : /*
712 : : * Make CurrentResourceOwner point to me, so that the release callback
713 : : * functions know which resource owner is been released.
714 : : */
158 heikki.linnakangas@i 715 :CBC 2335604 : save = CurrentResourceOwner;
716 : 2335604 : CurrentResourceOwner = owner;
717 : :
718 [ + + ]: 2335604 : if (phase == RESOURCE_RELEASE_BEFORE_LOCKS)
719 : : {
720 : : /*
721 : : * Release all resources that need to be released before the locks.
722 : : *
723 : : * During a commit, there shouldn't be any remaining resources ---
724 : : * that would indicate failure to clean up the executor correctly ---
725 : : * so issue warnings. In the abort case, just clean up quietly.
726 : : */
158 heikki.linnakangas@i 727 :GNC 778536 : ResourceOwnerReleaseAll(owner, phase, isCommit);
728 : : }
7211 tgl@sss.pgh.pa.us 729 [ + + ]:CBC 1557068 : else if (phase == RESOURCE_RELEASE_LOCKS)
730 : : {
731 [ + + ]: 778534 : if (isTopLevel)
732 : : {
733 : : /*
734 : : * For a top-level xact we are going to release all locks (or at
735 : : * least all non-session locks), so just do a single lmgr call at
736 : : * the top of the recursion.
737 : : */
738 [ + + ]: 466122 : if (owner == TopTransactionResourceOwner)
739 : : {
740 : 433752 : ProcReleaseLocks(isCommit);
1857 tmunro@postgresql.or 741 : 433752 : ReleasePredicateLocks(isCommit, false);
742 : : }
743 : : }
744 : : else
745 : : {
746 : : /*
747 : : * Release locks retail. Note that if we are committing a
748 : : * subtransaction, we do NOT release its locks yet, but transfer
749 : : * them to the parent.
750 : : */
751 : : LOCALLOCK **locks;
752 : : int nlocks;
753 : :
7172 tgl@sss.pgh.pa.us 754 [ - + ]: 312412 : Assert(owner->parent != NULL);
755 : :
756 : : /*
757 : : * Pass the list of locks owned by this resource owner to the lock
758 : : * manager, unless it has overflowed.
759 : : */
4315 heikki.linnakangas@i 760 [ + + ]: 312412 : if (owner->nlocks > MAX_RESOWNER_LOCKS)
761 : : {
762 : 3082 : locks = NULL;
763 : 3082 : nlocks = 0;
764 : : }
765 : : else
766 : : {
767 : 309330 : locks = owner->locks;
768 : 309330 : nlocks = owner->nlocks;
769 : : }
770 : :
7170 tgl@sss.pgh.pa.us 771 [ + + ]: 312412 : if (isCommit)
4315 heikki.linnakangas@i 772 : 307705 : LockReassignCurrentOwner(locks, nlocks);
773 : : else
774 : 4707 : LockReleaseCurrentOwner(locks, nlocks);
775 : : }
776 : : }
7211 tgl@sss.pgh.pa.us 777 [ + - ]: 778534 : else if (phase == RESOURCE_RELEASE_AFTER_LOCKS)
778 : : {
779 : : /*
780 : : * Release all resources that need to be released after the locks.
781 : : */
158 heikki.linnakangas@i 782 :GNC 778534 : ResourceOwnerReleaseAll(owner, phase, isCommit);
783 : : }
784 : :
785 : : /* Let add-on modules get a chance too */
158 heikki.linnakangas@i 786 [ - + ]:CBC 2335604 : for (item = ResourceRelease_callbacks; item; item = next)
787 : : {
788 : : /* allow callbacks to unregister themselves when called */
158 heikki.linnakangas@i 789 :LBC (1638) : next = item->next;
790 : (1638) : item->callback(phase, isCommit, isTopLevel, item->arg);
791 : : }
792 : :
158 heikki.linnakangas@i 793 :CBC 2335604 : CurrentResourceOwner = save;
794 : 2335604 : }
795 : :
796 : : /*
797 : : * ResourceOwnerReleaseAllOfKind
798 : : * Release all resources of a certain type held by this owner.
799 : : */
800 : : void
158 heikki.linnakangas@i 801 :GNC 7998 : ResourceOwnerReleaseAllOfKind(ResourceOwner owner, const ResourceOwnerDesc *kind)
802 : : {
803 : : /* Mustn't call this after we have already started releasing resources. */
804 [ - + ]: 7998 : if (owner->releasing)
158 heikki.linnakangas@i 805 [ # # ]:UNC 0 : elog(ERROR, "ResourceOwnerForget called for %s after release started", kind->name);
158 heikki.linnakangas@i 806 [ - + ]:GNC 7998 : Assert(!owner->sorted);
807 : :
808 : : /*
809 : : * Temporarily set 'releasing', to prevent calls to ResourceOwnerRemember
810 : : * while we're scanning the owner. Enlarging the hash would cause us to
811 : : * lose track of the point we're scanning.
812 : : */
813 : 7998 : owner->releasing = true;
814 : :
815 : : /* Array first */
816 [ + + ]: 35190 : for (int i = 0; i < owner->narr; i++)
817 : : {
818 [ + - ]: 27192 : if (owner->arr[i].kind == kind)
819 : : {
820 : 27192 : Datum value = owner->arr[i].item;
821 : :
822 : 27192 : owner->arr[i] = owner->arr[owner->narr - 1];
823 : 27192 : owner->narr--;
824 : 27192 : i--;
825 : :
826 : 27192 : kind->ReleaseResource(value);
827 : : }
828 : : }
829 : :
830 : : /* Then hash */
831 [ + + ]: 24830 : for (int i = 0; i < owner->capacity; i++)
832 : : {
833 [ + + ]: 16832 : if (owner->hash[i].kind == kind)
834 : : {
835 : 8416 : Datum value = owner->hash[i].item;
836 : :
837 : 8416 : owner->hash[i].item = (Datum) 0;
838 : 8416 : owner->hash[i].kind = NULL;
839 : 8416 : owner->nhash--;
840 : :
841 : 8416 : kind->ReleaseResource(value);
842 : : }
843 : : }
844 : 7998 : owner->releasing = false;
1480 tgl@sss.pgh.pa.us 845 :CBC 7998 : }
846 : :
847 : : /*
848 : : * ResourceOwnerDelete
849 : : * Delete an owner object and its descendants.
850 : : *
851 : : * The caller must have already released all resources in the object tree.
852 : : */
853 : : void
7211 854 : 776598 : ResourceOwnerDelete(ResourceOwner owner)
855 : : {
856 : : /* We had better not be deleting CurrentResourceOwner ... */
857 [ - + ]: 776598 : Assert(owner != CurrentResourceOwner);
858 : :
859 : : /* And it better not own any resources, either */
158 heikki.linnakangas@i 860 [ - + ]:GNC 776598 : Assert(owner->narr == 0);
861 [ - + ]: 776598 : Assert(owner->nhash == 0);
4315 heikki.linnakangas@i 862 [ + + - + ]:CBC 776598 : Assert(owner->nlocks == 0 || owner->nlocks == MAX_RESOWNER_LOCKS + 1);
863 : :
864 : : /*
865 : : * Delete children. The recursive call will delink the child from me, so
866 : : * just iterate as long as there is a child.
867 : : */
7211 tgl@sss.pgh.pa.us 868 [ + + ]: 806518 : while (owner->firstchild != NULL)
869 : 29920 : ResourceOwnerDelete(owner->firstchild);
870 : :
871 : : /*
872 : : * We delink the owner from its parent before deleting it, so that if
873 : : * there's an error we won't have deleted/busted owners still attached to
874 : : * the owner tree. Better a leak than a crash.
875 : : */
876 : 776598 : ResourceOwnerNewParent(owner, NULL);
877 : :
878 : : /* And free the object. */
158 heikki.linnakangas@i 879 [ + + ]:GNC 776598 : if (owner->hash)
880 : 3376 : pfree(owner->hash);
7211 tgl@sss.pgh.pa.us 881 :CBC 776598 : pfree(owner);
882 : 776598 : }
883 : :
884 : : /*
885 : : * Fetch parent of a ResourceOwner (returns NULL if top-level owner)
886 : : */
887 : : ResourceOwner
7170 888 : 307705 : ResourceOwnerGetParent(ResourceOwner owner)
889 : : {
890 : 307705 : return owner->parent;
891 : : }
892 : :
893 : : /*
894 : : * Reassign a ResourceOwner to have a new parent
895 : : */
896 : : void
7211 897 : 776635 : ResourceOwnerNewParent(ResourceOwner owner,
898 : : ResourceOwner newparent)
899 : : {
900 : 776635 : ResourceOwner oldparent = owner->parent;
901 : :
902 [ + + ]: 776635 : if (oldparent)
903 : : {
904 [ + + ]: 342240 : if (owner == oldparent->firstchild)
905 : 334598 : oldparent->firstchild = owner->nextchild;
906 : : else
907 : : {
908 : : ResourceOwner child;
909 : :
910 [ + - ]: 8558 : for (child = oldparent->firstchild; child; child = child->nextchild)
911 : : {
912 [ + + ]: 8558 : if (owner == child->nextchild)
913 : : {
914 : 7642 : child->nextchild = owner->nextchild;
915 : 7642 : break;
916 : : }
917 : : }
918 : : }
919 : : }
920 : :
921 [ + + ]: 776635 : if (newparent)
922 : : {
923 [ - + ]: 37 : Assert(owner != newparent);
924 : 37 : owner->parent = newparent;
925 : 37 : owner->nextchild = newparent->firstchild;
926 : 37 : newparent->firstchild = owner;
927 : : }
928 : : else
929 : : {
930 : 776598 : owner->parent = NULL;
931 : 776598 : owner->nextchild = NULL;
932 : : }
933 : 776635 : }
934 : :
935 : : /*
936 : : * Register or deregister callback functions for resource cleanup
937 : : *
938 : : * These functions can be used by dynamically loaded modules. These used
939 : : * to be the only way for an extension to register custom resource types
940 : : * with a resource owner, but nowadays it is easier to define a new
941 : : * ResourceOwnerDesc with custom callbacks.
942 : : */
943 : : void
7211 tgl@sss.pgh.pa.us 944 :LBC (23) : RegisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg)
945 : : {
946 : : ResourceReleaseCallbackItem *item;
947 : :
948 : : item = (ResourceReleaseCallbackItem *)
949 : (23) : MemoryContextAlloc(TopMemoryContext,
950 : : sizeof(ResourceReleaseCallbackItem));
951 : (23) : item->callback = callback;
952 : (23) : item->arg = arg;
953 : (23) : item->next = ResourceRelease_callbacks;
954 : (23) : ResourceRelease_callbacks = item;
955 : (23) : }
956 : :
957 : : void
7211 tgl@sss.pgh.pa.us 958 :UBC 0 : UnregisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg)
959 : : {
960 : : ResourceReleaseCallbackItem *item;
961 : : ResourceReleaseCallbackItem *prev;
962 : :
963 : 0 : prev = NULL;
964 [ # # ]: 0 : for (item = ResourceRelease_callbacks; item; prev = item, item = item->next)
965 : : {
966 [ # # # # ]: 0 : if (item->callback == callback && item->arg == arg)
967 : : {
968 [ # # ]: 0 : if (prev)
969 : 0 : prev->next = item->next;
970 : : else
971 : 0 : ResourceRelease_callbacks = item->next;
972 : 0 : pfree(item);
973 : 0 : break;
974 : : }
975 : : }
976 : 0 : }
977 : :
978 : : /*
979 : : * Establish an AuxProcessResourceOwner for the current process.
980 : : */
981 : : void
2097 tgl@sss.pgh.pa.us 982 :CBC 3312 : CreateAuxProcessResourceOwner(void)
983 : : {
984 [ - + ]: 3312 : Assert(AuxProcessResourceOwner == NULL);
985 [ - + ]: 3312 : Assert(CurrentResourceOwner == NULL);
986 : 3312 : AuxProcessResourceOwner = ResourceOwnerCreate(NULL, "AuxiliaryProcess");
987 : 3312 : CurrentResourceOwner = AuxProcessResourceOwner;
988 : :
989 : : /*
990 : : * Register a shmem-exit callback for cleanup of aux-process resource
991 : : * owner. (This needs to run after, e.g., ShutdownXLOG.)
992 : : */
993 : 3312 : on_shmem_exit(ReleaseAuxProcessResourcesCallback, 0);
994 : 3312 : }
995 : :
996 : : /*
997 : : * Convenience routine to release all resources tracked in
998 : : * AuxProcessResourceOwner (but that resowner is not destroyed here).
999 : : * Warn about leaked resources if isCommit is true.
1000 : : */
1001 : : void
1002 : 2427 : ReleaseAuxProcessResources(bool isCommit)
1003 : : {
1004 : : /*
1005 : : * At this writing, the only thing that could actually get released is
1006 : : * buffer pins; but we may as well do the full release protocol.
1007 : : */
1008 : 2427 : ResourceOwnerRelease(AuxProcessResourceOwner,
1009 : : RESOURCE_RELEASE_BEFORE_LOCKS,
1010 : : isCommit, true);
1011 : 2427 : ResourceOwnerRelease(AuxProcessResourceOwner,
1012 : : RESOURCE_RELEASE_LOCKS,
1013 : : isCommit, true);
1014 : 2427 : ResourceOwnerRelease(AuxProcessResourceOwner,
1015 : : RESOURCE_RELEASE_AFTER_LOCKS,
1016 : : isCommit, true);
1017 : : /* allow it to be reused */
158 heikki.linnakangas@i 1018 :GNC 2427 : AuxProcessResourceOwner->releasing = false;
1019 : 2427 : AuxProcessResourceOwner->sorted = false;
2097 tgl@sss.pgh.pa.us 1020 :CBC 2427 : }
1021 : :
1022 : : /*
1023 : : * Shmem-exit callback for the same.
1024 : : * Warn about leaked resources if process exit code is zero (ie normal).
1025 : : */
1026 : : static void
1027 : 2340 : ReleaseAuxProcessResourcesCallback(int code, Datum arg)
1028 : : {
1029 : 2340 : bool isCommit = (code == 0);
1030 : :
1031 : 2340 : ReleaseAuxProcessResources(isCommit);
1032 : 2340 : }
1033 : :
1034 : : /*
1035 : : * Remember that a Local Lock is owned by a ResourceOwner
1036 : : *
1037 : : * This is different from the generic ResourceOwnerRemember in that the list of
1038 : : * locks is only a lossy cache. It can hold up to MAX_RESOWNER_LOCKS entries,
1039 : : * and when it overflows, we stop tracking locks. The point of only remembering
1040 : : * only up to MAX_RESOWNER_LOCKS entries is that if a lot of locks are held,
1041 : : * ResourceOwnerForgetLock doesn't need to scan through a large array to find
1042 : : * the entry.
1043 : : */
1044 : : void
3973 bruce@momjian.us 1045 : 16167744 : ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK *locallock)
1046 : : {
3001 tgl@sss.pgh.pa.us 1047 [ - + ]: 16167744 : Assert(locallock != NULL);
1048 : :
4315 heikki.linnakangas@i 1049 [ + + ]: 16167744 : if (owner->nlocks > MAX_RESOWNER_LOCKS)
3973 bruce@momjian.us 1050 : 1547106 : return; /* we have already overflowed */
1051 : :
4315 heikki.linnakangas@i 1052 [ + + ]: 14620638 : if (owner->nlocks < MAX_RESOWNER_LOCKS)
1053 : 14611999 : owner->locks[owner->nlocks] = locallock;
1054 : : else
1055 : : {
1056 : : /* overflowed */
1057 : : }
1058 : 14620638 : owner->nlocks++;
1059 : : }
1060 : :
1061 : : /*
1062 : : * Forget that a Local Lock is owned by a ResourceOwner
1063 : : */
1064 : : void
1065 : 16167705 : ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock)
1066 : : {
1067 : : int i;
1068 : :
1069 [ + + ]: 16167705 : if (owner->nlocks > MAX_RESOWNER_LOCKS)
3973 bruce@momjian.us 1070 : 1685300 : return; /* we have overflowed */
1071 : :
4315 heikki.linnakangas@i 1072 [ - + ]: 14482405 : Assert(owner->nlocks > 0);
1073 [ + - ]: 16284870 : for (i = owner->nlocks - 1; i >= 0; i--)
1074 : : {
1075 [ + + ]: 16284870 : if (locallock == owner->locks[i])
1076 : : {
1077 : 14482405 : owner->locks[i] = owner->locks[owner->nlocks - 1];
1078 : 14482405 : owner->nlocks--;
1079 : 14482405 : return;
1080 : : }
1081 : : }
4315 heikki.linnakangas@i 1082 [ # # ]:UBC 0 : elog(ERROR, "lock reference %p is not owned by resource owner %s",
1083 : : locallock, owner->name);
1084 : : }
|