]> granicus.if.org Git - postgresql/blob - src/backend/utils/mmgr/portalmem.c
This patch will ensure that the hash table iteration performed by
[postgresql] / src / backend / utils / mmgr / portalmem.c
1 /*-------------------------------------------------------------------------
2  *
3  * portalmem.c
4  *        backend portal memory management
5  *
6  * Portals are objects representing the execution state of a query.
7  * This module provides memory management services for portals, but it
8  * doesn't actually run the executor for them.
9  *
10  *
11  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
12  * Portions Copyright (c) 1994, Regents of the University of California
13  *
14  * IDENTIFICATION
15  *        $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.79 2005/05/11 18:05:37 momjian Exp $
16  *
17  *-------------------------------------------------------------------------
18  */
19 #include "postgres.h"
20
21 #include "miscadmin.h"
22 #include "commands/portalcmds.h"
23 #include "executor/executor.h"
24 #include "utils/hsearch.h"
25 #include "utils/memutils.h"
26 #include "utils/portal.h"
27
28 /*
29  * Estimate of the maximum number of open portals a user would have,
30  * used in initially sizing the PortalHashTable in EnablePortalManager().
31  * Since the hash table can expand, there's no need to make this overly
32  * generous, and keeping it small avoids unnecessary overhead in the
33  * hash_seq_search() calls executed during transaction end.
34  */
35 #define PORTALS_PER_USER           16
36
37
38 /* ----------------
39  *              Global state
40  * ----------------
41  */
42
43 #define MAX_PORTALNAME_LEN              NAMEDATALEN
44
45 typedef struct portalhashent
46 {
47         char            portalname[MAX_PORTALNAME_LEN];
48         Portal          portal;
49 } PortalHashEnt;
50
51 static HTAB *PortalHashTable = NULL;
52
53 #define PortalHashTableLookup(NAME, PORTAL) \
54 do { \
55         PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \
56         \
57         MemSet(key, 0, MAX_PORTALNAME_LEN); \
58         StrNCpy(key, NAME, MAX_PORTALNAME_LEN); \
59         hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
60                                                                                  key, HASH_FIND, NULL); \
61         if (hentry) \
62                 PORTAL = hentry->portal; \
63         else \
64                 PORTAL = NULL; \
65 } while(0)
66
67 #define PortalHashTableInsert(PORTAL, NAME) \
68 do { \
69         PortalHashEnt *hentry; bool found; char key[MAX_PORTALNAME_LEN]; \
70         \
71         MemSet(key, 0, MAX_PORTALNAME_LEN); \
72         StrNCpy(key, NAME, MAX_PORTALNAME_LEN); \
73         hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
74                                                                                  key, HASH_ENTER, &found); \
75         if (hentry == NULL) \
76                 ereport(ERROR, \
77                                 (errcode(ERRCODE_OUT_OF_MEMORY), \
78                                  errmsg("out of memory"))); \
79         if (found) \
80                 elog(ERROR, "duplicate portal name"); \
81         hentry->portal = PORTAL; \
82         /* To avoid duplicate storage, make PORTAL->name point to htab entry */ \
83         PORTAL->name = hentry->portalname; \
84 } while(0)
85
86 #define PortalHashTableDelete(PORTAL) \
87 do { \
88         PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \
89         \
90         MemSet(key, 0, MAX_PORTALNAME_LEN); \
91         StrNCpy(key, PORTAL->name, MAX_PORTALNAME_LEN); \
92         hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
93                                                                                  key, HASH_REMOVE, NULL); \
94         if (hentry == NULL) \
95                 elog(WARNING, "trying to delete portal name that does not exist"); \
96 } while(0)
97
98 static MemoryContext PortalMemory = NULL;
99
100
101 /* ----------------------------------------------------------------
102  *                                 public portal interface functions
103  * ----------------------------------------------------------------
104  */
105
106 /*
107  * EnablePortalManager
108  *              Enables the portal management module at backend startup.
109  */
110 void
111 EnablePortalManager(void)
112 {
113         HASHCTL         ctl;
114
115         Assert(PortalMemory == NULL);
116
117         PortalMemory = AllocSetContextCreate(TopMemoryContext,
118                                                                                  "PortalMemory",
119                                                                                  ALLOCSET_DEFAULT_MINSIZE,
120                                                                                  ALLOCSET_DEFAULT_INITSIZE,
121                                                                                  ALLOCSET_DEFAULT_MAXSIZE);
122
123         ctl.keysize = MAX_PORTALNAME_LEN;
124         ctl.entrysize = sizeof(PortalHashEnt);
125
126         /*
127          * use PORTALS_PER_USER as a guess of how many hash table entries to
128          * create, initially
129          */
130         PortalHashTable = hash_create("Portal hash", PORTALS_PER_USER,
131                                                                   &ctl, HASH_ELEM);
132 }
133
134 /*
135  * GetPortalByName
136  *              Returns a portal given a portal name, or NULL if name not found.
137  */
138 Portal
139 GetPortalByName(const char *name)
140 {
141         Portal          portal;
142
143         if (PointerIsValid(name))
144                 PortalHashTableLookup(name, portal);
145         else
146                 portal = NULL;
147
148         return portal;
149 }
150
151 /*
152  * CreatePortal
153  *              Returns a new portal given a name.
154  *
155  * allowDup: if true, automatically drop any pre-existing portal of the
156  * same name (if false, an error is raised).
157  *
158  * dupSilent: if true, don't even emit a WARNING.
159  */
160 Portal
161 CreatePortal(const char *name, bool allowDup, bool dupSilent)
162 {
163         Portal          portal;
164
165         AssertArg(PointerIsValid(name));
166
167         portal = GetPortalByName(name);
168         if (PortalIsValid(portal))
169         {
170                 if (!allowDup)
171                         ereport(ERROR,
172                                         (errcode(ERRCODE_DUPLICATE_CURSOR),
173                                          errmsg("cursor \"%s\" already exists", name)));
174                 if (!dupSilent)
175                         ereport(WARNING,
176                                         (errcode(ERRCODE_DUPLICATE_CURSOR),
177                                          errmsg("closing existing cursor \"%s\"",
178                                                         name)));
179                 PortalDrop(portal, false);
180         }
181
182         /* make new portal structure */
183         portal = (Portal) MemoryContextAllocZero(PortalMemory, sizeof *portal);
184
185         /* initialize portal heap context; typically it won't store much */
186         portal->heap = AllocSetContextCreate(PortalMemory,
187                                                                                  "PortalHeapMemory",
188                                                                                  ALLOCSET_SMALL_MINSIZE,
189                                                                                  ALLOCSET_SMALL_INITSIZE,
190                                                                                  ALLOCSET_SMALL_MAXSIZE);
191
192         /* create a resource owner for the portal */
193         portal->resowner = ResourceOwnerCreate(CurTransactionResourceOwner,
194                                                                                    "Portal");
195
196         /* initialize portal fields that don't start off zero */
197         portal->cleanup = PortalCleanup;
198         portal->createSubid = GetCurrentSubTransactionId();
199         portal->strategy = PORTAL_MULTI_QUERY;
200         portal->cursorOptions = CURSOR_OPT_NO_SCROLL;
201         portal->atStart = true;
202         portal->atEnd = true;           /* disallow fetches until query is set */
203
204         /* put portal in table (sets portal->name) */
205         PortalHashTableInsert(portal, name);
206
207         return portal;
208 }
209
210 /*
211  * CreateNewPortal
212  *              Create a new portal, assigning it a random nonconflicting name.
213  */
214 Portal
215 CreateNewPortal(void)
216 {
217         static unsigned int unnamed_portal_count = 0;
218
219         char            portalname[MAX_PORTALNAME_LEN];
220
221         /* Select a nonconflicting name */
222         for (;;)
223         {
224                 unnamed_portal_count++;
225                 sprintf(portalname, "<unnamed portal %u>", unnamed_portal_count);
226                 if (GetPortalByName(portalname) == NULL)
227                         break;
228         }
229
230         return CreatePortal(portalname, false, false);
231 }
232
233 /*
234  * PortalDefineQuery
235  *              A simple subroutine to establish a portal's query.
236  *
237  * Notes: commandTag shall be NULL if and only if the original query string
238  * (before rewriting) was an empty string.      Also, the passed commandTag must
239  * be a pointer to a constant string, since it is not copied.  The caller is
240  * responsible for ensuring that the passed sourceText (if any), parse and
241  * plan trees have adequate lifetime.  Also, queryContext must accurately
242  * describe the location of the parse and plan trees.
243  */
244 void
245 PortalDefineQuery(Portal portal,
246                                   const char *sourceText,
247                                   const char *commandTag,
248                                   List *parseTrees,
249                                   List *planTrees,
250                                   MemoryContext queryContext)
251 {
252         AssertArg(PortalIsValid(portal));
253         AssertState(portal->queryContext == NULL);      /* else defined already */
254
255         Assert(list_length(parseTrees) == list_length(planTrees));
256
257         Assert(commandTag != NULL || parseTrees == NIL);
258
259         portal->sourceText = sourceText;
260         portal->commandTag = commandTag;
261         portal->parseTrees = parseTrees;
262         portal->planTrees = planTrees;
263         portal->queryContext = queryContext;
264 }
265
266 /*
267  * PortalCreateHoldStore
268  *              Create the tuplestore for a portal.
269  */
270 void
271 PortalCreateHoldStore(Portal portal)
272 {
273         MemoryContext oldcxt;
274
275         Assert(portal->holdContext == NULL);
276         Assert(portal->holdStore == NULL);
277
278         /*
279          * Create the memory context that is used for storage of the tuple
280          * set. Note this is NOT a child of the portal's heap memory.
281          */
282         portal->holdContext =
283                 AllocSetContextCreate(PortalMemory,
284                                                           "PortalHoldContext",
285                                                           ALLOCSET_DEFAULT_MINSIZE,
286                                                           ALLOCSET_DEFAULT_INITSIZE,
287                                                           ALLOCSET_DEFAULT_MAXSIZE);
288
289         /* Create the tuple store, selecting cross-transaction temp files. */
290         oldcxt = MemoryContextSwitchTo(portal->holdContext);
291
292         /* XXX: Should maintenance_work_mem be used for the portal size? */
293         portal->holdStore = tuplestore_begin_heap(true, true, work_mem);
294
295         MemoryContextSwitchTo(oldcxt);
296 }
297
298 /*
299  * PortalDrop
300  *              Destroy the portal.
301  */
302 void
303 PortalDrop(Portal portal, bool isTopCommit)
304 {
305         AssertArg(PortalIsValid(portal));
306
307         /* Not sure if this case can validly happen or not... */
308         if (portal->status == PORTAL_ACTIVE)
309                 elog(ERROR, "cannot drop active portal");
310
311         /*
312          * Remove portal from hash table.  Because we do this first, we will
313          * not come back to try to remove the portal again if there's any
314          * error in the subsequent steps.  Better to leak a little memory than
315          * to get into an infinite error-recovery loop.
316          */
317         PortalHashTableDelete(portal);
318
319         /* let portalcmds.c clean up the state it knows about */
320         if (PointerIsValid(portal->cleanup))
321                 (*portal->cleanup) (portal);
322
323         /*
324          * Release any resources still attached to the portal.  There are
325          * several cases being covered here:
326          *
327          * Top transaction commit (indicated by isTopCommit): normally we should
328          * do nothing here and let the regular end-of-transaction resource
329          * releasing mechanism handle these resources too.      However, if we
330          * have a FAILED portal (eg, a cursor that got an error), we'd better
331          * clean up its resources to avoid resource-leakage warning messages.
332          *
333          * Sub transaction commit: never comes here at all, since we don't kill
334          * any portals in AtSubCommit_Portals().
335          *
336          * Main or sub transaction abort: we will do nothing here because
337          * portal->resowner was already set NULL; the resources were already
338          * cleaned up in transaction abort.
339          *
340          * Ordinary portal drop: must release resources.  However, if the portal
341          * is not FAILED then we do not release its locks.      The locks become
342          * the responsibility of the transaction's ResourceOwner (since it is
343          * the parent of the portal's owner) and will be released when the
344          * transaction eventually ends.
345          */
346         if (portal->resowner &&
347                 (!isTopCommit || portal->status == PORTAL_FAILED))
348         {
349                 bool            isCommit = (portal->status != PORTAL_FAILED);
350
351                 ResourceOwnerRelease(portal->resowner,
352                                                          RESOURCE_RELEASE_BEFORE_LOCKS,
353                                                          isCommit, false);
354                 ResourceOwnerRelease(portal->resowner,
355                                                          RESOURCE_RELEASE_LOCKS,
356                                                          isCommit, false);
357                 ResourceOwnerRelease(portal->resowner,
358                                                          RESOURCE_RELEASE_AFTER_LOCKS,
359                                                          isCommit, false);
360                 ResourceOwnerDelete(portal->resowner);
361         }
362         portal->resowner = NULL;
363
364         /*
365          * Delete tuplestore if present.  We should do this even under error
366          * conditions; since the tuplestore would have been using cross-
367          * transaction storage, its temp files need to be explicitly deleted.
368          */
369         if (portal->holdStore)
370         {
371                 MemoryContext oldcontext;
372
373                 oldcontext = MemoryContextSwitchTo(portal->holdContext);
374                 tuplestore_end(portal->holdStore);
375                 MemoryContextSwitchTo(oldcontext);
376                 portal->holdStore = NULL;
377         }
378
379         /* delete tuplestore storage, if any */
380         if (portal->holdContext)
381                 MemoryContextDelete(portal->holdContext);
382
383         /* release subsidiary storage */
384         MemoryContextDelete(PortalGetHeapMemory(portal));
385
386         /* release portal struct (it's in PortalMemory) */
387         pfree(portal);
388 }
389
390 /*
391  * DropDependentPortals
392  *              Drop any portals using the specified context as queryContext.
393  *
394  * This is normally used to make sure we can safely drop a prepared statement.
395  */
396 void
397 DropDependentPortals(MemoryContext queryContext)
398 {
399         HASH_SEQ_STATUS status;
400         PortalHashEnt *hentry;
401
402         hash_seq_init(&status, PortalHashTable);
403
404         while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
405         {
406                 Portal          portal = hentry->portal;
407
408                 if (portal->queryContext == queryContext)
409                         PortalDrop(portal, false);
410         }
411 }
412
413
414 /*
415  * Pre-commit processing for portals.
416  *
417  * Any holdable cursors created in this transaction need to be converted to
418  * materialized form, since we are going to close down the executor and
419  * release locks.  Other portals are not touched yet.
420  *
421  * Returns TRUE if any holdable cursors were processed, FALSE if not.
422  */
423 bool
424 CommitHoldablePortals(void)
425 {
426         bool result = false;
427         HASH_SEQ_STATUS status;
428         PortalHashEnt *hentry;
429
430         hash_seq_init(&status, PortalHashTable);
431
432         while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
433         {
434                 Portal          portal = hentry->portal;
435
436                 /* Is it a holdable portal created in the current xact? */
437                 if ((portal->cursorOptions & CURSOR_OPT_HOLD) &&
438                         portal->createSubid != InvalidSubTransactionId &&
439                         portal->status == PORTAL_READY)
440                 {
441                         /*
442                          * We are exiting the transaction that created a holdable
443                          * cursor.      Instead of dropping the portal, prepare it for
444                          * access by later transactions.
445                          *
446                          * Note that PersistHoldablePortal() must release all resources
447                          * used by the portal that are local to the creating
448                          * transaction.
449                          */
450                         PortalCreateHoldStore(portal);
451                         PersistHoldablePortal(portal);
452
453                         /*
454                          * Any resources belonging to the portal will be released in
455                          * the upcoming transaction-wide cleanup; the portal will no
456                          * longer have its own resources.
457                          */
458                         portal->resowner = NULL;
459
460                         /*
461                          * Having successfully exported the holdable cursor, mark it
462                          * as not belonging to this transaction.
463                          */
464                         portal->createSubid = InvalidSubTransactionId;
465
466                         result = true;
467                 }
468         }
469
470         return result;
471 }
472
473 /*
474  * Pre-commit processing for portals.
475  *
476  * Remove all non-holdable portals created in this transaction.
477  * Portals remaining from prior transactions should be left untouched.
478  */
479 void
480 AtCommit_Portals(void)
481 {
482         HASH_SEQ_STATUS status;
483         PortalHashEnt *hentry;
484
485         hash_seq_init(&status, PortalHashTable);
486
487         while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
488         {
489                 Portal          portal = hentry->portal;
490
491                 /*
492                  * Do not touch active portals --- this can only happen in the
493                  * case of a multi-transaction utility command, such as VACUUM.
494                  *
495                  * Note however that any resource owner attached to such a portal is
496                  * still going to go away, so don't leave a dangling pointer.
497                  */
498                 if (portal->status == PORTAL_ACTIVE)
499                 {
500                         portal->resowner = NULL;
501                         continue;
502                 }
503
504                 /*
505                  * Do nothing to cursors held over from a previous transaction
506                  * (including holdable ones just frozen by CommitHoldablePortals).
507                  */
508                 if (portal->createSubid == InvalidSubTransactionId)
509                         continue;
510
511                 /* Zap all non-holdable portals */
512                 PortalDrop(portal, true);
513
514                 /* Restart the iteration */
515                 hash_seq_init(&status, PortalHashTable);
516         }
517 }
518
519 /*
520  * Abort processing for portals.
521  *
522  * At this point we reset "active" status and run the cleanup hook if
523  * present, but we can't release memory until the cleanup call.
524  *
525  * The reason we need to reset active is so that we can replace the unnamed
526  * portal, else we'll fail to execute ROLLBACK when it arrives.
527  */
528 void
529 AtAbort_Portals(void)
530 {
531         HASH_SEQ_STATUS status;
532         PortalHashEnt *hentry;
533
534         hash_seq_init(&status, PortalHashTable);
535
536         while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
537         {
538                 Portal          portal = hentry->portal;
539
540                 if (portal->status == PORTAL_ACTIVE)
541                         portal->status = PORTAL_FAILED;
542
543                 /*
544                  * Do nothing else to cursors held over from a previous
545                  * transaction.
546                  */
547                 if (portal->createSubid == InvalidSubTransactionId)
548                         continue;
549
550                 /* let portalcmds.c clean up the state it knows about */
551                 if (PointerIsValid(portal->cleanup))
552                 {
553                         (*portal->cleanup) (portal);
554                         portal->cleanup = NULL;
555                 }
556
557                 /*
558                  * Any resources belonging to the portal will be released in the
559                  * upcoming transaction-wide cleanup; they will be gone before we
560                  * run PortalDrop.
561                  */
562                 portal->resowner = NULL;
563         }
564 }
565
566 /*
567  * Post-abort cleanup for portals.
568  *
569  * Delete all portals not held over from prior transactions.  */
570 void
571 AtCleanup_Portals(void)
572 {
573         HASH_SEQ_STATUS status;
574         PortalHashEnt *hentry;
575
576         hash_seq_init(&status, PortalHashTable);
577
578         while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
579         {
580                 Portal          portal = hentry->portal;
581
582                 /* Do nothing to cursors held over from a previous transaction */
583                 if (portal->createSubid == InvalidSubTransactionId)
584                 {
585                         Assert(portal->status != PORTAL_ACTIVE);
586                         Assert(portal->resowner == NULL);
587                         continue;
588                 }
589
590                 /* Else zap it. */
591                 PortalDrop(portal, false);
592         }
593 }
594
595 /*
596  * Pre-subcommit processing for portals.
597  *
598  * Reassign the portals created in the current subtransaction to the parent
599  * subtransaction.
600  */
601 void
602 AtSubCommit_Portals(SubTransactionId mySubid,
603                                         SubTransactionId parentSubid,
604                                         ResourceOwner parentXactOwner)
605 {
606         HASH_SEQ_STATUS status;
607         PortalHashEnt *hentry;
608
609         hash_seq_init(&status, PortalHashTable);
610
611         while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
612         {
613                 Portal          portal = hentry->portal;
614
615                 if (portal->createSubid == mySubid)
616                 {
617                         portal->createSubid = parentSubid;
618                         if (portal->resowner)
619                                 ResourceOwnerNewParent(portal->resowner, parentXactOwner);
620                 }
621         }
622 }
623
624 /*
625  * Subtransaction abort handling for portals.
626  *
627  * Deactivate portals created during the failed subtransaction.
628  * Note that per AtSubCommit_Portals, this will catch portals created
629  * in descendants of the subtransaction too.
630  *
631  * We don't destroy any portals here; that's done in AtSubCleanup_Portals.
632  */
633 void
634 AtSubAbort_Portals(SubTransactionId mySubid,
635                                    SubTransactionId parentSubid,
636                                    ResourceOwner parentXactOwner)
637 {
638         HASH_SEQ_STATUS status;
639         PortalHashEnt *hentry;
640
641         hash_seq_init(&status, PortalHashTable);
642
643         while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
644         {
645                 Portal          portal = hentry->portal;
646
647                 if (portal->createSubid != mySubid)
648                         continue;
649
650                 /*
651                  * Force any active portals of my own transaction into FAILED
652                  * state. This is mostly to ensure that a portal running a FETCH
653                  * will go FAILED if the underlying cursor fails.  (Note we do NOT
654                  * want to do this to upper-level portals, since they may be able
655                  * to continue.)
656                  *
657                  * This is only needed to dodge the sanity check in PortalDrop.
658                  */
659                 if (portal->status == PORTAL_ACTIVE)
660                         portal->status = PORTAL_FAILED;
661
662                 /*
663                  * If the portal is READY then allow it to survive into the parent
664                  * transaction; otherwise shut it down.
665                  *
666                  * Currently, we can't actually support that because the portal's
667                  * query might refer to objects created or changed in the failed
668                  * subtransaction, leading to crashes if execution is resumed.
669                  * So, even READY portals are deleted.  It would be nice to detect
670                  * whether the query actually depends on any such object, instead.
671                  */
672 #ifdef NOT_USED
673                 if (portal->status == PORTAL_READY)
674                 {
675                         portal->createSubid = parentSubid;
676                         if (portal->resowner)
677                                 ResourceOwnerNewParent(portal->resowner, parentXactOwner);
678                 }
679                 else
680 #endif
681                 {
682                         /* let portalcmds.c clean up the state it knows about */
683                         if (PointerIsValid(portal->cleanup))
684                         {
685                                 (*portal->cleanup) (portal);
686                                 portal->cleanup = NULL;
687                         }
688
689                         /*
690                          * Any resources belonging to the portal will be released in
691                          * the upcoming transaction-wide cleanup; they will be gone
692                          * before we run PortalDrop.
693                          */
694                         portal->resowner = NULL;
695                 }
696         }
697 }
698
699 /*
700  * Post-subabort cleanup for portals.
701  *
702  * Drop all portals created in the failed subtransaction (but note that
703  * we will not drop any that were reassigned to the parent above).
704  */
705 void
706 AtSubCleanup_Portals(SubTransactionId mySubid)
707 {
708         HASH_SEQ_STATUS status;
709         PortalHashEnt *hentry;
710
711         hash_seq_init(&status, PortalHashTable);
712
713         while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
714         {
715                 Portal          portal = hentry->portal;
716
717                 if (portal->createSubid != mySubid)
718                         continue;
719
720                 /* Zap it. */
721                 PortalDrop(portal, false);
722         }
723 }