@@ -42,7 +42,29 @@ int max_standby_archive_delay = 30 * 1000;
42
42
int max_standby_streaming_delay = 30 * 1000 ;
43
43
bool log_recovery_conflict_waits = false;
44
44
45
- static HTAB * RecoveryLockLists ;
45
+ /*
46
+ * Keep track of all the exclusive locks owned by original transactions.
47
+ * For each known exclusive lock, there is a RecoveryLockEntry in the
48
+ * RecoveryLockHash hash table. All RecoveryLockEntrys belonging to a
49
+ * given XID are chained together so that we can find them easily.
50
+ * For each original transaction that is known to have any such locks,
51
+ * there is a RecoveryLockXidEntry in the RecoveryLockXidHash hash table,
52
+ * which stores the head of the chain of its locks.
53
+ */
54
+ typedef struct RecoveryLockEntry
55
+ {
56
+ xl_standby_lock key ; /* hash key: xid, dbOid, relOid */
57
+ struct RecoveryLockEntry * next ; /* chain link */
58
+ } RecoveryLockEntry ;
59
+
60
+ typedef struct RecoveryLockXidEntry
61
+ {
62
+ TransactionId xid ; /* hash key -- must be first */
63
+ struct RecoveryLockEntry * head ; /* chain head */
64
+ } RecoveryLockXidEntry ;
65
+
66
+ static HTAB * RecoveryLockHash = NULL ;
67
+ static HTAB * RecoveryLockXidHash = NULL ;
46
68
47
69
/* Flags set by timeout handlers */
48
70
static volatile sig_atomic_t got_standby_deadlock_timeout = false;
@@ -58,15 +80,6 @@ static XLogRecPtr LogCurrentRunningXacts(RunningTransactions CurrRunningXacts);
58
80
static void LogAccessExclusiveLocks (int nlocks , xl_standby_lock * locks );
59
81
static const char * get_recovery_conflict_desc (ProcSignalReason reason );
60
82
61
- /*
62
- * Keep track of all the locks owned by a given transaction.
63
- */
64
- typedef struct RecoveryLockListsEntry
65
- {
66
- TransactionId xid ;
67
- List * locks ;
68
- } RecoveryLockListsEntry ;
69
-
70
83
/*
71
84
* InitRecoveryTransactionEnvironment
72
85
* Initialize tracking of our primary's in-progress transactions.
@@ -85,16 +98,24 @@ InitRecoveryTransactionEnvironment(void)
85
98
VirtualTransactionId vxid ;
86
99
HASHCTL hash_ctl ;
87
100
101
+ Assert (RecoveryLockHash == NULL ); /* don't run this twice */
102
+
88
103
/*
89
- * Initialize the hash table for tracking the list of locks held by each
104
+ * Initialize the hash tables for tracking the locks held by each
90
105
* transaction.
91
106
*/
107
+ hash_ctl .keysize = sizeof (xl_standby_lock );
108
+ hash_ctl .entrysize = sizeof (RecoveryLockEntry );
109
+ RecoveryLockHash = hash_create ("RecoveryLockHash" ,
110
+ 64 ,
111
+ & hash_ctl ,
112
+ HASH_ELEM | HASH_BLOBS );
92
113
hash_ctl .keysize = sizeof (TransactionId );
93
- hash_ctl .entrysize = sizeof (RecoveryLockListsEntry );
94
- RecoveryLockLists = hash_create ("RecoveryLockLists " ,
95
- 64 ,
96
- & hash_ctl ,
97
- HASH_ELEM | HASH_BLOBS );
114
+ hash_ctl .entrysize = sizeof (RecoveryLockXidEntry );
115
+ RecoveryLockXidHash = hash_create ("RecoveryLockXidHash " ,
116
+ 64 ,
117
+ & hash_ctl ,
118
+ HASH_ELEM | HASH_BLOBS );
98
119
99
120
/*
100
121
* Initialize shared invalidation management for Startup process, being
@@ -140,12 +161,12 @@ void
140
161
ShutdownRecoveryTransactionEnvironment (void )
141
162
{
142
163
/*
143
- * Do nothing if RecoveryLockLists is NULL because which means that
144
- * transaction tracking has not been yet initialized or has been already
145
- * shutdowned. This prevents transaction tracking from being shutdowned
146
- * unexpectedly more than once .
164
+ * Do nothing if RecoveryLockHash is NULL because that means that
165
+ * transaction tracking has not yet been initialized or has already been
166
+ * shut down. This makes it safe to have possibly-redundant calls of this
167
+ * function during process exit .
147
168
*/
148
- if (RecoveryLockLists == NULL )
169
+ if (RecoveryLockHash == NULL )
149
170
return ;
150
171
151
172
/* Mark all tracked in-progress transactions as finished. */
@@ -154,9 +175,11 @@ ShutdownRecoveryTransactionEnvironment(void)
154
175
/* Release all locks the tracked transactions were holding */
155
176
StandbyReleaseAllLocks ();
156
177
157
- /* Destroy the hash table of locks. */
158
- hash_destroy (RecoveryLockLists );
159
- RecoveryLockLists = NULL ;
178
+ /* Destroy the lock hash tables. */
179
+ hash_destroy (RecoveryLockHash );
180
+ hash_destroy (RecoveryLockXidHash );
181
+ RecoveryLockHash = NULL ;
182
+ RecoveryLockXidHash = NULL ;
160
183
161
184
/* Cleanup our VirtualTransaction */
162
185
VirtualXactLockTableCleanup ();
@@ -932,12 +955,12 @@ StandbyLockTimeoutHandler(void)
932
955
* We only keep track of AccessExclusiveLocks, which are only ever held by
933
956
* one transaction on one relation.
934
957
*
935
- * We keep a hash table of lists of locks in local memory keyed by xid,
936
- * RecoveryLockLists, so we can keep track of the various entries made by
937
- * the Startup process's virtual xid in the shared lock table.
938
- *
939
- * List elements use type xl_standby_lock, since the WAL record type exactly
940
- * matches the information that we need to keep track of .
958
+ * We keep a table of known locks in the RecoveryLockHash hash table.
959
+ * The point of that table is to let us efficiently de-duplicate locks,
960
+ * which is important because checkpoints will re-report the same locks
961
+ * already held. There is also a RecoveryLockXidHash table with one entry
962
+ * per xid, which allows us to efficiently find all the locks held by a
963
+ * given original transaction .
941
964
*
942
965
* We use session locks rather than normal locks so we don't need
943
966
* ResourceOwners.
@@ -947,8 +970,9 @@ StandbyLockTimeoutHandler(void)
947
970
void
948
971
StandbyAcquireAccessExclusiveLock (TransactionId xid , Oid dbOid , Oid relOid )
949
972
{
950
- RecoveryLockListsEntry * entry ;
951
- xl_standby_lock * newlock ;
973
+ RecoveryLockXidEntry * xidentry ;
974
+ RecoveryLockEntry * lockentry ;
975
+ xl_standby_lock key ;
952
976
LOCKTAG locktag ;
953
977
bool found ;
954
978
@@ -964,62 +988,79 @@ StandbyAcquireAccessExclusiveLock(TransactionId xid, Oid dbOid, Oid relOid)
964
988
/* dbOid is InvalidOid when we are locking a shared relation. */
965
989
Assert (OidIsValid (relOid ));
966
990
967
- /* Create a new list for this xid, if we don't have one already. */
968
- entry = hash_search (RecoveryLockLists , & xid , HASH_ENTER , & found );
991
+ /* Create a hash entry for this xid, if we don't have one already. */
992
+ xidentry = hash_search (RecoveryLockXidHash , & xid , HASH_ENTER , & found );
969
993
if (!found )
970
994
{
971
- entry -> xid = xid ;
972
- entry -> locks = NIL ;
995
+ Assert ( xidentry -> xid == xid ); /* dynahash should have set this */
996
+ xidentry -> head = NULL ;
973
997
}
974
998
975
- newlock = palloc (sizeof (xl_standby_lock ));
976
- newlock -> xid = xid ;
977
- newlock -> dbOid = dbOid ;
978
- newlock -> relOid = relOid ;
979
- entry -> locks = lappend (entry -> locks , newlock );
999
+ /* Create a hash entry for this lock, unless we have one already. */
1000
+ key .xid = xid ;
1001
+ key .dbOid = dbOid ;
1002
+ key .relOid = relOid ;
1003
+ lockentry = hash_search (RecoveryLockHash , & key , HASH_ENTER , & found );
1004
+ if (!found )
1005
+ {
1006
+ /* It's new, so link it into the XID's list ... */
1007
+ lockentry -> next = xidentry -> head ;
1008
+ xidentry -> head = lockentry ;
980
1009
981
- SET_LOCKTAG_RELATION (locktag , newlock -> dbOid , newlock -> relOid );
1010
+ /* ... and acquire the lock locally. */
1011
+ SET_LOCKTAG_RELATION (locktag , dbOid , relOid );
982
1012
983
- (void ) LockAcquire (& locktag , AccessExclusiveLock , true, false);
1013
+ (void ) LockAcquire (& locktag , AccessExclusiveLock , true, false);
1014
+ }
984
1015
}
985
1016
1017
+ /*
1018
+ * Release all the locks associated with this RecoveryLockXidEntry.
1019
+ */
986
1020
static void
987
- StandbyReleaseLockList ( List * locks )
1021
+ StandbyReleaseXidEntryLocks ( RecoveryLockXidEntry * xidentry )
988
1022
{
989
- ListCell * lc ;
1023
+ RecoveryLockEntry * entry ;
1024
+ RecoveryLockEntry * next ;
990
1025
991
- foreach ( lc , locks )
1026
+ for ( entry = xidentry -> head ; entry != NULL ; entry = next )
992
1027
{
993
- xl_standby_lock * lock = (xl_standby_lock * ) lfirst (lc );
994
1028
LOCKTAG locktag ;
995
1029
996
1030
elog (trace_recovery (DEBUG4 ),
997
1031
"releasing recovery lock: xid %u db %u rel %u" ,
998
- lock -> xid , lock -> dbOid , lock -> relOid );
999
- SET_LOCKTAG_RELATION (locktag , lock -> dbOid , lock -> relOid );
1032
+ entry -> key .xid , entry -> key .dbOid , entry -> key .relOid );
1033
+ /* Release the lock ... */
1034
+ SET_LOCKTAG_RELATION (locktag , entry -> key .dbOid , entry -> key .relOid );
1000
1035
if (!LockRelease (& locktag , AccessExclusiveLock , true))
1001
1036
{
1002
1037
elog (LOG ,
1003
- "RecoveryLockLists contains entry for lock no longer recorded by lock manager: xid %u database %u relation %u" ,
1004
- lock -> xid , lock -> dbOid , lock -> relOid );
1038
+ "RecoveryLockHash contains entry for lock no longer recorded by lock manager: xid %u database %u relation %u" ,
1039
+ entry -> key . xid , entry -> key . dbOid , entry -> key . relOid );
1005
1040
Assert (false);
1006
1041
}
1042
+ /* ... and remove the per-lock hash entry */
1043
+ next = entry -> next ;
1044
+ hash_search (RecoveryLockHash , entry , HASH_REMOVE , NULL );
1007
1045
}
1008
1046
1009
- list_free_deep ( locks );
1047
+ xidentry -> head = NULL ; /* just for paranoia */
1010
1048
}
1011
1049
1050
+ /*
1051
+ * Release locks for specific XID, or all locks if it's InvalidXid.
1052
+ */
1012
1053
static void
1013
1054
StandbyReleaseLocks (TransactionId xid )
1014
1055
{
1015
- RecoveryLockListsEntry * entry ;
1056
+ RecoveryLockXidEntry * entry ;
1016
1057
1017
1058
if (TransactionIdIsValid (xid ))
1018
1059
{
1019
- if ((entry = hash_search (RecoveryLockLists , & xid , HASH_FIND , NULL )))
1060
+ if ((entry = hash_search (RecoveryLockXidHash , & xid , HASH_FIND , NULL )))
1020
1061
{
1021
- StandbyReleaseLockList (entry -> locks );
1022
- hash_search (RecoveryLockLists , entry , HASH_REMOVE , NULL );
1062
+ StandbyReleaseXidEntryLocks (entry );
1063
+ hash_search (RecoveryLockXidHash , entry , HASH_REMOVE , NULL );
1023
1064
}
1024
1065
}
1025
1066
else
@@ -1028,7 +1069,7 @@ StandbyReleaseLocks(TransactionId xid)
1028
1069
1029
1070
/*
1030
1071
* Release locks for a transaction tree, starting at xid down, from
1031
- * RecoveryLockLists .
1072
+ * RecoveryLockXidHash .
1032
1073
*
1033
1074
* Called during WAL replay of COMMIT/ROLLBACK when in hot standby mode,
1034
1075
* to remove any AccessExclusiveLocks requested by a transaction.
@@ -1051,15 +1092,15 @@ void
1051
1092
StandbyReleaseAllLocks (void )
1052
1093
{
1053
1094
HASH_SEQ_STATUS status ;
1054
- RecoveryLockListsEntry * entry ;
1095
+ RecoveryLockXidEntry * entry ;
1055
1096
1056
1097
elog (trace_recovery (DEBUG2 ), "release all standby locks" );
1057
1098
1058
- hash_seq_init (& status , RecoveryLockLists );
1099
+ hash_seq_init (& status , RecoveryLockXidHash );
1059
1100
while ((entry = hash_seq_search (& status )))
1060
1101
{
1061
- StandbyReleaseLockList (entry -> locks );
1062
- hash_search (RecoveryLockLists , entry , HASH_REMOVE , NULL );
1102
+ StandbyReleaseXidEntryLocks (entry );
1103
+ hash_search (RecoveryLockXidHash , entry , HASH_REMOVE , NULL );
1063
1104
}
1064
1105
}
1065
1106
@@ -1072,9 +1113,9 @@ void
1072
1113
StandbyReleaseOldLocks (TransactionId oldxid )
1073
1114
{
1074
1115
HASH_SEQ_STATUS status ;
1075
- RecoveryLockListsEntry * entry ;
1116
+ RecoveryLockXidEntry * entry ;
1076
1117
1077
- hash_seq_init (& status , RecoveryLockLists );
1118
+ hash_seq_init (& status , RecoveryLockXidHash );
1078
1119
while ((entry = hash_seq_search (& status )))
1079
1120
{
1080
1121
Assert (TransactionIdIsValid (entry -> xid ));
@@ -1088,8 +1129,8 @@ StandbyReleaseOldLocks(TransactionId oldxid)
1088
1129
continue ;
1089
1130
1090
1131
/* Remove all locks and hash table entry. */
1091
- StandbyReleaseLockList (entry -> locks );
1092
- hash_search (RecoveryLockLists , entry , HASH_REMOVE , NULL );
1132
+ StandbyReleaseXidEntryLocks (entry );
1133
+ hash_search (RecoveryLockXidHash , entry , HASH_REMOVE , NULL );
1093
1134
}
1094
1135
}
1095
1136
0 commit comments