66 * Copyright (c) 2000-2009, PostgreSQL Global Development Group
77 *
88 * IDENTIFICATION
9- * $PostgreSQL: pgsql/src/backend/access/transam/varsup.c,v 1.84 2009/04/23 00 :23:45 tgl Exp $
9+ * $PostgreSQL: pgsql/src/backend/access/transam/varsup.c,v 1.85 2009/08/31 02 :23:21 tgl Exp $
1010 *
1111 *-------------------------------------------------------------------------
1212 */
1616#include "access/clog.h"
1717#include "access/subtrans.h"
1818#include "access/transam.h"
19+ #include "commands/dbcommands.h"
1920#include "miscadmin.h"
2021#include "postmaster/autovacuum.h"
2122#include "storage/pmsignal.h"
2223#include "storage/proc.h"
2324#include "utils/builtins.h"
25+ #include "utils/syscache.h"
2426
2527
2628/* Number of OIDs to prefetch (preallocate) per XLOG write */
@@ -31,9 +33,14 @@ VariableCache ShmemVariableCache = NULL;
3133
3234
3335/*
34- * Allocate the next XID for my new transaction or subtransaction.
36+ * Allocate the next XID for a new transaction or subtransaction.
3537 *
3638 * The new XID is also stored into MyProc before returning.
39+ *
40+ * Note: when this is called, we are actually already inside a valid
41+ * transaction, since XIDs are now not allocated until the transaction
42+ * does something. So it is safe to do a database lookup if we want to
43+ * issue a warning about XID wrap.
3744 */
3845TransactionId
3946GetNewTransactionId (bool isSubXact )
@@ -72,6 +79,20 @@ GetNewTransactionId(bool isSubXact)
7279 if (TransactionIdFollowsOrEquals (xid , ShmemVariableCache -> xidVacLimit ) &&
7380 TransactionIdIsValid (ShmemVariableCache -> xidVacLimit ))
7481 {
82+ /*
83+ * For safety's sake, we release XidGenLock while sending signals,
84+ * warnings, etc. This is not so much because we care about
85+ * preserving concurrency in this situation, as to avoid any
86+ * possibility of deadlock while doing get_database_name().
87+ * First, copy all the shared values we'll need in this path.
88+ */
89+ TransactionId xidWarnLimit = ShmemVariableCache -> xidWarnLimit ;
90+ TransactionId xidStopLimit = ShmemVariableCache -> xidStopLimit ;
91+ TransactionId xidWrapLimit = ShmemVariableCache -> xidWrapLimit ;
92+ Oid oldest_datoid = ShmemVariableCache -> oldestXidDB ;
93+
94+ LWLockRelease (XidGenLock );
95+
7596 /*
7697 * To avoid swamping the postmaster with signals, we issue the autovac
7798 * request only once per 64K transaction starts. This still gives
@@ -81,22 +102,50 @@ GetNewTransactionId(bool isSubXact)
81102 SendPostmasterSignal (PMSIGNAL_START_AUTOVAC_LAUNCHER );
82103
83104 if (IsUnderPostmaster &&
84- TransactionIdFollowsOrEquals (xid , ShmemVariableCache -> xidStopLimit ))
85- ereport (ERROR ,
86- (errcode (ERRCODE_PROGRAM_LIMIT_EXCEEDED ),
87- errmsg ("database is not accepting commands to avoid wraparound data loss in database \"%s\"" ,
88- NameStr (ShmemVariableCache -> limit_datname )),
89- errhint ("Stop the postmaster and use a standalone backend to vacuum database \"%s\".\n"
90- "You might also need to commit or roll back old prepared transactions." ,
91- NameStr (ShmemVariableCache -> limit_datname ))));
92- else if (TransactionIdFollowsOrEquals (xid , ShmemVariableCache -> xidWarnLimit ))
93- ereport (WARNING ,
94- (errmsg ("database \"%s\" must be vacuumed within %u transactions" ,
95- NameStr (ShmemVariableCache -> limit_datname ),
96- ShmemVariableCache -> xidWrapLimit - xid ),
97- errhint ("To avoid a database shutdown, execute a database-wide VACUUM in \"%s\".\n"
98- "You might also need to commit or roll back old prepared transactions." ,
99- NameStr (ShmemVariableCache -> limit_datname ))));
105+ TransactionIdFollowsOrEquals (xid , xidStopLimit ))
106+ {
107+ char * oldest_datname = get_database_name (oldest_datoid );
108+
109+ /* complain even if that DB has disappeared */
110+ if (oldest_datname )
111+ ereport (ERROR ,
112+ (errcode (ERRCODE_PROGRAM_LIMIT_EXCEEDED ),
113+ errmsg ("database is not accepting commands to avoid wraparound data loss in database \"%s\"" ,
114+ oldest_datname ),
115+ errhint ("Stop the postmaster and use a standalone backend to vacuum that database.\n"
116+ "You might also need to commit or roll back old prepared transactions." )));
117+ else
118+ ereport (ERROR ,
119+ (errcode (ERRCODE_PROGRAM_LIMIT_EXCEEDED ),
120+ errmsg ("database is not accepting commands to avoid wraparound data loss in database with OID %u" ,
121+ oldest_datoid ),
122+ errhint ("Stop the postmaster and use a standalone backend to vacuum that database.\n"
123+ "You might also need to commit or roll back old prepared transactions." )));
124+ }
125+ else if (TransactionIdFollowsOrEquals (xid , xidWarnLimit ))
126+ {
127+ char * oldest_datname = get_database_name (oldest_datoid );
128+
129+ /* complain even if that DB has disappeared */
130+ if (oldest_datname )
131+ ereport (WARNING ,
132+ (errmsg ("database \"%s\" must be vacuumed within %u transactions" ,
133+ oldest_datname ,
134+ xidWrapLimit - xid ),
135+ errhint ("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
136+ "You might also need to commit or roll back old prepared transactions." )));
137+ else
138+ ereport (WARNING ,
139+ (errmsg ("database with OID %u must be vacuumed within %u transactions" ,
140+ oldest_datoid ,
141+ xidWrapLimit - xid ),
142+ errhint ("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
143+ "You might also need to commit or roll back old prepared transactions." )));
144+ }
145+
146+ /* Re-acquire lock and start over */
147+ LWLockAcquire (XidGenLock , LW_EXCLUSIVE );
148+ xid = ShmemVariableCache -> nextXid ;
100149 }
101150
102151 /*
@@ -199,11 +248,10 @@ ReadNewTransactionId(void)
199248/*
200249 * Determine the last safe XID to allocate given the currently oldest
201250 * datfrozenxid (ie, the oldest XID that might exist in any database
202- * of our cluster).
251+ * of our cluster), and the OID of the (or a) database with that value .
203252 */
204253void
205- SetTransactionIdLimit (TransactionId oldest_datfrozenxid ,
206- Name oldest_datname )
254+ SetTransactionIdLimit (TransactionId oldest_datfrozenxid , Oid oldest_datoid )
207255{
208256 TransactionId xidVacLimit ;
209257 TransactionId xidWarnLimit ;
@@ -275,14 +323,14 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid,
275323 ShmemVariableCache -> xidWarnLimit = xidWarnLimit ;
276324 ShmemVariableCache -> xidStopLimit = xidStopLimit ;
277325 ShmemVariableCache -> xidWrapLimit = xidWrapLimit ;
278- namecpy ( & ShmemVariableCache -> limit_datname , oldest_datname ) ;
326+ ShmemVariableCache -> oldestXidDB = oldest_datoid ;
279327 curXid = ShmemVariableCache -> nextXid ;
280328 LWLockRelease (XidGenLock );
281329
282330 /* Log the info */
283331 ereport (DEBUG1 ,
284- (errmsg ("transaction ID wrap limit is %u, limited by database \"%s\" " ,
285- xidWrapLimit , NameStr ( * oldest_datname ) )));
332+ (errmsg ("transaction ID wrap limit is %u, limited by database with OID %u " ,
333+ xidWrapLimit , oldest_datoid )));
286334
287335 /*
288336 * If past the autovacuum force point, immediately signal an autovac
@@ -297,13 +345,59 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid,
297345
298346 /* Give an immediate warning if past the wrap warn point */
299347 if (TransactionIdFollowsOrEquals (curXid , xidWarnLimit ))
300- ereport (WARNING ,
301- (errmsg ("database \"%s\" must be vacuumed within %u transactions" ,
302- NameStr (* oldest_datname ),
303- xidWrapLimit - curXid ),
304- errhint ("To avoid a database shutdown, execute a database-wide VACUUM in \"%s\".\n"
305- "You might also need to commit or roll back old prepared transactions." ,
306- NameStr (* oldest_datname ))));
348+ {
349+ char * oldest_datname = get_database_name (oldest_datoid );
350+
351+ /*
352+ * Note: it's possible that get_database_name fails and returns NULL,
353+ * for example because the database just got dropped. We'll still
354+ * warn, even though the warning might now be unnecessary.
355+ */
356+ if (oldest_datname )
357+ ereport (WARNING ,
358+ (errmsg ("database \"%s\" must be vacuumed within %u transactions" ,
359+ oldest_datname ,
360+ xidWrapLimit - curXid ),
361+ errhint ("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
362+ "You might also need to commit or roll back old prepared transactions." )));
363+ else
364+ ereport (WARNING ,
365+ (errmsg ("database with OID %u must be vacuumed within %u transactions" ,
366+ oldest_datoid ,
367+ xidWrapLimit - curXid ),
368+ errhint ("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
369+ "You might also need to commit or roll back old prepared transactions." )));
370+ }
371+ }
372+
373+
374+ /*
375+ * TransactionIdLimitIsValid -- is the shared XID wrap-limit data sane?
376+ *
377+ * We primarily check whether oldestXidDB is valid. The cases we have in
378+ * mind are that that database was dropped, or the field was reset to zero
379+ * by pg_resetxlog. In either case we should force recalculation of the
380+ * wrap limit. In future we might add some more sanity checks here.
381+ */
382+ bool
383+ TransactionIdLimitIsValid (void )
384+ {
385+ TransactionId oldestXid ;
386+ Oid oldestXidDB ;
387+
388+ /* Locking is probably not really necessary, but let's be careful */
389+ LWLockAcquire (XidGenLock , LW_SHARED );
390+ oldestXid = ShmemVariableCache -> oldestXid ;
391+ oldestXidDB = ShmemVariableCache -> oldestXidDB ;
392+ LWLockRelease (XidGenLock );
393+
394+ if (!TransactionIdIsNormal (oldestXid ))
395+ return false; /* shouldn't happen, but just in case */
396+ if (!SearchSysCacheExists (DATABASEOID ,
397+ ObjectIdGetDatum (oldestXidDB ),
398+ 0 , 0 , 0 ))
399+ return false; /* could happen, per comment above */
400+ return true;
307401}
308402
309403
0 commit comments