6
6
* Copyright (c) 2000-2009, PostgreSQL Global Development Group
7
7
*
8
8
* 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 $
10
10
*
11
11
*-------------------------------------------------------------------------
12
12
*/
16
16
#include "access/clog.h"
17
17
#include "access/subtrans.h"
18
18
#include "access/transam.h"
19
+ #include "commands/dbcommands.h"
19
20
#include "miscadmin.h"
20
21
#include "postmaster/autovacuum.h"
21
22
#include "storage/pmsignal.h"
22
23
#include "storage/proc.h"
23
24
#include "utils/builtins.h"
25
+ #include "utils/syscache.h"
24
26
25
27
26
28
/* Number of OIDs to prefetch (preallocate) per XLOG write */
@@ -31,9 +33,14 @@ VariableCache ShmemVariableCache = NULL;
31
33
32
34
33
35
/*
34
- * Allocate the next XID for my new transaction or subtransaction.
36
+ * Allocate the next XID for a new transaction or subtransaction.
35
37
*
36
38
* 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.
37
44
*/
38
45
TransactionId
39
46
GetNewTransactionId (bool isSubXact )
@@ -72,6 +79,20 @@ GetNewTransactionId(bool isSubXact)
72
79
if (TransactionIdFollowsOrEquals (xid , ShmemVariableCache -> xidVacLimit ) &&
73
80
TransactionIdIsValid (ShmemVariableCache -> xidVacLimit ))
74
81
{
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
+
75
96
/*
76
97
* To avoid swamping the postmaster with signals, we issue the autovac
77
98
* request only once per 64K transaction starts. This still gives
@@ -81,22 +102,50 @@ GetNewTransactionId(bool isSubXact)
81
102
SendPostmasterSignal (PMSIGNAL_START_AUTOVAC_LAUNCHER );
82
103
83
104
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 ;
100
149
}
101
150
102
151
/*
@@ -199,11 +248,10 @@ ReadNewTransactionId(void)
199
248
/*
200
249
* Determine the last safe XID to allocate given the currently oldest
201
250
* 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 .
203
252
*/
204
253
void
205
- SetTransactionIdLimit (TransactionId oldest_datfrozenxid ,
206
- Name oldest_datname )
254
+ SetTransactionIdLimit (TransactionId oldest_datfrozenxid , Oid oldest_datoid )
207
255
{
208
256
TransactionId xidVacLimit ;
209
257
TransactionId xidWarnLimit ;
@@ -275,14 +323,14 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid,
275
323
ShmemVariableCache -> xidWarnLimit = xidWarnLimit ;
276
324
ShmemVariableCache -> xidStopLimit = xidStopLimit ;
277
325
ShmemVariableCache -> xidWrapLimit = xidWrapLimit ;
278
- namecpy ( & ShmemVariableCache -> limit_datname , oldest_datname ) ;
326
+ ShmemVariableCache -> oldestXidDB = oldest_datoid ;
279
327
curXid = ShmemVariableCache -> nextXid ;
280
328
LWLockRelease (XidGenLock );
281
329
282
330
/* Log the info */
283
331
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 )));
286
334
287
335
/*
288
336
* If past the autovacuum force point, immediately signal an autovac
@@ -297,13 +345,59 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid,
297
345
298
346
/* Give an immediate warning if past the wrap warn point */
299
347
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;
307
401
}
308
402
309
403
0 commit comments