@@ -3204,31 +3204,124 @@ LockRefindAndRelease(LockMethod lockMethodTable, PGPROC *proc,
32043204 }
32053205}
32063206
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+
32073303/*
32083304 * AtPrepare_Locks
32093305 * Do the preparatory work for a PREPARE: make 2PC state file records
32103306 * for all locks currently held.
32113307 *
32123308 * Session-level locks are ignored, as are VXID locks.
32133309 *
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.
32193314 */
32203315void
32213316AtPrepare_Locks (void )
32223317{
32233318 HASH_SEQ_STATUS status ;
32243319 LOCALLOCK * locallock ;
32253320
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 */
32323325 hash_seq_init (& status , LockMethodLocalHash );
32333326
32343327 while ((locallock = (LOCALLOCK * ) hash_seq_search (& status )) != NULL )
@@ -3264,19 +3357,7 @@ AtPrepare_Locks(void)
32643357 if (!haveXactLock )
32653358 continue ;
32663359
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 */
32803361 if (haveSessionLock )
32813362 ereport (ERROR ,
32823363 (errcode (ERRCODE_FEATURE_NOT_SUPPORTED ),
0 commit comments