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