@@ -3114,31 +3114,124 @@ LockRefindAndRelease(LockMethod lockMethodTable, PGPROC *proc,
3114
3114
}
3115
3115
}
3116
3116
3117
+ /*
3118
+ * CheckForSessionAndXactLocks
3119
+ * Check to see if transaction holds both session-level and xact-level
3120
+ * locks on the same object; if so, throw an error.
3121
+ *
3122
+ * If we have both session- and transaction-level locks on the same object,
3123
+ * PREPARE TRANSACTION must fail. This should never happen with regular
3124
+ * locks, since we only take those at session level in some special operations
3125
+ * like VACUUM. It's possible to hit this with advisory locks, though.
3126
+ *
3127
+ * It would be nice if we could keep the session hold and give away the
3128
+ * transactional hold to the prepared xact. However, that would require two
3129
+ * PROCLOCK objects, and we cannot be sure that another PROCLOCK will be
3130
+ * available when it comes time for PostPrepare_Locks to do the deed.
3131
+ * So for now, we error out while we can still do so safely.
3132
+ *
3133
+ * Since the LOCALLOCK table stores a separate entry for each lockmode,
3134
+ * we can't implement this check by examining LOCALLOCK entries in isolation.
3135
+ * We must build a transient hashtable that is indexed by locktag only.
3136
+ */
3137
+ static void
3138
+ CheckForSessionAndXactLocks (void )
3139
+ {
3140
+ typedef struct
3141
+ {
3142
+ LOCKTAG lock ; /* identifies the lockable object */
3143
+ bool sessLock ; /* is any lockmode held at session level? */
3144
+ bool xactLock ; /* is any lockmode held at xact level? */
3145
+ } PerLockTagEntry ;
3146
+
3147
+ HASHCTL hash_ctl ;
3148
+ HTAB * lockhtab ;
3149
+ HASH_SEQ_STATUS status ;
3150
+ LOCALLOCK * locallock ;
3151
+
3152
+ /* Create a local hash table keyed by LOCKTAG only */
3153
+ hash_ctl .keysize = sizeof (LOCKTAG );
3154
+ hash_ctl .entrysize = sizeof (PerLockTagEntry );
3155
+ hash_ctl .hcxt = CurrentMemoryContext ;
3156
+
3157
+ lockhtab = hash_create ("CheckForSessionAndXactLocks table" ,
3158
+ 256 , /* arbitrary initial size */
3159
+ & hash_ctl ,
3160
+ HASH_ELEM | HASH_BLOBS | HASH_CONTEXT );
3161
+
3162
+ /* Scan local lock table to find entries for each LOCKTAG */
3163
+ hash_seq_init (& status , LockMethodLocalHash );
3164
+
3165
+ while ((locallock = (LOCALLOCK * ) hash_seq_search (& status )) != NULL )
3166
+ {
3167
+ LOCALLOCKOWNER * lockOwners = locallock -> lockOwners ;
3168
+ PerLockTagEntry * hentry ;
3169
+ bool found ;
3170
+ int i ;
3171
+
3172
+ /*
3173
+ * Ignore VXID locks. We don't want those to be held by prepared
3174
+ * transactions, since they aren't meaningful after a restart.
3175
+ */
3176
+ if (locallock -> tag .lock .locktag_type == LOCKTAG_VIRTUALTRANSACTION )
3177
+ continue ;
3178
+
3179
+ /* Ignore it if we don't actually hold the lock */
3180
+ if (locallock -> nLocks <= 0 )
3181
+ continue ;
3182
+
3183
+ /* Otherwise, find or make an entry in lockhtab */
3184
+ hentry = (PerLockTagEntry * ) hash_search (lockhtab ,
3185
+ (void * ) & locallock -> tag .lock ,
3186
+ HASH_ENTER , & found );
3187
+ if (!found ) /* initialize, if newly created */
3188
+ hentry -> sessLock = hentry -> xactLock = false;
3189
+
3190
+ /* Scan to see if we hold lock at session or xact level or both */
3191
+ for (i = locallock -> numLockOwners - 1 ; i >= 0 ; i -- )
3192
+ {
3193
+ if (lockOwners [i ].owner == NULL )
3194
+ hentry -> sessLock = true;
3195
+ else
3196
+ hentry -> xactLock = true;
3197
+ }
3198
+
3199
+ /*
3200
+ * We can throw error immediately when we see both types of locks; no
3201
+ * need to wait around to see if there are more violations.
3202
+ */
3203
+ if (hentry -> sessLock && hentry -> xactLock )
3204
+ ereport (ERROR ,
3205
+ (errcode (ERRCODE_FEATURE_NOT_SUPPORTED ),
3206
+ errmsg ("cannot PREPARE while holding both session-level and transaction-level locks on the same object" )));
3207
+ }
3208
+
3209
+ /* Success, so clean up */
3210
+ hash_destroy (lockhtab );
3211
+ }
3212
+
3117
3213
/*
3118
3214
* AtPrepare_Locks
3119
3215
* Do the preparatory work for a PREPARE: make 2PC state file records
3120
3216
* for all locks currently held.
3121
3217
*
3122
3218
* Session-level locks are ignored, as are VXID locks.
3123
3219
*
3124
- * There are some special cases that we error out on: we can't be holding any
3125
- * locks at both session and transaction level (since we must either keep or
3126
- * give away the PROCLOCK object), and we can't be holding any locks on
3127
- * temporary objects (since that would mess up the current backend if it tries
3128
- * to exit before the prepared xact is committed).
3220
+ * For the most part, we don't need to touch shared memory for this ---
3221
+ * all the necessary state information is in the locallock table.
3222
+ * Fast-path locks are an exception, however: we move any such locks to
3223
+ * the main table before allowing PREPARE TRANSACTION to succeed.
3129
3224
*/
3130
3225
void
3131
3226
AtPrepare_Locks (void )
3132
3227
{
3133
3228
HASH_SEQ_STATUS status ;
3134
3229
LOCALLOCK * locallock ;
3135
3230
3136
- /*
3137
- * For the most part, we don't need to touch shared memory for this ---
3138
- * all the necessary state information is in the locallock table.
3139
- * Fast-path locks are an exception, however: we move any such locks to
3140
- * the main table before allowing PREPARE TRANSACTION to succeed.
3141
- */
3231
+ /* First, verify there aren't locks of both xact and session level */
3232
+ CheckForSessionAndXactLocks ();
3233
+
3234
+ /* Now do the per-locallock cleanup work */
3142
3235
hash_seq_init (& status , LockMethodLocalHash );
3143
3236
3144
3237
while ((locallock = (LOCALLOCK * ) hash_seq_search (& status )) != NULL )
@@ -3174,19 +3267,7 @@ AtPrepare_Locks(void)
3174
3267
if (!haveXactLock )
3175
3268
continue ;
3176
3269
3177
- /*
3178
- * If we have both session- and transaction-level locks, fail. This
3179
- * should never happen with regular locks, since we only take those at
3180
- * session level in some special operations like VACUUM. It's
3181
- * possible to hit this with advisory locks, though.
3182
- *
3183
- * It would be nice if we could keep the session hold and give away
3184
- * the transactional hold to the prepared xact. However, that would
3185
- * require two PROCLOCK objects, and we cannot be sure that another
3186
- * PROCLOCK will be available when it comes time for PostPrepare_Locks
3187
- * to do the deed. So for now, we error out while we can still do so
3188
- * safely.
3189
- */
3270
+ /* This can't happen, because we already checked it */
3190
3271
if (haveSessionLock )
3191
3272
ereport (ERROR ,
3192
3273
(errcode (ERRCODE_FEATURE_NOT_SUPPORTED ),
0 commit comments