@@ -3204,31 +3204,124 @@ LockRefindAndRelease(LockMethod lockMethodTable, PGPROC *proc,
3204
3204
}
3205
3205
}
3206
3206
3207
+ /*
3208
+ * CheckForSessionAndXactLocks
3209
+ * Check to see if transaction holds both session-level and xact-level
3210
+ * locks on the same object; if so, throw an error.
3211
+ *
3212
+ * If we have both session- and transaction-level locks on the same object,
3213
+ * PREPARE TRANSACTION must fail. This should never happen with regular
3214
+ * locks, since we only take those at session level in some special operations
3215
+ * like VACUUM. It's possible to hit this with advisory locks, though.
3216
+ *
3217
+ * It would be nice if we could keep the session hold and give away the
3218
+ * transactional hold to the prepared xact. However, that would require two
3219
+ * PROCLOCK objects, and we cannot be sure that another PROCLOCK will be
3220
+ * available when it comes time for PostPrepare_Locks to do the deed.
3221
+ * So for now, we error out while we can still do so safely.
3222
+ *
3223
+ * Since the LOCALLOCK table stores a separate entry for each lockmode,
3224
+ * we can't implement this check by examining LOCALLOCK entries in isolation.
3225
+ * We must build a transient hashtable that is indexed by locktag only.
3226
+ */
3227
+ static void
3228
+ CheckForSessionAndXactLocks (void )
3229
+ {
3230
+ typedef struct
3231
+ {
3232
+ LOCKTAG lock ; /* identifies the lockable object */
3233
+ bool sessLock ; /* is any lockmode held at session level? */
3234
+ bool xactLock ; /* is any lockmode held at xact level? */
3235
+ } PerLockTagEntry ;
3236
+
3237
+ HASHCTL hash_ctl ;
3238
+ HTAB * lockhtab ;
3239
+ HASH_SEQ_STATUS status ;
3240
+ LOCALLOCK * locallock ;
3241
+
3242
+ /* Create a local hash table keyed by LOCKTAG only */
3243
+ hash_ctl .keysize = sizeof (LOCKTAG );
3244
+ hash_ctl .entrysize = sizeof (PerLockTagEntry );
3245
+ hash_ctl .hcxt = CurrentMemoryContext ;
3246
+
3247
+ lockhtab = hash_create ("CheckForSessionAndXactLocks table" ,
3248
+ 256 , /* arbitrary initial size */
3249
+ & hash_ctl ,
3250
+ HASH_ELEM | HASH_BLOBS | HASH_CONTEXT );
3251
+
3252
+ /* Scan local lock table to find entries for each LOCKTAG */
3253
+ hash_seq_init (& status , LockMethodLocalHash );
3254
+
3255
+ while ((locallock = (LOCALLOCK * ) hash_seq_search (& status )) != NULL )
3256
+ {
3257
+ LOCALLOCKOWNER * lockOwners = locallock -> lockOwners ;
3258
+ PerLockTagEntry * hentry ;
3259
+ bool found ;
3260
+ int i ;
3261
+
3262
+ /*
3263
+ * Ignore VXID locks. We don't want those to be held by prepared
3264
+ * transactions, since they aren't meaningful after a restart.
3265
+ */
3266
+ if (locallock -> tag .lock .locktag_type == LOCKTAG_VIRTUALTRANSACTION )
3267
+ continue ;
3268
+
3269
+ /* Ignore it if we don't actually hold the lock */
3270
+ if (locallock -> nLocks <= 0 )
3271
+ continue ;
3272
+
3273
+ /* Otherwise, find or make an entry in lockhtab */
3274
+ hentry = (PerLockTagEntry * ) hash_search (lockhtab ,
3275
+ (void * ) & locallock -> tag .lock ,
3276
+ HASH_ENTER , & found );
3277
+ if (!found ) /* initialize, if newly created */
3278
+ hentry -> sessLock = hentry -> xactLock = false;
3279
+
3280
+ /* Scan to see if we hold lock at session or xact level or both */
3281
+ for (i = locallock -> numLockOwners - 1 ; i >= 0 ; i -- )
3282
+ {
3283
+ if (lockOwners [i ].owner == NULL )
3284
+ hentry -> sessLock = true;
3285
+ else
3286
+ hentry -> xactLock = true;
3287
+ }
3288
+
3289
+ /*
3290
+ * We can throw error immediately when we see both types of locks; no
3291
+ * need to wait around to see if there are more violations.
3292
+ */
3293
+ if (hentry -> sessLock && hentry -> xactLock )
3294
+ ereport (ERROR ,
3295
+ (errcode (ERRCODE_FEATURE_NOT_SUPPORTED ),
3296
+ errmsg ("cannot PREPARE while holding both session-level and transaction-level locks on the same object" )));
3297
+ }
3298
+
3299
+ /* Success, so clean up */
3300
+ hash_destroy (lockhtab );
3301
+ }
3302
+
3207
3303
/*
3208
3304
* AtPrepare_Locks
3209
3305
* Do the preparatory work for a PREPARE: make 2PC state file records
3210
3306
* for all locks currently held.
3211
3307
*
3212
3308
* Session-level locks are ignored, as are VXID locks.
3213
3309
*
3214
- * There are some special cases that we error out on: we can't be holding any
3215
- * locks at both session and transaction level (since we must either keep or
3216
- * give away the PROCLOCK object), and we can't be holding any locks on
3217
- * temporary objects (since that would mess up the current backend if it tries
3218
- * to exit before the prepared xact is committed).
3310
+ * For the most part, we don't need to touch shared memory for this ---
3311
+ * all the necessary state information is in the locallock table.
3312
+ * Fast-path locks are an exception, however: we move any such locks to
3313
+ * the main table before allowing PREPARE TRANSACTION to succeed.
3219
3314
*/
3220
3315
void
3221
3316
AtPrepare_Locks (void )
3222
3317
{
3223
3318
HASH_SEQ_STATUS status ;
3224
3319
LOCALLOCK * locallock ;
3225
3320
3226
- /*
3227
- * For the most part, we don't need to touch shared memory for this ---
3228
- * all the necessary state information is in the locallock table.
3229
- * Fast-path locks are an exception, however: we move any such locks to
3230
- * the main table before allowing PREPARE TRANSACTION to succeed.
3231
- */
3321
+ /* First, verify there aren't locks of both xact and session level */
3322
+ CheckForSessionAndXactLocks ();
3323
+
3324
+ /* Now do the per-locallock cleanup work */
3232
3325
hash_seq_init (& status , LockMethodLocalHash );
3233
3326
3234
3327
while ((locallock = (LOCALLOCK * ) hash_seq_search (& status )) != NULL )
@@ -3264,19 +3357,7 @@ AtPrepare_Locks(void)
3264
3357
if (!haveXactLock )
3265
3358
continue ;
3266
3359
3267
- /*
3268
- * If we have both session- and transaction-level locks, fail. This
3269
- * should never happen with regular locks, since we only take those at
3270
- * session level in some special operations like VACUUM. It's
3271
- * possible to hit this with advisory locks, though.
3272
- *
3273
- * It would be nice if we could keep the session hold and give away
3274
- * the transactional hold to the prepared xact. However, that would
3275
- * require two PROCLOCK objects, and we cannot be sure that another
3276
- * PROCLOCK will be available when it comes time for PostPrepare_Locks
3277
- * to do the deed. So for now, we error out while we can still do so
3278
- * safely.
3279
- */
3360
+ /* This can't happen, because we already checked it */
3280
3361
if (haveSessionLock )
3281
3362
ereport (ERROR ,
3282
3363
(errcode (ERRCODE_FEATURE_NOT_SUPPORTED ),
0 commit comments