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