diff options
author | unknown <monty@donna.mysql.com> | 2001-02-17 14:19:19 +0200 |
---|---|---|
committer | unknown <monty@donna.mysql.com> | 2001-02-17 14:19:19 +0200 |
commit | 2662b59306ef0cd495fa6e2edf7129e58a11393a (patch) | |
tree | bfe39951a73e906579ab819bf5198ad8f3a64a36 /innobase/lock | |
parent | 66de55a56bdcf2f7a9c0c4f8e19b3e761475e202 (diff) | |
download | mariadb-git-2662b59306ef0cd495fa6e2edf7129e58a11393a.tar.gz |
Added Innobase to source distribution
Docs/manual.texi:
Added Innobase documentation
configure.in:
Incremented version
include/my_base.h:
Added option for Innobase
myisam/mi_check.c:
cleanup
mysql-test/t/bdb.test:
cleanup
mysql-test/t/innobase.test:
Extended with new tests from bdb.test
mysql-test/t/merge.test:
Added test of SHOW create
mysys/my_init.c:
Fix for UNIXWARE 7
scripts/mysql_install_db.sh:
Always write how to start mysqld
scripts/safe_mysqld.sh:
Fixed type
sql/ha_innobase.cc:
Update to new version
sql/ha_innobase.h:
Update to new version
sql/handler.h:
Added 'update_table_comment()' and 'append_create_info()'
sql/sql_delete.cc:
Fixes for Innobase
sql/sql_select.cc:
Fixes for Innobase
sql/sql_show.cc:
Append create information (for MERGE tables)
sql/sql_update.cc:
Fixes for Innobase
Diffstat (limited to 'innobase/lock')
-rw-r--r-- | innobase/lock/Makefile.am | 24 | ||||
-rw-r--r-- | innobase/lock/lock0lock.c | 3976 | ||||
-rw-r--r-- | innobase/lock/makefilewin | 7 |
3 files changed, 4007 insertions, 0 deletions
diff --git a/innobase/lock/Makefile.am b/innobase/lock/Makefile.am new file mode 100644 index 00000000000..f9e1b227f3c --- /dev/null +++ b/innobase/lock/Makefile.am @@ -0,0 +1,24 @@ +# Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB +# & Innobase Oy +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +include ../include/Makefile.i + +libs_LIBRARIES = liblock.a + +liblock_a_SOURCES = lock0lock.c + +EXTRA_PROGRAMS = diff --git a/innobase/lock/lock0lock.c b/innobase/lock/lock0lock.c new file mode 100644 index 00000000000..efea160fac1 --- /dev/null +++ b/innobase/lock/lock0lock.c @@ -0,0 +1,3976 @@ +/****************************************************** +The transaction lock system + +(c) 1996 Innobase Oy + +Created 5/7/1996 Heikki Tuuri +*******************************************************/ + +#include "lock0lock.h" + +#ifdef UNIV_NONINL +#include "lock0lock.ic" +#endif + +#include "usr0sess.h" + +/* When releasing transaction locks, this specifies how often we release +the kernel mutex for a moment to give also others access to it */ + +#define LOCK_RELEASE_KERNEL_INTERVAL 1000 + +/* Safety margin when creating a new record lock: this many extra records +can be inserted to the page without need to create a lock with a bigger +bitmap */ + +#define LOCK_PAGE_BITMAP_MARGIN 64 + +/* An explicit record lock affects both the record and the gap before it. +An implicit x-lock does not affect the gap, it only locks the index +record from read or update. + +If a transaction has modified or inserted an index record, then +it owns an implicit x-lock on the record. On a secondary index record, +a transaction has an implicit x-lock also if it has modified the +clustered index record, the max trx id of the page where the secondary +index record resides is >= trx id of the transaction (or database recovery +is running), and there are no explicit non-gap lock requests on the +secondary index record. + +This complicated definition for a secondary index comes from the +implementation: we want to be able to determine if a secondary index +record has an implicit x-lock, just by looking at the present clustered +index record, not at the historical versions of the record. The +complicated definition can be explained to the user so that there is +nondeterminism in the access path when a query is answered: we may, +or may not, access the clustered index record and thus may, or may not, +bump into an x-lock set there. + +Different transaction can have conflicting locks set on the gap at the +same time. The locks on the gap are purely inhibitive: an insert cannot +be made, or a select cursor may have to wait, if a different transaction +has a conflicting lock on the gap. An x-lock on the gap does not give +the right to insert into the gap if there are conflicting locks granted +on the gap at the same time. + +An explicit lock can be placed on a user record or the supremum record of +a page. The locks on the supremum record are always thought to be of the gap +type, though the gap bit is not set. When we perform an update of a record +where the size of the record changes, we may temporarily store its explicit +locks on the infimum record of the page, though the infimum otherwise never +carries locks. + +A waiting record lock can also be of the gap type. A waiting lock request +can be granted when there is no conflicting mode lock request by another +transaction ahead of it in the explicit lock queue. + +------------------------------------------------------------------------- +RULE 1: If there is an implicit x-lock on a record, and there are non-gap +------- +lock requests waiting in the queue, then the transaction holding the implicit +x-lock also has an explicit non-gap record x-lock. Therefore, as locks are +released, we can grant locks to waiting lock requests purely by looking at +the explicit lock requests in the queue. + +RULE 2: Granted non-gap locks on a record are always ahead in the queue +------- +of waiting non-gap locks on a record. + +RULE 3: Different transactions cannot have conflicting granted non-gap locks +------- +on a record at the same time. However, they can have conflicting granted gap +locks. +RULE 4: If a there is a waiting lock request in a queue, no lock request, +------- +gap or not, can be inserted ahead of it in the queue. In record deletes +and page splits, new gap type locks can be created by the database manager +for a transaction, and without rule 4, the waits-for graph of transactions +might become cyclic without the database noticing it, as the deadlock check +is only performed when a transaction itself requests a lock! +------------------------------------------------------------------------- + +An insert is allowed to a gap if there are no explicit lock requests by +other transactions on the next record. It does not matter if these lock +requests are granted or waiting, gap bit set or not. On the other hand, an +implicit x-lock by another transaction does not prevent an insert, which +allows for more concurrency when using an Oracle-style sequence number +generator for the primary key with many transactions doing inserts +concurrently. + +A modify of a record is allowed if the transaction has an x-lock on the +record, or if other transactions do not have any non-gap lock requests on the +record. + +A read of a single user record with a cursor is allowed if the transaction +has a non-gap explicit, or an implicit lock on the record, or if the other +transactions have no x-lock requests on the record. At a page supremum a +read is always allowed. + +In summary, an implicit lock is seen as a granted x-lock only on the +record, not on the gap. An explicit lock with no gap bit set is a lock +both on the record and the gap. If the gap bit is set, the lock is only +on the gap. Different transaction cannot own conflicting locks on the +record at the same time, but they may own conflicting locks on the gap. +Granted locks on a record give an access right to the record, but gap type +locks just inhibit operations. + +NOTE: Finding out if some transaction has an implicit x-lock on a secondary +index record can be cumbersome. We may have to look at previous versions of +the corresponding clustered index record to find out if a delete marked +secondary index record was delete marked by an active transaction, not by +a committed one. + +FACT A: If a transaction has inserted a row, it can delete it any time +without need to wait for locks. + +PROOF: The transaction has an implicit x-lock on every index record inserted +for the row, and can thus modify each record without the need to wait. Q.E.D. + +FACT B: If a transaction has read some result set with a cursor, it can read +it again, and retrieves the same result set, if it has not modified the +result set in the meantime. Hence, there is no phantom problem. If the +biggest record, in the alphabetical order, touched by the cursor is removed, +a lock wait may occur, otherwise not. + +PROOF: When a read cursor proceeds, it sets an s-lock on each user record +it passes, and a gap type s-lock on each page supremum. The cursor must +wait until it has these locks granted. Then no other transaction can +have a granted x-lock on any of the user records, and therefore cannot +modify the user records. Neither can any other transaction insert into +the gaps which were passed over by the cursor. Page splits and merges, +and removal of obsolete versions of records do not affect this, because +when a user record or a page supremum is removed, the next record inherits +its locks as gap type locks, and therefore blocks inserts to the same gap. +Also, if a page supremum is inserted, it inherits its locks from the successor +record. When the cursor is positioned again at the start of the result set, +the records it will touch on its course are either records it touched +during the last pass or new inserted page supremums. It can immediately +access all these records, and when it arrives at the biggest record, it +notices that the result set is complete. If the biggest record was removed, +lock wait can occur because the next record only inherits a gap type lock, +and a wait may be needed. Q.E.D. */ + +/* If an index record should be changed or a new inserted, we must check +the lock on the record or the next. When a read cursor starts reading, +we will set a record level s-lock on each record it passes, except on the +initial record on which the cursor is positioned before we start to fetch +records. Our index tree search has the convention that the B-tree +cursor is positioned BEFORE the first possibly matching record in +the search. Optimizations are possible here: if the record is searched +on an equality condition to a unique key, we could actually set a special +lock on the record, a lock which would not prevent any insert before +this record. In the next key locking an x-lock set on a record also +prevents inserts just before that record. + There are special infimum and supremum records on each page. +A supremum record can be locked by a read cursor. This records cannot be +updated but the lock prevents insert of a user record to the end of +the page. + Next key locks will prevent the phantom problem where new rows +could appear to SELECT result sets after the select operation has been +performed. Prevention of phantoms ensures the serilizability of +transactions. + What should we check if an insert of a new record is wanted? +Only the lock on the next record on the same page, because also the +supremum record can carry a lock. An s-lock prevents insertion, but +what about an x-lock? If it was set by a searched update, then there +is implicitly an s-lock, too, and the insert should be prevented. +What if our transaction owns an x-lock to the next record, but there is +a waiting s-lock request on the next record? If this s-lock was placed +by a read cursor moving in the ascending order in the index, we cannot +do the insert immediately, because when we finally commit our transaction, +the read cursor should see also the new inserted record. So we should +move the read cursor backward from the the next record for it to pass over +the new inserted record. This move backward may be too cumbersome to +implement. If we in this situation just enqueue a second x-lock request +for our transaction on the next record, then the deadlock mechanism +notices a deadlock between our transaction and the s-lock request +transaction. This seems to be an ok solution. + We could have the convention that granted explicit record locks, +lock the corresponding records from changing, and also lock the gaps +before them from inserting. A waiting explicit lock request locks the gap +before from inserting. Implicit record x-locks, which we derive from the +transaction id in the clustered index record, only lock the record itself +from modification, not the gap before it from inserting. + How should we store update locks? If the search is done by a unique +key, we could just modify the record trx id. Otherwise, we could put a record +x-lock on the record. If the update changes ordering fields of the +clustered index record, the inserted new record needs no record lock in +lock table, the trx id is enough. The same holds for a secondary index +record. Searched delete is similar to update. + +PROBLEM: +What about waiting lock requests? If a transaction is waiting to make an +update to a record which another modified, how does the other transaction +know to send the end-lock-wait signal to the waiting transaction? If we have +the convention that a transaction may wait for just one lock at a time, how +do we preserve it if lock wait ends? + +PROBLEM: +Checking the trx id label of a secondary index record. In the case of a +modification, not an insert, is this necessary? A secondary index record +is modified only by setting or resetting its deleted flag. A secondary index +record contains fields to uniquely determine the corresponding clustered +index record. A secondary index record is therefore only modified if we +also modify the clustered index record, and the trx id checking is done +on the clustered index record, before we come to modify the secondary index +record. So, in the case of delete marking or unmarking a secondary index +record, we do not have to care about trx ids, only the locks in the lock +table must be checked. In the case of a select from a secondary index, the +trx id is relevant, and in this case we may have to search the clustered +index record. + +PROBLEM: How to update record locks when page is split or merged, or +-------------------------------------------------------------------- +a record is deleted or updated? +If the size of fields in a record changes, we perform the update by +a delete followed by an insert. How can we retain the locks set or +waiting on the record? Because a record lock is indexed in the bitmap +by the heap number of the record, when we remove the record from the +record list, it is possible still to keep the lock bits. If the page +is reorganized, we could make a table of old and new heap numbers, +and permute the bitmaps in the locks accordingly. We can add to the +table a row telling where the updated record ended. If the update does +not require a reorganization of the page, we can simply move the lock +bits for the updated record to the position determined by its new heap +number (we may have to allocate a new lock, if we run out of the bitmap +in the old one). + A more complicated case is the one where the reinsertion of the +updated record is done pessimistically, because the structure of the +tree may change. + +PROBLEM: If a supremum record is removed in a page merge, or a record +--------------------------------------------------------------------- +removed in a purge, what to do to the waiting lock requests? In a split to +the right, we just move the lock requests to the new supremum. If a record +is removed, we could move the waiting lock request to its inheritor, the +next record in the index. But, the next record may already have lock +requests on its own queue. A new deadlock check should be made then. Maybe +it is easier just to release the waiting transactions. They can then enqueue +new lock requests on appropriate records. + +PROBLEM: When a record is inserted, what locks should it inherit from the +------------------------------------------------------------------------- +upper neighbor? An insert of a new supremum record in a page split is +always possible, but an insert of a new user record requires that the upper +neighbor does not have any lock requests by other transactions, granted or +waiting, in its lock queue. Solution: We can copy the locks as gap type +locks, so that also the waiting locks are transformed to granted gap type +locks on the inserted record. */ + +ibool lock_print_waits = FALSE; + +/* The lock system */ +lock_sys_t* lock_sys = NULL; + +/* A table lock */ +typedef struct lock_table_struct lock_table_t; +struct lock_table_struct{ + dict_table_t* table; /* database table in dictionary cache */ + UT_LIST_NODE_T(lock_t) + locks; /* list of locks on the same table */ +}; + +/* Record lock for a page */ +typedef struct lock_rec_struct lock_rec_t; +struct lock_rec_struct{ + ulint space; /* space id */ + ulint page_no; /* page number */ + ulint n_bits; /* number of bits in the lock bitmap */ + /* NOTE: the lock bitmap is placed immediately + after the lock struct */ +}; + +/* Lock struct */ +struct lock_struct{ + trx_t* trx; /* transaction owning the lock */ + UT_LIST_NODE_T(lock_t) + trx_locks; /* list of the locks of the + transaction */ + ulint type_mode; /* lock type, mode, gap flag, and + wait flag, ORed */ + hash_node_t hash; /* hash chain node for a record lock */ + dict_index_t* index; /* index for a record lock */ + union { + lock_table_t tab_lock;/* table lock */ + lock_rec_t rec_lock;/* record lock */ + } un_member; +}; + +/************************************************************************ +Checks if a lock request results in a deadlock. */ +static +ibool +lock_deadlock_occurs( +/*=================*/ + /* out: TRUE if a deadlock was detected */ + lock_t* lock, /* in: lock the transaction is requesting */ + trx_t* trx); /* in: transaction */ +/************************************************************************ +Looks recursively for a deadlock. */ +static +ibool +lock_deadlock_recursive( +/*====================*/ + /* out: TRUE if a deadlock was detected */ + trx_t* start, /* in: recursion starting point */ + trx_t* trx, /* in: a transaction waiting for a lock */ + lock_t* wait_lock); /* in: the lock trx is waiting to be granted */ + +/************************************************************************* +Reserves the kernel mutex. This function is used in this module to allow +monitoring the contention degree on the kernel mutex caused by the lock +operations. */ +UNIV_INLINE +void +lock_mutex_enter_kernel(void) +/*=========================*/ +{ + mutex_enter(&kernel_mutex); +} + +/************************************************************************* +Releses the kernel mutex. This function is used in this module to allow +monitoring the contention degree on the kernel mutex caused by the lock +operations. */ +UNIV_INLINE +void +lock_mutex_exit_kernel(void) +/*=========================*/ +{ + mutex_exit(&kernel_mutex); +} + +#ifdef notdefined + +/************************************************************************* +Gets the mutex protecting record locks for a page in the buffer pool. */ +UNIV_INLINE +mutex_t* +lock_rec_get_mutex( +/*===============*/ + byte* ptr) /* in: pointer to somewhere within a buffer frame */ +{ + return(buf_frame_get_lock_mutex(ptr)); +} + +/************************************************************************* +Reserves the mutex protecting record locks for a page in the buffer pool. */ +UNIV_INLINE +void +lock_rec_mutex_enter( +/*=================*/ + byte* ptr) /* in: pointer to somewhere within a buffer frame */ +{ + mutex_enter(lock_rec_get_mutex(ptr)); +} + +/************************************************************************* +Releases the mutex protecting record locks for a page in the buffer pool. */ +UNIV_INLINE +void +lock_rec_mutex_exit( +/*================*/ + byte* ptr) /* in: pointer to somewhere within a buffer frame */ +{ + mutex_exit(lock_rec_get_mutex(ptr)); +} + +/************************************************************************* +Checks if the caller owns the mutex to record locks of a page. Works only in +the debug version. */ +UNIV_INLINE +ibool +lock_rec_mutex_own( +/*===============*/ + /* out: TRUE if the current OS thread has reserved the + mutex */ + byte* ptr) /* in: pointer to somewhere within a buffer frame */ +{ + return(mutex_own(lock_rec_get_mutex(ptr))); +} + +/************************************************************************* +Gets the mutex protecting record locks on a given page address. */ + +mutex_t* +lock_rec_get_mutex_for_addr( +/*========================*/ + ulint space, /* in: space id */ + ulint page_no)/* in: page number */ +{ + return(hash_get_mutex(lock_sys->rec_hash, + lock_rec_fold(space, page_no))); +} + +/************************************************************************* +Checks if the caller owns the mutex to record locks of a page. Works only in +the debug version. */ +UNIV_INLINE +ibool +lock_rec_mutex_own_addr( +/*====================*/ + ulint space, /* in: space id */ + ulint page_no)/* in: page number */ +{ + return(mutex_own(lock_rec_get_mutex_for_addr(space, page_no))); +} + +/************************************************************************* +Reserves all the mutexes protecting record locks. */ +UNIV_INLINE +void +lock_rec_mutex_enter_all(void) +/*==========================*/ +{ + hash_table_t* table; + ulint n_mutexes; + ulint i; + + table = lock_sys->rec_hash; + + n_mutexes = table->n_mutexes; + + for (i = 0; i < n_mutexes; i++) { + + mutex_enter(hash_get_nth_mutex(table, i)); + } +} + +/************************************************************************* +Releases all the mutexes protecting record locks. */ +UNIV_INLINE +void +lock_rec_mutex_exit_all(void) +/*=========================*/ +{ + hash_table_t* table; + ulint n_mutexes; + ulint i; + + table = lock_sys->rec_hash; + + n_mutexes = table->n_mutexes; + + for (i = 0; i < n_mutexes; i++) { + + mutex_exit(hash_get_nth_mutex(table, i)); + } +} + +/************************************************************************* +Checks that the current OS thread owns all the mutexes protecting record +locks. */ +UNIV_INLINE +ibool +lock_rec_mutex_own_all(void) +/*========================*/ + /* out: TRUE if owns all */ +{ + hash_table_t* table; + ulint n_mutexes; + ibool owns_yes = TRUE; + ulint i; + + table = lock_sys->rec_hash; + + n_mutexes = table->n_mutexes; + + for (i = 0; i < n_mutexes; i++) { + if (!mutex_own(hash_get_nth_mutex(table, i))) { + + owns_yes = FALSE; + } + } + + return(owns_yes); +} + +#endif + +/************************************************************************* +Checks that a record is seen in a consistent read. */ + +ibool +lock_clust_rec_cons_read_sees( +/*==========================*/ + /* out: TRUE if sees, or FALSE if an earlier + version of the record should be retrieved */ + rec_t* rec, /* in: user record which should be read or + passed over by a read cursor */ + dict_index_t* index, /* in: clustered index */ + read_view_t* view) /* in: consistent read view */ +{ + dulint trx_id; + + ut_ad(index->type & DICT_CLUSTERED); + ut_ad(page_rec_is_user_rec(rec)); + + trx_id = row_get_rec_trx_id(rec, index); + + if (read_view_sees_trx_id(view, trx_id)) { + + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************* +Checks that a non-clustered index record is seen in a consistent read. */ + +ulint +lock_sec_rec_cons_read_sees( +/*========================*/ + /* out: TRUE if certainly sees, or FALSE if an + earlier version of the clustered index record + might be needed: NOTE that a non-clustered + index page contains so little information on + its modifications that also in the case FALSE, + the present version of rec may be the right, + but we must check this from the clustered + index record */ + rec_t* rec, /* in: user record which should be read or + passed over by a read cursor */ + dict_index_t* index, /* in: non-clustered index */ + read_view_t* view) /* in: consistent read view */ +{ + dulint max_trx_id; + + ut_ad(!(index->type & DICT_CLUSTERED)); + ut_ad(page_rec_is_user_rec(rec)); + + if (recv_recovery_is_on()) { + + return(FALSE); + } + + max_trx_id = page_get_max_trx_id(buf_frame_align(rec)); + + if (ut_dulint_cmp(max_trx_id, view->up_limit_id) >= 0) { + + return(FALSE); + } + + return(TRUE); +} + +/************************************************************************* +Creates the lock system at database start. */ + +void +lock_sys_create( +/*============*/ + ulint n_cells) /* in: number of slots in lock hash table */ +{ + lock_sys = mem_alloc(sizeof(lock_sys_t)); + + lock_sys->rec_hash = hash_create(n_cells); + + /* hash_create_mutexes(lock_sys->rec_hash, 2, SYNC_REC_LOCK); */ +} + +/************************************************************************* +Gets the mode of a lock. */ +UNIV_INLINE +ulint +lock_get_mode( +/*==========*/ + /* out: mode */ + lock_t* lock) /* in: lock */ +{ + ut_ad(lock); + + return(lock->type_mode & LOCK_MODE_MASK); +} + +/************************************************************************* +Gets the type of a lock. */ +UNIV_INLINE +ulint +lock_get_type( +/*==========*/ + /* out: LOCK_TABLE or LOCK_RECa */ + lock_t* lock) /* in: lock */ +{ + ut_ad(lock); + + return(lock->type_mode & LOCK_TYPE_MASK); +} + +/************************************************************************* +Gets the wait flag of a lock. */ +UNIV_INLINE +ibool +lock_get_wait( +/*==========*/ + /* out: TRUE if waiting */ + lock_t* lock) /* in: lock */ +{ + ut_ad(lock); + + if (lock->type_mode & LOCK_WAIT) { + + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************* +Sets the wait flag of a lock and the back pointer in trx to lock. */ +UNIV_INLINE +void +lock_set_lock_and_trx_wait( +/*=======================*/ + lock_t* lock, /* in: lock */ + trx_t* trx) /* in: trx */ +{ + ut_ad(lock); + ut_ad(trx->wait_lock == NULL); + + trx->wait_lock = lock; + lock->type_mode = lock->type_mode | LOCK_WAIT; +} + +/************************************************************************** +The back pointer to a waiting lock request in the transaction is set to NULL +and the wait bit in lock type_mode is reset. */ +UNIV_INLINE +void +lock_reset_lock_and_trx_wait( +/*=========================*/ + lock_t* lock) /* in: record lock */ +{ + ut_ad((lock->trx)->wait_lock == lock); + ut_ad(lock_get_wait(lock)); + + /* Reset the back pointer in trx to this waiting lock request */ + + (lock->trx)->wait_lock = NULL; + lock->type_mode = lock->type_mode & ~LOCK_WAIT; +} + +/************************************************************************* +Gets the gap flag of a record lock. */ +UNIV_INLINE +ibool +lock_rec_get_gap( +/*=============*/ + /* out: TRUE if gap flag set */ + lock_t* lock) /* in: record lock */ +{ + ut_ad(lock); + ut_ad(lock_get_type(lock) == LOCK_REC); + + if (lock->type_mode & LOCK_GAP) { + + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************* +Sets the gap flag of a record lock. */ +UNIV_INLINE +void +lock_rec_set_gap( +/*=============*/ + lock_t* lock, /* in: record lock */ + ibool val) /* in: value to set: TRUE or FALSE */ +{ + ut_ad(lock); + ut_ad((val == TRUE) || (val == FALSE)); + ut_ad(lock_get_type(lock) == LOCK_REC); + + if (val) { + lock->type_mode = lock->type_mode | LOCK_GAP; + } else { + lock->type_mode = lock->type_mode & ~LOCK_GAP; + } +} + +/************************************************************************* +Calculates if lock mode 1 is stronger or equal to lock mode 2. */ +UNIV_INLINE +ibool +lock_mode_stronger_or_eq( +/*=====================*/ + /* out: TRUE if mode1 stronger or equal to mode2 */ + ulint mode1, /* in: lock mode */ + ulint mode2) /* in: lock mode */ +{ + ut_ad((mode1 == LOCK_X) || (mode1 == LOCK_S) || (mode1 == LOCK_IX) + || (mode1 == LOCK_IS)); + ut_ad((mode2 == LOCK_X) || (mode2 == LOCK_S) || (mode2 == LOCK_IX) + || (mode2 == LOCK_IS)); + if (mode1 == LOCK_X) { + + return(TRUE); + + } else if ((mode1 == LOCK_S) + && ((mode2 == LOCK_S) || (mode2 == LOCK_IS))) { + return(TRUE); + + } else if ((mode1 == LOCK_IS) && (mode2 == LOCK_IS)) { + + return(TRUE); + + } else if ((mode1 == LOCK_IX) && ((mode2 == LOCK_IX) + || (mode2 == LOCK_IS))) { + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************* +Calculates if lock mode 1 is compatible with lock mode 2. */ +UNIV_INLINE +ibool +lock_mode_compatible( +/*=================*/ + /* out: TRUE if mode1 compatible with mode2 */ + ulint mode1, /* in: lock mode */ + ulint mode2) /* in: lock mode */ +{ + ut_ad((mode1 == LOCK_X) || (mode1 == LOCK_S) || (mode1 == LOCK_IX) + || (mode1 == LOCK_IS)); + ut_ad((mode2 == LOCK_X) || (mode2 == LOCK_S) || (mode2 == LOCK_IX) + || (mode2 == LOCK_IS)); + + if ((mode1 == LOCK_S) && ((mode2 == LOCK_IS) || (mode2 == LOCK_S))) { + + return(TRUE); + + } else if (mode1 == LOCK_X) { + + return(FALSE); + + } else if ((mode1 == LOCK_IS) && ((mode2 == LOCK_IS) + || (mode2 == LOCK_IX) + || (mode2 == LOCK_S))) { + return(TRUE); + + } else if ((mode1 == LOCK_IX) && ((mode2 == LOCK_IS) + || (mode2 == LOCK_IX))) { + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************* +Returns LOCK_X if mode is LOCK_S, and vice versa. */ +UNIV_INLINE +ulint +lock_get_confl_mode( +/*================*/ + /* out: conflicting basic lock mode */ + ulint mode) /* in: LOCK_S or LOCK_X */ +{ + ut_ad((mode == LOCK_X) || (mode == LOCK_S)); + + if (mode == LOCK_S) { + + return(LOCK_X); + } + + return(LOCK_S); +} + +/************************************************************************* +Checks if a lock request lock1 has to wait for request lock2. NOTE that we, +for simplicity, ignore the gap bits in locks, and treat gap type lock +requests like non-gap lock requests. */ +UNIV_INLINE +ibool +lock_has_to_wait( +/*=============*/ + /* out: TRUE if lock1 has to wait lock2 to be removed */ + lock_t* lock1, /* in: waiting record lock */ + lock_t* lock2) /* in: another lock; NOTE that it is assumed that this + has a lock bit set on the same record as in lock1 */ +{ + if ((lock1->trx != lock2->trx) + && !lock_mode_compatible(lock_get_mode(lock1), + lock_get_mode(lock2))) { + return(TRUE); + } + + return(FALSE); +} + +/*============== RECORD LOCK BASIC FUNCTIONS ============================*/ + +/************************************************************************* +Gets the number of bits in a record lock bitmap. */ +UNIV_INLINE +ulint +lock_rec_get_n_bits( +/*================*/ + /* out: number of bits */ + lock_t* lock) /* in: record lock */ +{ + return(lock->un_member.rec_lock.n_bits); +} + +/************************************************************************* +Gets the nth bit of a record lock. */ +UNIV_INLINE +ibool +lock_rec_get_nth_bit( +/*=================*/ + /* out: TRUE if bit set */ + lock_t* lock, /* in: record lock */ + ulint i) /* in: index of the bit */ +{ + ulint byte_index; + ulint bit_index; + ulint b; + + ut_ad(lock); + ut_ad(lock_get_type(lock) == LOCK_REC); + + if (i >= lock->un_member.rec_lock.n_bits) { + + return(FALSE); + } + + byte_index = i / 8; + bit_index = i % 8; + + b = (ulint)*((byte*)lock + sizeof(lock_t) + byte_index); + + return(ut_bit_get_nth(b, bit_index)); +} + +/************************************************************************** +Sets the nth bit of a record lock to TRUE. */ +UNIV_INLINE +void +lock_rec_set_nth_bit( +/*==================*/ + lock_t* lock, /* in: record lock */ + ulint i) /* in: index of the bit */ +{ + ulint byte_index; + ulint bit_index; + byte* ptr; + ulint b; + + ut_ad(lock); + ut_ad(lock_get_type(lock) == LOCK_REC); + ut_ad(i < lock->un_member.rec_lock.n_bits); + + byte_index = i / 8; + bit_index = i % 8; + + ptr = (byte*)lock + sizeof(lock_t) + byte_index; + + b = (ulint)*ptr; + + b = ut_bit_set_nth(b, bit_index, TRUE); + + *ptr = (byte)b; +} + +/************************************************************************** +Looks for a set bit in a record lock bitmap. Returns ULINT_UNDEFINED, +if none found. */ +static +ulint +lock_rec_find_set_bit( +/*==================*/ + /* out: bit index == heap number of the record, or + ULINT_UNDEFINED if none found */ + lock_t* lock) /* in: record lock with at least one bit set */ +{ + ulint i; + + for (i = 0; i < lock_rec_get_n_bits(lock); i++) { + + if (lock_rec_get_nth_bit(lock, i)) { + + return(i); + } + } + + return(ULINT_UNDEFINED); +} + +/************************************************************************** +Resets the nth bit of a record lock. */ +UNIV_INLINE +void +lock_rec_reset_nth_bit( +/*===================*/ + lock_t* lock, /* in: record lock */ + ulint i) /* in: index of the bit which must be set to TRUE + when this function is called */ +{ + ulint byte_index; + ulint bit_index; + byte* ptr; + ulint b; + + ut_ad(lock); + ut_ad(lock_get_type(lock) == LOCK_REC); + ut_ad(i < lock->un_member.rec_lock.n_bits); + + byte_index = i / 8; + bit_index = i % 8; + + ptr = (byte*)lock + sizeof(lock_t) + byte_index; + + b = (ulint)*ptr; + + b = ut_bit_set_nth(b, bit_index, FALSE); + + *ptr = (byte)b; +} + +/************************************************************************* +Gets the first or next record lock on a page. */ +UNIV_INLINE +lock_t* +lock_rec_get_next_on_page( +/*======================*/ + /* out: next lock, NULL if none exists */ + lock_t* lock) /* in: a record lock */ +{ + ulint space; + ulint page_no; + + ut_ad(mutex_own(&kernel_mutex)); + + space = lock->un_member.rec_lock.space; + page_no = lock->un_member.rec_lock.page_no; + + for (;;) { + lock = HASH_GET_NEXT(hash, lock); + + if (!lock) { + + break; + } + + if ((lock->un_member.rec_lock.space == space) + && (lock->un_member.rec_lock.page_no == page_no)) { + + break; + } + } + + return(lock); +} + +/************************************************************************* +Gets the first record lock on a page, where the page is identified by its +file address. */ +UNIV_INLINE +lock_t* +lock_rec_get_first_on_page_addr( +/*============================*/ + /* out: first lock, NULL if none exists */ + ulint space, /* in: space */ + ulint page_no)/* in: page number */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + + lock = HASH_GET_FIRST(lock_sys->rec_hash, + lock_rec_hash(space, page_no)); + while (lock) { + if ((lock->un_member.rec_lock.space == space) + && (lock->un_member.rec_lock.page_no == page_no)) { + + break; + } + + lock = HASH_GET_NEXT(hash, lock); + } + + return(lock); +} + +/************************************************************************* +Returns TRUE if there are explicit record locks on a page. */ + +ibool +lock_rec_expl_exist_on_page( +/*========================*/ + /* out: TRUE if there are explicit record locks on + the page */ + ulint space, /* in: space id */ + ulint page_no)/* in: page number */ +{ + ibool ret; + + mutex_enter(&kernel_mutex); + + if (lock_rec_get_first_on_page_addr(space, page_no)) { + ret = TRUE; + } else { + ret = FALSE; + } + + mutex_exit(&kernel_mutex); + + return(ret); +} + +/************************************************************************* +Gets the first record lock on a page, where the page is identified by a +pointer to it. */ +UNIV_INLINE +lock_t* +lock_rec_get_first_on_page( +/*=======================*/ + /* out: first lock, NULL if none exists */ + byte* ptr) /* in: pointer to somewhere on the page */ +{ + ulint hash; + lock_t* lock; + ulint space; + ulint page_no; + + ut_ad(mutex_own(&kernel_mutex)); + + hash = buf_frame_get_lock_hash_val(ptr); + + lock = HASH_GET_FIRST(lock_sys->rec_hash, hash); + + while (lock) { + space = buf_frame_get_space_id(ptr); + page_no = buf_frame_get_page_no(ptr); + + if ((lock->un_member.rec_lock.space == space) + && (lock->un_member.rec_lock.page_no == page_no)) { + + break; + } + + lock = HASH_GET_NEXT(hash, lock); + } + + return(lock); +} + +/************************************************************************* +Gets the next explicit lock request on a record. */ +UNIV_INLINE +lock_t* +lock_rec_get_next( +/*==============*/ + /* out: next lock, NULL if none exists */ + rec_t* rec, /* in: record on a page */ + lock_t* lock) /* in: lock */ +{ + ut_ad(mutex_own(&kernel_mutex)); + + for (;;) { + lock = lock_rec_get_next_on_page(lock); + + if (lock == NULL) { + + return(NULL); + } + + if (lock_rec_get_nth_bit(lock, rec_get_heap_no(rec))) { + + return(lock); + } + } +} + +/************************************************************************* +Gets the first explicit lock request on a record. */ +UNIV_INLINE +lock_t* +lock_rec_get_first( +/*===============*/ + /* out: first lock, NULL if none exists */ + rec_t* rec) /* in: record on a page */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + + lock = lock_rec_get_first_on_page(rec); + + while (lock) { + if (lock_rec_get_nth_bit(lock, rec_get_heap_no(rec))) { + + break; + } + + lock = lock_rec_get_next_on_page(lock); + } + + return(lock); +} + +/************************************************************************* +Resets the record lock bitmap to zero. NOTE: does not touch the wait_lock +pointer in the transaction! This function is used in lock object creation +and resetting. */ +static +void +lock_rec_bitmap_reset( +/*==================*/ + lock_t* lock) /* in: record lock */ +{ + byte* ptr; + ulint n_bytes; + ulint i; + + /* Reset to zero the bitmap which resides immediately after the lock + struct */ + + ptr = (byte*)lock + sizeof(lock_t); + + n_bytes = lock_rec_get_n_bits(lock) / 8; + + ut_ad((lock_rec_get_n_bits(lock) % 8) == 0); + + for (i = 0; i < n_bytes; i++) { + + *ptr = 0; + ptr++; + } +} + +/************************************************************************* +Copies a record lock to heap. */ +static +lock_t* +lock_rec_copy( +/*==========*/ + /* out: copy of lock */ + lock_t* lock, /* in: record lock */ + mem_heap_t* heap) /* in: memory heap */ +{ + lock_t* dupl_lock; + ulint size; + + size = sizeof(lock_t) + lock_rec_get_n_bits(lock) / 8; + + dupl_lock = mem_heap_alloc(heap, size); + + ut_memcpy(dupl_lock, lock, size); + + return(dupl_lock); +} + +/************************************************************************* +Gets the previous record lock set on a record. */ +static +lock_t* +lock_rec_get_prev( +/*==============*/ + /* out: previous lock on the same record, NULL if + none exists */ + lock_t* in_lock,/* in: record lock */ + ulint heap_no)/* in: heap number of the record */ +{ + lock_t* lock; + ulint space; + ulint page_no; + lock_t* found_lock = NULL; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(lock_get_type(in_lock) == LOCK_REC); + + space = in_lock->un_member.rec_lock.space; + page_no = in_lock->un_member.rec_lock.page_no; + + lock = lock_rec_get_first_on_page_addr(space, page_no); + + for (;;) { + ut_ad(lock); + + if (lock == in_lock) { + + return(found_lock); + } + + if (lock_rec_get_nth_bit(lock, heap_no)) { + + found_lock = lock; + } + + lock = lock_rec_get_next_on_page(lock); + } +} + +/*============= FUNCTIONS FOR ANALYZING TABLE LOCK QUEUE ================*/ + +/************************************************************************* +Checks if a transaction has the specified table lock, or stronger. */ +UNIV_INLINE +lock_t* +lock_table_has( +/*===========*/ + /* out: lock or NULL */ + trx_t* trx, /* in: transaction */ + dict_table_t* table, /* in: table */ + ulint mode) /* in: lock mode */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + + /* Look for stronger locks the same trx already has on the table */ + + lock = UT_LIST_GET_LAST(table->locks); + + while (lock != NULL) { + + if ((lock->trx == trx) + && (lock_mode_stronger_or_eq(lock_get_mode(lock), mode))) { + + /* The same trx already has locked the table in + a mode stronger or equal to the mode given */ + + ut_ad(!lock_get_wait(lock)); + + return(lock); + } + + lock = UT_LIST_GET_PREV(un_member.tab_lock.locks, lock); + } + + return(NULL); +} + +/*============= FUNCTIONS FOR ANALYZING RECORD LOCK QUEUE ================*/ + +/************************************************************************* +Checks if a transaction has a GRANTED explicit non-gap lock on rec, stronger +or equal to mode. */ +UNIV_INLINE +lock_t* +lock_rec_has_expl( +/*==============*/ + /* out: lock or NULL */ + ulint mode, /* in: lock mode */ + rec_t* rec, /* in: record */ + trx_t* trx) /* in: transaction */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad((mode == LOCK_X) || (mode == LOCK_S)); + + lock = lock_rec_get_first(rec); + + while (lock) { + if ((lock->trx == trx) + && lock_mode_stronger_or_eq(lock_get_mode(lock), mode) + && !lock_get_wait(lock) + && !(lock_rec_get_gap(lock) + || page_rec_is_supremum(rec))) { + return(lock); + } + + lock = lock_rec_get_next(rec, lock); + } + + return(NULL); +} + +/************************************************************************* +Checks if some other transaction has an explicit lock request stronger or +equal to mode on rec or gap, waiting or granted, in the lock queue. */ +UNIV_INLINE +lock_t* +lock_rec_other_has_expl_req( +/*========================*/ + /* out: lock or NULL */ + ulint mode, /* in: lock mode */ + ulint gap, /* in: LOCK_GAP if also gap locks are taken + into account, or 0 if not */ + ulint wait, /* in: LOCK_WAIT if also waiting locks are + taken into account, or 0 if not */ + rec_t* rec, /* in: record to look at */ + trx_t* trx) /* in: transaction, or NULL if requests + by any transaction are wanted */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad((mode == LOCK_X) || (mode == LOCK_S)); + + lock = lock_rec_get_first(rec); + + while (lock) { + if ((lock->trx != trx) + && (gap || + !(lock_rec_get_gap(lock) || page_rec_is_supremum(rec))) + && (wait || !lock_get_wait(lock)) + && lock_mode_stronger_or_eq(lock_get_mode(lock), mode)) { + + return(lock); + } + + lock = lock_rec_get_next(rec, lock); + } + + return(NULL); +} + +/************************************************************************* +Looks for a suitable type record lock struct by the same trx on the same page. +This can be used to save space when a new record lock should be set on a page: +no new struct is needed, if a suitable old is found. */ +UNIV_INLINE +lock_t* +lock_rec_find_similar_on_page( +/*==========================*/ + /* out: lock or NULL */ + ulint type_mode, /* in: lock type_mode field */ + rec_t* rec, /* in: record */ + trx_t* trx) /* in: transaction */ +{ + lock_t* lock; + ulint heap_no; + + ut_ad(mutex_own(&kernel_mutex)); + + heap_no = rec_get_heap_no(rec); + + lock = lock_rec_get_first_on_page(rec); + + while (lock != NULL) { + if ((lock->trx == trx) + && (lock->type_mode == type_mode) + && (lock_rec_get_n_bits(lock) > heap_no)) { + + return(lock); + } + + lock = lock_rec_get_next_on_page(lock); + } + + return(NULL); +} + +/************************************************************************* +Checks if some transaction has an implicit x-lock on a record in a secondary +index. */ + +trx_t* +lock_sec_rec_some_has_impl_off_kernel( +/*==================================*/ + /* out: transaction which has the x-lock, or + NULL */ + rec_t* rec, /* in: user record */ + dict_index_t* index) /* in: secondary index */ +{ + page_t* page; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(!(index->type & DICT_CLUSTERED)); + ut_ad(page_rec_is_user_rec(rec)); + + page = buf_frame_align(rec); + + /* Some transaction may have an implicit x-lock on the record only + if the max trx id for the page >= min trx id for the trx list, or + database recovery is running. We do not write the changes of a page + max trx id to the log, and therefore during recovery, this value + for a page may be incorrect. */ + + if (!(ut_dulint_cmp(page_get_max_trx_id(page), + trx_list_get_min_trx_id()) >= 0) + && !recv_recovery_is_on()) { + + return(NULL); + } + + /* Ok, in this case it is possible that some transaction has an + implicit x-lock. We have to look in the clustered index. */ + + return(row_vers_impl_x_locked_off_kernel(rec, index)); +} + +/*============== RECORD LOCK CREATION AND QUEUE MANAGEMENT =============*/ + +/************************************************************************* +Creates a new record lock and inserts it to the lock queue. Does NOT check +for deadlocks or lock compatibility! */ +static +lock_t* +lock_rec_create( +/*============*/ + /* out: created lock, NULL if out of memory */ + ulint type_mode,/* in: lock mode and wait flag, type is + ignored and replaced by LOCK_REC */ + rec_t* rec, /* in: record on page */ + dict_index_t* index, /* in: index of record */ + trx_t* trx) /* in: transaction */ +{ + page_t* page; + lock_t* lock; + ulint page_no; + ulint heap_no; + ulint space; + ulint n_bits; + ulint n_bytes; + + ut_ad(mutex_own(&kernel_mutex)); + + page = buf_frame_align(rec); + space = buf_frame_get_space_id(page); + page_no = buf_frame_get_page_no(page); + heap_no = rec_get_heap_no(rec); + + /* If rec is the supremum record, then we reset the gap bit, as + all locks on the supremum are automatically of the gap type, and + we try to avoid unnecessary memory consumption of a new record lock + struct for a gap type lock */ + + if (rec == page_get_supremum_rec(page)) { + + type_mode = type_mode & ~LOCK_GAP; + } + + /* Make lock bitmap bigger by a safety margin */ + n_bits = page_header_get_field(page, PAGE_N_HEAP) + + LOCK_PAGE_BITMAP_MARGIN; + n_bytes = 1 + n_bits / 8; + + lock = mem_heap_alloc(trx->lock_heap, sizeof(lock_t) + n_bytes); + + if (lock == NULL) { + + return(NULL); + } + + UT_LIST_ADD_LAST(trx_locks, trx->trx_locks, lock); + + lock->trx = trx; + + lock->type_mode = (type_mode & ~LOCK_TYPE_MASK) | LOCK_REC; + lock->index = index; + + lock->un_member.rec_lock.space = space; + lock->un_member.rec_lock.page_no = page_no; + lock->un_member.rec_lock.n_bits = n_bytes * 8; + + /* Reset to zero the bitmap which resides immediately after the + lock struct */ + + lock_rec_bitmap_reset(lock); + + /* Set the bit corresponding to rec */ + lock_rec_set_nth_bit(lock, heap_no); + + HASH_INSERT(lock_t, hash, lock_sys->rec_hash, + lock_rec_fold(space, page_no), lock); + if (type_mode & LOCK_WAIT) { + + lock_set_lock_and_trx_wait(lock, trx); + } + + return(lock); +} + +/************************************************************************* +Enqueues a waiting request for a lock which cannot be granted immediately. +Checks for deadlocks. */ +static +ulint +lock_rec_enqueue_waiting( +/*=====================*/ + /* out: DB_LOCK_WAIT, DB_DEADLOCK, or + DB_QUE_THR_SUSPENDED */ + ulint type_mode,/* in: lock mode this transaction is + requesting: LOCK_S or LOCK_X, ORed with + LOCK_GAP if a gap lock is requested */ + rec_t* rec, /* in: record */ + dict_index_t* index, /* in: index of record */ + que_thr_t* thr) /* in: query thread */ +{ + lock_t* lock; + trx_t* trx; + + ut_ad(mutex_own(&kernel_mutex)); + + /* Test if there already is some other reason to suspend thread: + we do not enqueue a lock request if the query thread should be + stopped anyway */ + + if (que_thr_stop(thr)) { + + return(DB_QUE_THR_SUSPENDED); + } + + trx = thr_get_trx(thr); + + /* Enqueue the lock request that will wait to be granted */ + lock = lock_rec_create(type_mode | LOCK_WAIT, rec, index, trx); + + /* Check if a deadlock occurs: if yes, remove the lock request and + return an error code */ + + if (lock_deadlock_occurs(lock, trx)) { + + lock_reset_lock_and_trx_wait(lock); + lock_rec_reset_nth_bit(lock, rec_get_heap_no(rec)); + + return(DB_DEADLOCK); + } + + trx->que_state = TRX_QUE_LOCK_WAIT; + + ut_a(que_thr_stop(thr)); + + if (lock_print_waits) { + printf("Lock wait for trx %lu in index %s\n", + ut_dulint_get_low(trx->id), index->name); + } + + return(DB_LOCK_WAIT); +} + +/************************************************************************* +Adds a record lock request in the record queue. The request is normally +added as the last in the queue, but if there are no waiting lock requests +on the record, and the request to be added is not a waiting request, we +can reuse a suitable record lock object already existing on the same page, +just setting the appropriate bit in its bitmap. This is a low-level function +which does NOT check for deadlocks or lock compatibility! */ +static +lock_t* +lock_rec_add_to_queue( +/*==================*/ + /* out: lock where the bit was set, NULL if out + of memory */ + ulint type_mode,/* in: lock mode, wait, and gap flags; type + is ignored and replaced by LOCK_REC */ + rec_t* rec, /* in: record on page */ + dict_index_t* index, /* in: index of record */ + trx_t* trx) /* in: transaction */ +{ + lock_t* lock; + lock_t* similar_lock = NULL; + ulint heap_no; + page_t* page; + ibool somebody_waits = FALSE; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad((type_mode & (LOCK_WAIT | LOCK_GAP)) + || ((type_mode & LOCK_MODE_MASK) != LOCK_S) + || !lock_rec_other_has_expl_req(LOCK_X, 0, LOCK_WAIT, rec, trx)); + ut_ad((type_mode & (LOCK_WAIT | LOCK_GAP)) + || ((type_mode & LOCK_MODE_MASK) != LOCK_X) + || !lock_rec_other_has_expl_req(LOCK_S, 0, LOCK_WAIT, rec, trx)); + + type_mode = type_mode | LOCK_REC; + + page = buf_frame_align(rec); + + /* If rec is the supremum record, then we can reset the gap bit, as + all locks on the supremum are automatically of the gap type, and we + try to avoid unnecessary memory consumption of a new record lock + struct for a gap type lock */ + + if (rec == page_get_supremum_rec(page)) { + + type_mode = type_mode & ~LOCK_GAP; + } + + /* Look for a waiting lock request on the same record, or for a + similar record lock on the same page */ + + heap_no = rec_get_heap_no(rec); + lock = lock_rec_get_first_on_page(rec); + + while (lock != NULL) { + if (lock_get_wait(lock) + && (lock_rec_get_nth_bit(lock, heap_no))) { + + somebody_waits = TRUE; + } + + lock = lock_rec_get_next_on_page(lock); + } + + similar_lock = lock_rec_find_similar_on_page(type_mode, rec, trx); + + if (similar_lock && !somebody_waits && !(type_mode & LOCK_WAIT)) { + + lock_rec_set_nth_bit(similar_lock, heap_no); + + return(similar_lock); + } + + return(lock_rec_create(type_mode, rec, index, trx)); +} + +/************************************************************************* +This is a fast routine for locking a record in the most common cases: +there are no explicit locks on the page, or there is just one lock, owned +by this transaction, and of the right type_mode. This is a low-level function +which does NOT look at implicit locks! Checks lock compatibility within +explicit locks. */ +UNIV_INLINE +ibool +lock_rec_lock_fast( +/*===============*/ + /* out: TRUE if locking succeeded */ + ibool impl, /* in: if TRUE, no lock is set if no wait + is necessary: we assume that the caller will + set an implicit lock */ + ulint mode, /* in: lock mode */ + rec_t* rec, /* in: record */ + dict_index_t* index, /* in: index of record */ + que_thr_t* thr) /* in: query thread */ +{ + lock_t* lock; + ulint heap_no; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad((mode == LOCK_X) || (mode == LOCK_S)); + + heap_no = rec_get_heap_no(rec); + + lock = lock_rec_get_first_on_page(rec); + + if (lock == NULL) { + if (!impl) { + lock_rec_create(mode, rec, index, thr_get_trx(thr)); + } + + return(TRUE); + } + + if (lock_rec_get_next_on_page(lock)) { + + return(FALSE); + } + + if ((lock->trx != thr_get_trx(thr)) + || (lock->type_mode != (mode | LOCK_REC)) + || (lock_rec_get_n_bits(lock) <= heap_no)) { + return(FALSE); + } + + if (!impl) { + lock_rec_set_nth_bit(lock, heap_no); + } + + return(TRUE); +} + +/************************************************************************* +This is the general, and slower, routine for locking a record. This is a +low-level function which does NOT look at implicit locks! Checks lock +compatibility within explicit locks. */ +static +ulint +lock_rec_lock_slow( +/*===============*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, or error + code */ + ibool impl, /* in: if TRUE, no lock is set if no wait is + necessary: we assume that the caller will set + an implicit lock */ + ulint mode, /* in: lock mode */ + rec_t* rec, /* in: record */ + dict_index_t* index, /* in: index of record */ + que_thr_t* thr) /* in: query thread */ +{ + ulint confl_mode; + trx_t* trx; + ulint err; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad((mode == LOCK_X) || (mode == LOCK_S)); + + trx = thr_get_trx(thr); + confl_mode = lock_get_confl_mode(mode); + + ut_ad((mode != LOCK_S) || lock_table_has(trx, index->table, + LOCK_IS)); + ut_ad((mode != LOCK_X) || lock_table_has(trx, index->table, + LOCK_IX)); + if (lock_rec_has_expl(mode, rec, trx)) { + /* The trx already has a strong enough lock on rec: do + nothing */ + + err = DB_SUCCESS; + } else if (lock_rec_other_has_expl_req(confl_mode, 0, LOCK_WAIT, rec, + trx)) { + /* If another transaction has a non-gap conflicting request in + the queue, as this transaction does not have a lock strong + enough already granted on the record, we have to wait. */ + + err = lock_rec_enqueue_waiting(mode, rec, index, thr); + } else { + if (!impl) { + /* Set the requested lock on the record */ + + lock_rec_add_to_queue(LOCK_REC | mode, rec, index, + trx); + } + + err = DB_SUCCESS; + } + + return(err); +} + +/************************************************************************* +Tries to lock the specified record in the mode requested. If not immediately +possible, enqueues a waiting lock request. This is a low-level function +which does NOT look at implicit locks! Checks lock compatibility within +explicit locks. */ + +ulint +lock_rec_lock( +/*==========*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, or error + code */ + ibool impl, /* in: if TRUE, no lock is set if no wait is + necessary: we assume that the caller will set + an implicit lock */ + ulint mode, /* in: lock mode */ + rec_t* rec, /* in: record */ + dict_index_t* index, /* in: index of record */ + que_thr_t* thr) /* in: query thread */ +{ + ulint err; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad((mode != LOCK_S) || lock_table_has(thr_get_trx(thr), + index->table, LOCK_IS)); + ut_ad((mode != LOCK_X) || lock_table_has(thr_get_trx(thr), + index->table, LOCK_IX)); + + if (lock_rec_lock_fast(impl, mode, rec, index, thr)) { + + /* We try a simplified and faster subroutine for the most + common cases */ + + err = DB_SUCCESS; + } else { + err = lock_rec_lock_slow(impl, mode, rec, index, thr); + } + + return(err); +} + +/************************************************************************* +Checks if a waiting record lock request still has to wait in a queue. +NOTE that we, for simplicity, ignore the gap bits in locks, and treat +gap type lock requests like non-gap lock requests. */ +static +ibool +lock_rec_has_to_wait_in_queue( +/*==========================*/ + /* out: TRUE if still has to wait */ + lock_t* wait_lock) /* in: waiting record lock */ +{ + lock_t* lock; + ulint space; + ulint page_no; + ulint heap_no; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(lock_get_wait(wait_lock)); + + space = wait_lock->un_member.rec_lock.space; + page_no = wait_lock->un_member.rec_lock.page_no; + heap_no = lock_rec_find_set_bit(wait_lock); + + lock = lock_rec_get_first_on_page_addr(space, page_no); + + while (lock != wait_lock) { + + if (lock_has_to_wait(wait_lock, lock) + && lock_rec_get_nth_bit(lock, heap_no)) { + + return(TRUE); + } + + lock = lock_rec_get_next_on_page(lock); + } + + return(FALSE); +} + +/***************************************************************** +Grants a lock to a waiting lock request and releases the waiting +transaction. */ + +void +lock_grant( +/*=======*/ + lock_t* lock) /* in: waiting lock request */ +{ + ut_ad(mutex_own(&kernel_mutex)); + + lock_reset_lock_and_trx_wait(lock); + + if (lock_print_waits) { + printf("Lock wait for trx %lu ends\n", + ut_dulint_get_low(lock->trx->id)); + } + + trx_end_lock_wait(lock->trx); +} + +/***************************************************************** +Cancels a waiting record lock request and releases the waiting transaction +that requested it. NOTE: does NOT check if waiting lock requests behind this +one can now be granted! */ + +void +lock_rec_cancel( +/*============*/ + lock_t* lock) /* in: waiting record lock request */ +{ + ut_ad(mutex_own(&kernel_mutex)); + + /* Reset the bit in lock bitmap */ + lock_rec_reset_nth_bit(lock, lock_rec_find_set_bit(lock)); + + /* Reset the wait flag and the back pointer to lock in trx */ + + lock_reset_lock_and_trx_wait(lock); + + /* The following function releases the trx from lock wait */ + + trx_end_lock_wait(lock->trx); +} + +/***************************************************************** +Removes a record lock request, waiting or granted, from the queue and +grants locks to other transactions in the queue if they now are entitled +to a lock. NOTE: all record locks contained in in_lock are removed. */ + +void +lock_rec_dequeue_from_page( +/*=======================*/ + lock_t* in_lock)/* in: record lock object: all record locks which + are contained in this lock object are removed; + transactions waiting behind will get their lock + requests granted, if they are now qualified to it */ +{ + ulint space; + ulint page_no; + lock_t* lock; + trx_t* trx; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(lock_get_type(in_lock) == LOCK_REC); + + trx = in_lock->trx; + + space = in_lock->un_member.rec_lock.space; + page_no = in_lock->un_member.rec_lock.page_no; + + HASH_DELETE(lock_t, hash, lock_sys->rec_hash, + lock_rec_fold(space, page_no), in_lock); + + UT_LIST_REMOVE(trx_locks, trx->trx_locks, in_lock); + + /* Check if waiting locks in the queue can now be granted: grant + locks if there are no conflicting locks ahead. */ + + lock = lock_rec_get_first_on_page_addr(space, page_no); + + while (lock != NULL) { + + if (lock_get_wait(lock) + && !lock_rec_has_to_wait_in_queue(lock)) { + + /* Grant the lock */ + lock_grant(lock); + } + + lock = lock_rec_get_next_on_page(lock); + } +} + +/***************************************************************** +Removes record lock objects set on an index page which is discarded. This +function does not move locks, or check for waiting locks, therefore the +lock bitmaps must already be reset when this function is called. */ +static +void +lock_rec_free_all_from_discard_page( +/*================================*/ + page_t* page) /* in: page to be discarded */ +{ + ulint space; + ulint page_no; + lock_t* lock; + lock_t* next_lock; + trx_t* trx; + + ut_ad(mutex_own(&kernel_mutex)); + + space = buf_frame_get_space_id(page); + page_no = buf_frame_get_page_no(page); + + lock = lock_rec_get_first_on_page_addr(space, page_no); + + while (lock != NULL) { + ut_ad(lock_rec_find_set_bit(lock) == ULINT_UNDEFINED); + ut_ad(!lock_get_wait(lock)); + + next_lock = lock_rec_get_next_on_page(lock); + + HASH_DELETE(lock_t, hash, lock_sys->rec_hash, + lock_rec_fold(space, page_no), lock); + trx = lock->trx; + + UT_LIST_REMOVE(trx_locks, trx->trx_locks, lock); + + lock = next_lock; + } +} + +/*============= RECORD LOCK MOVING AND INHERITING ===================*/ + +/***************************************************************** +Resets the lock bits for a single record. Releases transactions waiting for +lock requests here. */ + +void +lock_rec_reset_and_release_wait( +/*============================*/ + rec_t* rec) /* in: record whose locks bits should be reset */ +{ + lock_t* lock; + ulint heap_no; + + ut_ad(mutex_own(&kernel_mutex)); + + heap_no = rec_get_heap_no(rec); + + lock = lock_rec_get_first(rec); + + while (lock != NULL) { + + if (lock_get_wait(lock)) { + lock_rec_cancel(lock); + } else { + lock_rec_reset_nth_bit(lock, heap_no); + } + + lock = lock_rec_get_next(rec, lock); + } +} + +/***************************************************************** +Makes a record to inherit the locks of another record as gap type locks, but +does not reset the lock bits of the other record. Also waiting lock requests +on rec are inherited as GRANTED gap locks. */ + +void +lock_rec_inherit_to_gap( +/*====================*/ + rec_t* heir, /* in: record which inherits */ + rec_t* rec) /* in: record from which inherited; does NOT reset + the locks on this record */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + + lock = lock_rec_get_first(rec); + + while (lock != NULL) { + lock_rec_add_to_queue((lock->type_mode | LOCK_GAP) & ~LOCK_WAIT, + heir, lock->index, lock->trx); + lock = lock_rec_get_next(rec, lock); + } +} + +/***************************************************************** +Moves the locks of a record to another record and resets the lock bits of +the donating record. */ +static +void +lock_rec_move( +/*==========*/ + rec_t* receiver, /* in: record which gets locks; this record + must have no lock requests on it! */ + rec_t* donator) /* in: record which gives locks */ +{ + lock_t* lock; + ulint heap_no; + ulint type_mode; + + ut_ad(mutex_own(&kernel_mutex)); + + heap_no = rec_get_heap_no(donator); + + lock = lock_rec_get_first(donator); + + ut_ad(lock_rec_get_first(receiver) == NULL); + + while (lock != NULL) { + type_mode = lock->type_mode; + + lock_rec_reset_nth_bit(lock, heap_no); + + if (lock_get_wait(lock)) { + lock_reset_lock_and_trx_wait(lock); + } + + /* Note that we FIRST reset the bit, and then set the lock: + the function works also if donator == receiver */ + + lock_rec_add_to_queue(type_mode, receiver, lock->index, + lock->trx); + lock = lock_rec_get_next(donator, lock); + } + + ut_ad(lock_rec_get_first(donator) == NULL); +} + +/***************************************************************** +Updates the lock table when we have reorganized a page. NOTE: we copy +also the locks set on the infimum of the page; the infimum may carry +locks if an update of a record is occurring on the page, and its locks +were temporarily stored on the infimum. */ + +void +lock_move_reorganize_page( +/*======================*/ + page_t* page, /* in: old index page, now reorganized */ + page_t* old_page) /* in: copy of the old, not reorganized page */ +{ + lock_t* lock; + lock_t* old_lock; + page_cur_t cur1; + page_cur_t cur2; + ulint old_heap_no; + UT_LIST_BASE_NODE_T(lock_t) old_locks; + mem_heap_t* heap = NULL; + rec_t* sup; + + lock_mutex_enter_kernel(); + + lock = lock_rec_get_first_on_page(page); + + if (lock == NULL) { + lock_mutex_exit_kernel(); + + return; + } + + heap = mem_heap_create(256); + + /* Copy first all the locks on the page to heap and reset the + bitmaps in the original locks; chain the copies of the locks + using the trx_locks field in them. */ + + UT_LIST_INIT(old_locks); + + while (lock != NULL) { + + /* Make a copy of the lock */ + old_lock = lock_rec_copy(lock, heap); + + UT_LIST_ADD_LAST(trx_locks, old_locks, old_lock); + + /* Reset bitmap of lock */ + lock_rec_bitmap_reset(lock); + + if (lock_get_wait(lock)) { + lock_reset_lock_and_trx_wait(lock); + } + + lock = lock_rec_get_next_on_page(lock); + } + + sup = page_get_supremum_rec(page); + + lock = UT_LIST_GET_FIRST(old_locks); + + while (lock) { + /* NOTE: we copy also the locks set on the infimum and + supremum of the page; the infimum may carry locks if an + update of a record is occurring on the page, and its locks + were temporarily stored on the infimum */ + + page_cur_set_before_first(page, &cur1); + page_cur_set_before_first(old_page, &cur2); + + /* Set locks according to old locks */ + for (;;) { + ut_ad(0 == ut_memcmp(page_cur_get_rec(&cur1), + page_cur_get_rec(&cur2), + rec_get_data_size( + page_cur_get_rec(&cur2)))); + + old_heap_no = rec_get_heap_no(page_cur_get_rec(&cur2)); + + if (lock_rec_get_nth_bit(lock, old_heap_no)) { + + /* NOTE that the old lock bitmap could be too + small for the new heap number! */ + + lock_rec_add_to_queue(lock->type_mode, + page_cur_get_rec(&cur1), + lock->index, lock->trx); + + /* if ((page_cur_get_rec(&cur1) == sup) + && lock_get_wait(lock)) { + printf( + "---\n--\n!!!Lock reorg: supr type %lu\n", + lock->type_mode); + } */ + } + + if (page_cur_get_rec(&cur1) == sup) { + + break; + } + + page_cur_move_to_next(&cur1); + page_cur_move_to_next(&cur2); + } + + /* Remember that we chained old locks on the trx_locks field: */ + + lock = UT_LIST_GET_NEXT(trx_locks, lock); + } + + lock_mutex_exit_kernel(); + + mem_heap_free(heap); + +/* ut_ad(lock_rec_validate_page(buf_frame_get_space_id(page), + buf_frame_get_page_no(page))); */ +} + +/***************************************************************** +Moves the explicit locks on user records to another page if a record +list end is moved to another page. */ + +void +lock_move_rec_list_end( +/*===================*/ + page_t* new_page, /* in: index page to move to */ + page_t* page, /* in: index page */ + rec_t* rec) /* in: record on page: this is the + first record moved */ +{ + lock_t* lock; + page_cur_t cur1; + page_cur_t cur2; + ulint heap_no; + rec_t* sup; + ulint type_mode; + + lock_mutex_enter_kernel(); + + /* Note: when we move locks from record to record, waiting locks + and possible granted gap type locks behind them are enqueued in + the original order, because new elements are inserted to a hash + table to the end of the hash chain, and lock_rec_add_to_queue + does not reuse locks if there are waiters in the queue. */ + + sup = page_get_supremum_rec(page); + + lock = lock_rec_get_first_on_page(page); + + while (lock != NULL) { + + page_cur_position(rec, &cur1); + + if (page_cur_is_before_first(&cur1)) { + page_cur_move_to_next(&cur1); + } + + page_cur_set_before_first(new_page, &cur2); + page_cur_move_to_next(&cur2); + + /* Copy lock requests on user records to new page and + reset the lock bits on the old */ + + while (page_cur_get_rec(&cur1) != sup) { + + ut_ad(0 == ut_memcmp(page_cur_get_rec(&cur1), + page_cur_get_rec(&cur2), + rec_get_data_size( + page_cur_get_rec(&cur2)))); + + heap_no = rec_get_heap_no(page_cur_get_rec(&cur1)); + + if (lock_rec_get_nth_bit(lock, heap_no)) { + type_mode = lock->type_mode; + + lock_rec_reset_nth_bit(lock, heap_no); + + if (lock_get_wait(lock)) { + lock_reset_lock_and_trx_wait(lock); + } + + lock_rec_add_to_queue(type_mode, + page_cur_get_rec(&cur2), + lock->index, lock->trx); + } + + page_cur_move_to_next(&cur1); + page_cur_move_to_next(&cur2); + } + + lock = lock_rec_get_next_on_page(lock); + } + + lock_mutex_exit_kernel(); + +/* ut_ad(lock_rec_validate_page(buf_frame_get_space_id(page), + buf_frame_get_page_no(page))); + ut_ad(lock_rec_validate_page(buf_frame_get_space_id(new_page), + buf_frame_get_page_no(new_page))); */ +} + +/***************************************************************** +Moves the explicit locks on user records to another page if a record +list start is moved to another page. */ + +void +lock_move_rec_list_start( +/*=====================*/ + page_t* new_page, /* in: index page to move to */ + page_t* page, /* in: index page */ + rec_t* rec, /* in: record on page: this is the + first record NOT copied */ + rec_t* old_end) /* in: old previous-to-last record on + new_page before the records were copied */ +{ + lock_t* lock; + page_cur_t cur1; + page_cur_t cur2; + ulint heap_no; + ulint type_mode; + + ut_ad(new_page); + + lock_mutex_enter_kernel(); + + lock = lock_rec_get_first_on_page(page); + + while (lock != NULL) { + + page_cur_set_before_first(page, &cur1); + page_cur_move_to_next(&cur1); + + page_cur_position(old_end, &cur2); + page_cur_move_to_next(&cur2); + + /* Copy lock requests on user records to new page and + reset the lock bits on the old */ + + while (page_cur_get_rec(&cur1) != rec) { + + ut_ad(0 == ut_memcmp(page_cur_get_rec(&cur1), + page_cur_get_rec(&cur2), + rec_get_data_size( + page_cur_get_rec(&cur2)))); + + heap_no = rec_get_heap_no(page_cur_get_rec(&cur1)); + + if (lock_rec_get_nth_bit(lock, heap_no)) { + type_mode = lock->type_mode; + + lock_rec_reset_nth_bit(lock, heap_no); + + if (lock_get_wait(lock)) { + lock_reset_lock_and_trx_wait(lock); + } + + lock_rec_add_to_queue(type_mode, + page_cur_get_rec(&cur2), + lock->index, lock->trx); + } + + page_cur_move_to_next(&cur1); + page_cur_move_to_next(&cur2); + } + + lock = lock_rec_get_next_on_page(lock); + } + + lock_mutex_exit_kernel(); + +/* ut_ad(lock_rec_validate_page(buf_frame_get_space_id(page), + buf_frame_get_page_no(page))); + ut_ad(lock_rec_validate_page(buf_frame_get_space_id(new_page), + buf_frame_get_page_no(new_page))); */ +} + +/***************************************************************** +Updates the lock table when a page is split to the right. */ + +void +lock_update_split_right( +/*====================*/ + page_t* right_page, /* in: right page */ + page_t* left_page) /* in: left page */ +{ + lock_mutex_enter_kernel(); + + /* Move the locks on the supremum of the left page to the supremum + of the right page */ + + lock_rec_move(page_get_supremum_rec(right_page), + page_get_supremum_rec(left_page)); + + /* Inherit the locks to the supremum of left page from the successor + of the infimum on right page */ + + lock_rec_inherit_to_gap(page_get_supremum_rec(left_page), + page_rec_get_next(page_get_infimum_rec(right_page))); + + lock_mutex_exit_kernel(); +} + +/***************************************************************** +Updates the lock table when a page is merged to the right. */ + +void +lock_update_merge_right( +/*====================*/ + rec_t* orig_succ, /* in: original successor of infimum + on the right page before merge */ + page_t* left_page) /* in: merged index page which will be + discarded */ +{ + lock_mutex_enter_kernel(); + + /* Inherit the locks from the supremum of the left page to the + original successor of infimum on the right page, to which the left + page was merged */ + + lock_rec_inherit_to_gap(orig_succ, page_get_supremum_rec(left_page)); + + /* Reset the locks on the supremum of the left page, releasing + waiting transactions */ + + lock_rec_reset_and_release_wait(page_get_supremum_rec(left_page)); + + lock_rec_free_all_from_discard_page(left_page); + + lock_mutex_exit_kernel(); +} + +/***************************************************************** +Updates the lock table when the root page is copied to another in +btr_root_raise_and_insert. Note that we leave lock structs on the +root page, even though they do not make sense on other than leaf +pages: the reason is that in a pessimistic update the infimum record +of the root page will act as a dummy carrier of the locks of the record +to be updated. */ + +void +lock_update_root_raise( +/*===================*/ + page_t* new_page, /* in: index page to which copied */ + page_t* root) /* in: root page */ +{ + lock_mutex_enter_kernel(); + + /* Move the locks on the supremum of the root to the supremum + of new_page */ + + lock_rec_move(page_get_supremum_rec(new_page), + page_get_supremum_rec(root)); + lock_mutex_exit_kernel(); +} + +/***************************************************************** +Updates the lock table when a page is copied to another and the original page +is removed from the chain of leaf pages, except if page is the root! */ + +void +lock_update_copy_and_discard( +/*=========================*/ + page_t* new_page, /* in: index page to which copied */ + page_t* page) /* in: index page; NOT the root! */ +{ + lock_mutex_enter_kernel(); + + /* Move the locks on the supremum of the old page to the supremum + of new_page */ + + lock_rec_move(page_get_supremum_rec(new_page), + page_get_supremum_rec(page)); + lock_rec_free_all_from_discard_page(page); + + lock_mutex_exit_kernel(); +} + +/***************************************************************** +Updates the lock table when a page is split to the left. */ + +void +lock_update_split_left( +/*===================*/ + page_t* right_page, /* in: right page */ + page_t* left_page) /* in: left page */ +{ + lock_mutex_enter_kernel(); + + /* Inherit the locks to the supremum of the left page from the + successor of the infimum on the right page */ + + lock_rec_inherit_to_gap(page_get_supremum_rec(left_page), + page_rec_get_next(page_get_infimum_rec(right_page))); + + lock_mutex_exit_kernel(); +} + +/***************************************************************** +Updates the lock table when a page is merged to the left. */ + +void +lock_update_merge_left( +/*===================*/ + page_t* left_page, /* in: left page to which merged */ + rec_t* orig_pred, /* in: original predecessor of supremum + on the left page before merge */ + page_t* right_page) /* in: merged index page which will be + discarded */ +{ + lock_mutex_enter_kernel(); + + if (page_rec_get_next(orig_pred) != page_get_supremum_rec(left_page)) { + + /* Inherit the locks on the supremum of the left page to the + first record which was moved from the right page */ + + lock_rec_inherit_to_gap(page_rec_get_next(orig_pred), + page_get_supremum_rec(left_page)); + + /* Reset the locks on the supremum of the left page, + releasing waiting transactions */ + + lock_rec_reset_and_release_wait(page_get_supremum_rec( + left_page)); + } + + /* Move the locks from the supremum of right page to the supremum + of the left page */ + + lock_rec_move(page_get_supremum_rec(left_page), + page_get_supremum_rec(right_page)); + + lock_rec_free_all_from_discard_page(right_page); + + lock_mutex_exit_kernel(); +} + +/***************************************************************** +Resets the original locks on heir and replaces them with gap type locks +inherited from rec. */ + +void +lock_rec_reset_and_inherit_gap_locks( +/*=================================*/ + rec_t* heir, /* in: heir record */ + rec_t* rec) /* in: record */ +{ + mutex_enter(&kernel_mutex); + + lock_rec_reset_and_release_wait(heir); + + lock_rec_inherit_to_gap(heir, rec); + + mutex_exit(&kernel_mutex); +} + +/***************************************************************** +Updates the lock table when a page is discarded. */ + +void +lock_update_discard( +/*================*/ + rec_t* heir, /* in: record which will inherit the locks */ + page_t* page) /* in: index page which will be discarded */ +{ + rec_t* rec; + + lock_mutex_enter_kernel(); + + if (NULL == lock_rec_get_first_on_page(page)) { + /* No locks exist on page, nothing to do */ + + lock_mutex_exit_kernel(); + + return; + } + + /* Inherit all the locks on the page to the record and reset all + the locks on the page */ + + rec = page_get_infimum_rec(page); + + for (;;) { + lock_rec_inherit_to_gap(heir, rec); + + /* Reset the locks on rec, releasing waiting transactions */ + + lock_rec_reset_and_release_wait(rec); + + if (rec == page_get_supremum_rec(page)) { + + break; + } + + rec = page_rec_get_next(rec); + } + + lock_rec_free_all_from_discard_page(page); + + lock_mutex_exit_kernel(); +} + +/***************************************************************** +Updates the lock table when a new user record is inserted. */ + +void +lock_update_insert( +/*===============*/ + rec_t* rec) /* in: the inserted record */ +{ + lock_mutex_enter_kernel(); + + /* Inherit the locks for rec, in gap mode, from the next record */ + + lock_rec_inherit_to_gap(rec, page_rec_get_next(rec)); + + lock_mutex_exit_kernel(); +} + +/***************************************************************** +Updates the lock table when a record is removed. */ + +void +lock_update_delete( +/*===============*/ + rec_t* rec) /* in: the record to be removed */ +{ + lock_mutex_enter_kernel(); + + /* Let the next record inherit the locks from rec, in gap mode */ + + lock_rec_inherit_to_gap(page_rec_get_next(rec), rec); + + /* Reset the lock bits on rec and release waiting transactions */ + + lock_rec_reset_and_release_wait(rec); + + lock_mutex_exit_kernel(); +} + +/************************************************************************* +Stores on the page infimum record the explicit locks of another record. +This function is used to store the lock state of a record when it is +updated and the size of the record changes in the update. The record +is moved in such an update, perhaps to another page. The infimum record +acts as a dummy carrier record, taking care of lock releases while the +actual record is being moved. */ + +void +lock_rec_store_on_page_infimum( +/*===========================*/ + rec_t* rec) /* in: record whose lock state is stored + on the infimum record of the same page; lock + bits are reset on the record */ +{ + page_t* page; + + page = buf_frame_align(rec); + + lock_mutex_enter_kernel(); + + lock_rec_move(page_get_infimum_rec(page), rec); + + lock_mutex_exit_kernel(); +} + +/************************************************************************* +Restores the state of explicit lock requests on a single record, where the +state was stored on the infimum of the page. */ + +void +lock_rec_restore_from_page_infimum( +/*===============================*/ + rec_t* rec, /* in: record whose lock state is restored */ + page_t* page) /* in: page (rec is not necessarily on this page) + whose infimum stored the lock state; lock bits are + reset on the infimum */ +{ + lock_mutex_enter_kernel(); + + lock_rec_move(rec, page_get_infimum_rec(page)); + + lock_mutex_exit_kernel(); +} + +/*=========== DEADLOCK CHECKING ======================================*/ + +/************************************************************************ +Checks if a lock request results in a deadlock. */ +static +ibool +lock_deadlock_occurs( +/*=================*/ + /* out: TRUE if a deadlock was detected */ + lock_t* lock, /* in: lock the transaction is requesting */ + trx_t* trx) /* in: transaction */ +{ + dict_table_t* table; + dict_index_t* index; + ibool ret; + + ut_ad(trx && lock); + ut_ad(mutex_own(&kernel_mutex)); + + ret = lock_deadlock_recursive(trx, trx, lock); + + if (ret) { + if (lock_get_type(lock) == LOCK_TABLE) { + table = lock->un_member.tab_lock.table; + index = NULL; + } else { + index = lock->index; + table = index->table; + } + /* + sess_raise_error_low(trx, DB_DEADLOCK, lock->type_mode, table, + index, NULL, NULL, NULL); + */ + } + + return(ret); +} + +/************************************************************************ +Looks recursively for a deadlock. */ +static +ibool +lock_deadlock_recursive( +/*====================*/ + /* out: TRUE if a deadlock was detected */ + trx_t* start, /* in: recursion starting point */ + trx_t* trx, /* in: a transaction waiting for a lock */ + lock_t* wait_lock) /* in: the lock trx is waiting to be granted */ +{ + lock_t* lock; + ulint bit_no; + trx_t* lock_trx; + + ut_a(trx && start && wait_lock); + ut_ad(mutex_own(&kernel_mutex)); + + lock = wait_lock; + + if (lock_get_type(wait_lock) == LOCK_REC) { + + bit_no = lock_rec_find_set_bit(wait_lock); + + ut_a(bit_no != ULINT_UNDEFINED); + } + + /* Look at the locks ahead of wait_lock in the lock queue */ + + for (;;) { + if (lock_get_type(lock) == LOCK_TABLE) { + + lock = UT_LIST_GET_PREV(un_member.tab_lock.locks, lock); + } else { + ut_ad(lock_get_type(lock) == LOCK_REC); + + lock = lock_rec_get_prev(lock, bit_no); + } + + if (lock == NULL) { + + return(FALSE); + } + + if (lock_has_to_wait(wait_lock, lock)) { + + lock_trx = lock->trx; + + if (lock_trx == start) { + if (lock_print_waits) { + printf("Deadlock detected\n"); + } + + return(TRUE); + } + + if (lock_trx->que_state == TRX_QUE_LOCK_WAIT) { + + /* Another trx ahead has requested lock in an + incompatible mode, and is itself waiting for + a lock */ + + if (lock_deadlock_recursive(start, lock_trx, + lock_trx->wait_lock)) { + + return(TRUE); + } + } + } + }/* end of the 'for (;;)'-loop */ +} + +/*========================= TABLE LOCKS ==============================*/ + +/************************************************************************* +Creates a table lock object and adds it as the last in the lock queue +of the table. Does NOT check for deadlocks or lock compatibility. */ +UNIV_INLINE +lock_t* +lock_table_create( +/*==============*/ + /* out, own: new lock object, or NULL if + out of memory */ + dict_table_t* table, /* in: database table in dictionary cache */ + ulint type_mode,/* in: lock mode possibly ORed with + LOCK_WAIT */ + trx_t* trx) /* in: trx */ +{ + lock_t* lock; + + ut_ad(table && trx); + ut_ad(mutex_own(&kernel_mutex)); + + lock = mem_heap_alloc(trx->lock_heap, sizeof(lock_t)); + + if (lock == NULL) { + + return(NULL); + } + + UT_LIST_ADD_LAST(trx_locks, trx->trx_locks, lock); + + lock->type_mode = type_mode | LOCK_TABLE; + lock->trx = trx; + + lock->un_member.tab_lock.table = table; + + UT_LIST_ADD_LAST(un_member.tab_lock.locks, table->locks, lock); + + if (type_mode & LOCK_WAIT) { + + lock_set_lock_and_trx_wait(lock, trx); + } + + return(lock); +} + +/***************************************************************** +Removes a table lock request from the queue and the trx list of locks; +this is a low-level function which does NOT check if waiting requests +can now be granted. */ +UNIV_INLINE +void +lock_table_remove_low( +/*==================*/ + lock_t* lock) /* in: table lock */ +{ + dict_table_t* table; + trx_t* trx; + + ut_ad(mutex_own(&kernel_mutex)); + + table = lock->un_member.tab_lock.table; + trx = lock->trx; + + UT_LIST_REMOVE(trx_locks, trx->trx_locks, lock); + UT_LIST_REMOVE(un_member.tab_lock.locks, table->locks, lock); +} + +/************************************************************************* +Enqueues a waiting request for a table lock which cannot be granted +immediately. Checks for deadlocks. */ + +ulint +lock_table_enqueue_waiting( +/*=======================*/ + /* out: DB_LOCK_WAIT, DB_DEADLOCK, or + DB_QUE_THR_SUSPENDED */ + ulint mode, /* in: lock mode this transaction is + requesting */ + dict_table_t* table, /* in: table */ + que_thr_t* thr) /* in: query thread */ +{ + lock_t* lock; + trx_t* trx; + + ut_ad(mutex_own(&kernel_mutex)); + + /* Test if there already is some other reason to suspend thread: + we do not enqueue a lock request if the query thread should be + stopped anyway */ + + if (que_thr_stop(thr)) { + + return(DB_QUE_THR_SUSPENDED); + } + + trx = thr_get_trx(thr); + + /* Enqueue the lock request that will wait to be granted */ + + lock = lock_table_create(table, mode | LOCK_WAIT, trx); + + /* Check if a deadlock occurs: if yes, remove the lock request and + return an error code */ + + if (lock_deadlock_occurs(lock, trx)) { + + lock_reset_lock_and_trx_wait(lock); + lock_table_remove_low(lock); + + return(DB_DEADLOCK); + } + + trx->que_state = TRX_QUE_LOCK_WAIT; + + ut_a(que_thr_stop(thr)); + + return(DB_LOCK_WAIT); +} + +/************************************************************************* +Checks if other transactions have an incompatible mode lock request in +the lock queue. */ +UNIV_INLINE +ibool +lock_table_other_has_incompatible( +/*==============================*/ + trx_t* trx, /* in: transaction, or NULL if all + transactions should be included */ + ulint wait, /* in: LOCK_WAIT if also waiting locks are + taken into account, or 0 if not */ + dict_table_t* table, /* in: table */ + ulint mode) /* in: lock mode */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + + lock = UT_LIST_GET_LAST(table->locks); + + while (lock != NULL) { + + if ((lock->trx != trx) + && (!lock_mode_compatible(lock_get_mode(lock), mode)) + && (wait || !(lock_get_wait(lock)))) { + + return(TRUE); + } + + lock = UT_LIST_GET_PREV(un_member.tab_lock.locks, lock); + } + + return(FALSE); +} + +/************************************************************************* +Locks the specified database table in the mode given. If the lock cannot +be granted immediately, the query thread is put to wait. */ + +ulint +lock_table( +/*=======*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, + DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG bit is set, + does nothing */ + dict_table_t* table, /* in: database table in dictionary cache */ + ulint mode, /* in: lock mode */ + que_thr_t* thr) /* in: query thread */ +{ + trx_t* trx; + ulint err; + + ut_ad(table && thr); + + if (flags & BTR_NO_LOCKING_FLAG) { + + return(DB_SUCCESS); + } + + trx = thr_get_trx(thr); + + lock_mutex_enter_kernel(); + + /* Look for stronger locks the same trx already has on the table */ + + if (lock_table_has(trx, table, mode)) { + + lock_mutex_exit_kernel(); + + return(DB_SUCCESS); + } + + /* We have to check if the new lock is compatible with any locks + other transactions have in the table lock queue. */ + + if (lock_table_other_has_incompatible(trx, LOCK_WAIT, table, mode)) { + + /* Another trx has request on the table in an incompatible + mode: this trx must wait */ + + err = lock_table_enqueue_waiting(mode, table, thr); + + lock_mutex_exit_kernel(); + + return(err); + } + + lock_table_create(table, mode, trx); + + lock_mutex_exit_kernel(); + + return(DB_SUCCESS); +} + +/************************************************************************* +Checks if there are any locks set on the table. */ + +ibool +lock_is_on_table( +/*=============*/ + /* out: TRUE if there are lock(s) */ + dict_table_t* table) /* in: database table in dictionary cache */ +{ + ibool ret; + + ut_ad(table); + + lock_mutex_enter_kernel(); + + if (UT_LIST_GET_LAST(table->locks)) { + ret = TRUE; + } else { + ret = FALSE; + } + + lock_mutex_exit_kernel(); + + return(ret); +} + +/************************************************************************* +Checks if a waiting table lock request still has to wait in a queue. */ +static +ibool +lock_table_has_to_wait_in_queue( +/*============================*/ + /* out: TRUE if still has to wait */ + lock_t* wait_lock) /* in: waiting table lock */ +{ + dict_table_t* table; + lock_t* lock; + + ut_ad(lock_get_wait(wait_lock)); + + table = wait_lock->un_member.tab_lock.table; + + lock = UT_LIST_GET_FIRST(table->locks); + + while (lock != wait_lock) { + + if (lock_has_to_wait(wait_lock, lock)) { + + return(TRUE); + } + + lock = UT_LIST_GET_NEXT(un_member.tab_lock.locks, lock); + } + + return(FALSE); +} + +/***************************************************************** +Removes a table lock request, waiting or granted, from the queue and grants +locks to other transactions in the queue, if they now are entitled to a +lock. */ + +void +lock_table_dequeue( +/*===============*/ + lock_t* in_lock)/* in: table lock object; transactions waiting + behind will get their lock requests granted, if + they are now qualified to it */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(lock_get_type(in_lock) == LOCK_TABLE); + + lock = UT_LIST_GET_NEXT(un_member.tab_lock.locks, in_lock); + + lock_table_remove_low(in_lock); + + /* Check if waiting locks in the queue can now be granted: grant + locks if there are no conflicting locks ahead. */ + + while (lock != NULL) { + + if (lock_get_wait(lock) + && !lock_table_has_to_wait_in_queue(lock)) { + + /* Grant the lock */ + lock_grant(lock); + } + + lock = UT_LIST_GET_NEXT(un_member.tab_lock.locks, lock); + } +} + +/*=========================== LOCK RELEASE ==============================*/ + +/************************************************************************* +Releases transaction locks, and releases possible other transactions waiting +because of these locks. */ + +void +lock_release_off_kernel( +/*====================*/ + trx_t* trx) /* in: transaction */ +{ + ulint count; + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + + lock = UT_LIST_GET_LAST(trx->trx_locks); + + count = 0; + + while (lock != NULL) { + + count++; + + if (lock_get_type(lock) == LOCK_REC) { + + lock_rec_dequeue_from_page(lock); + } else { + ut_ad(lock_get_type(lock) == LOCK_TABLE); + + lock_table_dequeue(lock); + } + + if (count == LOCK_RELEASE_KERNEL_INTERVAL) { + /* Release the kernel mutex for a while, so that we + do not monopolize it */ + + lock_mutex_exit_kernel(); + + lock_mutex_enter_kernel(); + + count = 0; + } + + lock = UT_LIST_GET_LAST(trx->trx_locks); + } + + mem_heap_empty(trx->lock_heap); +} + +/*===================== VALIDATION AND DEBUGGING ====================*/ + +/************************************************************************* +Prints info of a table lock. */ + +void +lock_table_print( +/*=============*/ + lock_t* lock) /* in: table type lock */ +{ + ut_ad(mutex_own(&kernel_mutex)); + ut_a(lock_get_type(lock) == LOCK_TABLE); + + printf("\nTABLE LOCK table %s trx id %lu %lu", + lock->un_member.tab_lock.table->name, + (lock->trx)->id.high, (lock->trx)->id.low); + + if (lock_get_mode(lock) == LOCK_S) { + printf(" lock mode S"); + } else if (lock_get_mode(lock) == LOCK_X) { + printf(" lock_mode X"); + } else if (lock_get_mode(lock) == LOCK_IS) { + printf(" lock_mode IS"); + } else if (lock_get_mode(lock) == LOCK_IX) { + printf(" lock_mode IX"); + } else { + ut_error; + } + + if (lock_get_wait(lock)) { + printf(" waiting"); + } + + printf("\n"); +} + +/************************************************************************* +Prints info of a record lock. */ + +void +lock_rec_print( +/*===========*/ + lock_t* lock) /* in: record type lock */ +{ + page_t* page; + ulint space; + ulint page_no; + ulint i; + mtr_t mtr; + + ut_ad(mutex_own(&kernel_mutex)); + ut_a(lock_get_type(lock) == LOCK_REC); + + space = lock->un_member.rec_lock.space; + page_no = lock->un_member.rec_lock.page_no; + + printf("\nRECORD LOCKS space id %lu page no %lu n bits %lu", + space, page_no, lock_rec_get_n_bits(lock)); + + printf(" index %s trx id %lu %lu", (lock->index)->name, + (lock->trx)->id.high, (lock->trx)->id.low); + + if (lock_get_mode(lock) == LOCK_S) { + printf(" lock mode S"); + } else if (lock_get_mode(lock) == LOCK_X) { + printf(" lock_mode X"); + } else { + ut_error; + } + + if (lock_rec_get_gap(lock)) { + printf(" gap type lock"); + } + + if (lock_get_wait(lock)) { + printf(" waiting"); + } + + printf("\n"); + + mtr_start(&mtr); + + /* If the page is not in the buffer pool, we cannot load it + because we have the kernel mutex and ibuf operations would + break the latching order */ + + page = buf_page_get_gen(space, page_no, RW_NO_LATCH, + NULL, BUF_GET_IF_IN_POOL, +#ifdef UNIV_SYNC_DEBUG + __FILE__, __LINE__, +#endif + &mtr); + if (page) { + page = buf_page_get_nowait(space, page_no, RW_S_LATCH, &mtr); + } + + if (page) { + buf_page_dbg_add_level(page, SYNC_NO_ORDER_CHECK); + } + + for (i = 0; i < lock_rec_get_n_bits(lock); i++) { + + if (lock_rec_get_nth_bit(lock, i)) { + + printf("Record lock, heap no %lu ", i); + + if (page) { + rec_print(page_find_rec_with_heap_no(page, i)); + } + + printf("\n"); + } + } + + mtr_commit(&mtr); +} + +/************************************************************************* +Calculates the number of record lock structs in the record lock hash table. */ +static +ulint +lock_get_n_rec_locks(void) +/*======================*/ +{ + lock_t* lock; + ulint n_locks = 0; + ulint i; + + ut_ad(mutex_own(&kernel_mutex)); + + for (i = 0; i < hash_get_n_cells(lock_sys->rec_hash); i++) { + + lock = HASH_GET_FIRST(lock_sys->rec_hash, i); + + while (lock) { + n_locks++; + + lock = HASH_GET_NEXT(hash, lock); + } + } + + return(n_locks); +} + +/************************************************************************* +Prints info of locks for all transactions. */ + +void +lock_print_info(void) +/*=================*/ +{ + lock_t* lock; + trx_t* trx; + ulint space; + ulint page_no; + page_t* page; + ibool load_page_first = TRUE; + ulint nth_trx = 0; + ulint nth_lock = 0; + ulint i; + mtr_t mtr; + + lock_mutex_enter_kernel(); + + printf("------------------------------------\n"); + printf("LOCK INFO:\n"); + printf("Number of locks in the record hash table %lu\n", + lock_get_n_rec_locks()); +loop: + trx = UT_LIST_GET_FIRST(trx_sys->trx_list); + + i = 0; + + while (trx && (i < nth_trx)) { + trx = UT_LIST_GET_NEXT(trx_list, trx); + i++; + } + + if (trx == NULL) { + lock_mutex_exit_kernel(); + + lock_validate(); + + return; + } + + if (nth_lock == 0) { + printf("\nLOCKS FOR TRANSACTION ID %lu %lu\n", trx->id.high, + trx->id.low); + } + + i = 0; + + lock = UT_LIST_GET_FIRST(trx->trx_locks); + + while (lock && (i < nth_lock)) { + lock = UT_LIST_GET_NEXT(trx_locks, lock); + i++; + } + + if (lock == NULL) { + nth_trx++; + nth_lock = 0; + + goto loop; + } + + if (lock_get_type(lock) == LOCK_REC) { + space = lock->un_member.rec_lock.space; + page_no = lock->un_member.rec_lock.page_no; + + if (load_page_first) { + lock_mutex_exit_kernel(); + + mtr_start(&mtr); + + page = buf_page_get_with_no_latch(space, page_no, &mtr); + + mtr_commit(&mtr); + + load_page_first = FALSE; + + lock_mutex_enter_kernel(); + + goto loop; + } + + lock_rec_print(lock); + } else { + ut_ad(lock_get_type(lock) == LOCK_TABLE); + + lock_table_print(lock); + } + + load_page_first = TRUE; + + nth_lock++; + + goto loop; +} + +/************************************************************************* +Validates the lock queue on a table. */ + +ibool +lock_table_queue_validate( +/*======================*/ + /* out: TRUE if ok */ + dict_table_t* table) /* in: table */ +{ + lock_t* lock; + ibool is_waiting; + + ut_ad(mutex_own(&kernel_mutex)); + + is_waiting = FALSE; + + lock = UT_LIST_GET_FIRST(table->locks); + + while (lock) { + ut_a(((lock->trx)->conc_state == TRX_ACTIVE) + || ((lock->trx)->conc_state == TRX_COMMITTED_IN_MEMORY)); + + if (!lock_get_wait(lock)) { + + ut_a(!is_waiting); + + ut_a(!lock_table_other_has_incompatible(lock->trx, 0, + table, lock_get_mode(lock))); + } else { + is_waiting = TRUE; + + ut_a(lock_table_has_to_wait_in_queue(lock)); + } + + lock = UT_LIST_GET_NEXT(un_member.tab_lock.locks, lock); + } + + return(TRUE); +} + +/************************************************************************* +Validates the lock queue on a single record. */ + +ibool +lock_rec_queue_validate( +/*====================*/ + /* out: TRUE if ok */ + rec_t* rec, /* in: record to look at */ + dict_index_t* index) /* in: index, or NULL if not known */ +{ + trx_t* impl_trx; + lock_t* lock; + ibool is_waiting; + + ut_a(rec); + + lock_mutex_enter_kernel(); + + if (page_rec_is_supremum(rec) || page_rec_is_infimum(rec)) { + + lock = lock_rec_get_first(rec); + + while (lock) { + ut_a(lock->trx->conc_state == TRX_ACTIVE + || lock->trx->conc_state + == TRX_COMMITTED_IN_MEMORY); + + ut_a(trx_in_trx_list(lock->trx)); + + if (lock_get_wait(lock)) { + ut_a(lock_rec_has_to_wait_in_queue(lock)); + } + + if (index) { + ut_a(lock->index == index); + } + + lock = lock_rec_get_next(rec, lock); + } + + lock_mutex_exit_kernel(); + + return(TRUE); + } + + if (index && index->type & DICT_CLUSTERED) { + + impl_trx = lock_clust_rec_some_has_impl(rec, index); + + if (impl_trx && lock_rec_other_has_expl_req(LOCK_S, 0, + LOCK_WAIT, rec, impl_trx)) { + + ut_a(lock_rec_has_expl(LOCK_X, rec, impl_trx)); + } + } + + if (index && !(index->type & DICT_CLUSTERED)) { + + /* The kernel mutex may get released temporarily in the + next function call: we have to release lock table mutex + to obey the latching order */ + + impl_trx = lock_sec_rec_some_has_impl_off_kernel(rec, index); + + if (impl_trx && lock_rec_other_has_expl_req(LOCK_S, 0, + LOCK_WAIT, rec, impl_trx)) { + + ut_a(lock_rec_has_expl(LOCK_X, rec, impl_trx)); + } + } + + is_waiting = FALSE; + + lock = lock_rec_get_first(rec); + + while (lock) { + ut_a(lock->trx->conc_state == TRX_ACTIVE + || lock->trx->conc_state == TRX_COMMITTED_IN_MEMORY); + ut_a(trx_in_trx_list(lock->trx)); + + if (index) { + ut_a(lock->index == index); + } + + if (!lock_rec_get_gap(lock) && !lock_get_wait(lock)) { + + ut_a(!is_waiting); + + if (lock_get_mode(lock) == LOCK_S) { + ut_a(!lock_rec_other_has_expl_req(LOCK_X, + 0, 0, rec, + lock->trx)); + } else { + ut_a(!lock_rec_other_has_expl_req(LOCK_S, + 0, 0, rec, + lock->trx)); + } + + } else if (lock_get_wait(lock) && !lock_rec_get_gap(lock)) { + + is_waiting = TRUE; + ut_a(lock_rec_has_to_wait_in_queue(lock)); + } + + lock = lock_rec_get_next(rec, lock); + } + + lock_mutex_exit_kernel(); + + return(TRUE); +} + +/************************************************************************* +Validates the record lock queues on a page. */ + +ibool +lock_rec_validate_page( +/*===================*/ + /* out: TRUE if ok */ + ulint space, /* in: space id */ + ulint page_no)/* in: page number */ +{ + dict_index_t* index; + page_t* page; + lock_t* lock; + rec_t* rec; + ulint nth_lock = 0; + ulint nth_bit = 0; + ulint i; + mtr_t mtr; + + ut_ad(!mutex_own(&kernel_mutex)); + + mtr_start(&mtr); + + page = buf_page_get(space, page_no, RW_X_LATCH, &mtr); + buf_page_dbg_add_level(page, SYNC_NO_ORDER_CHECK); + + lock_mutex_enter_kernel(); +loop: + lock = lock_rec_get_first_on_page_addr(space, page_no); + + if (!lock) { + goto function_exit; + } + + for (i = 0; i < nth_lock; i++) { + + lock = lock_rec_get_next_on_page(lock); + + if (!lock) { + goto function_exit; + } + } + + ut_a(trx_in_trx_list(lock->trx)); + ut_a(((lock->trx)->conc_state == TRX_ACTIVE) + || ((lock->trx)->conc_state == TRX_COMMITTED_IN_MEMORY)); + + for (i = nth_bit; i < lock_rec_get_n_bits(lock); i++) { + + if ((i == 1) || lock_rec_get_nth_bit(lock, i)) { + + index = lock->index; + rec = page_find_rec_with_heap_no(page, i); + + printf("Validating %lu %lu\n", space, page_no); + + lock_mutex_exit_kernel(); + + lock_rec_queue_validate(rec, index); + + lock_mutex_enter_kernel(); + + nth_bit = i + 1; + + goto loop; + } + } + + nth_bit = 0; + nth_lock++; + + goto loop; + +function_exit: + lock_mutex_exit_kernel(); + + mtr_commit(&mtr); + + return(TRUE); +} + +/************************************************************************* +Validates the lock system. */ + +ibool +lock_validate(void) +/*===============*/ + /* out: TRUE if ok */ +{ + lock_t* lock; + trx_t* trx; + dulint limit; + ulint space; + ulint page_no; + ulint i; + + lock_mutex_enter_kernel(); + + trx = UT_LIST_GET_FIRST(trx_sys->trx_list); + + while (trx) { + lock = UT_LIST_GET_FIRST(trx->trx_locks); + + while (lock) { + if (lock_get_type(lock) == LOCK_TABLE) { + + lock_table_queue_validate( + lock->un_member.tab_lock.table); + } + + lock = UT_LIST_GET_NEXT(trx_locks, lock); + } + + trx = UT_LIST_GET_NEXT(trx_list, trx); + } + + for (i = 0; i < hash_get_n_cells(lock_sys->rec_hash); i++) { + + limit = ut_dulint_zero; + + for (;;) { + lock = HASH_GET_FIRST(lock_sys->rec_hash, i); + + while (lock) { + ut_a(trx_in_trx_list(lock->trx)); + + space = lock->un_member.rec_lock.space; + page_no = lock->un_member.rec_lock.page_no; + + if (ut_dulint_cmp( + ut_dulint_create(space, page_no), + limit) >= 0) { + break; + } + + lock = HASH_GET_NEXT(hash, lock); + } + + if (!lock) { + + break; + } + + lock_mutex_exit_kernel(); + + lock_rec_validate_page(space, page_no); + + lock_mutex_enter_kernel(); + + limit = ut_dulint_create(space, page_no + 1); + } + } + + lock_mutex_exit_kernel(); + + return(TRUE); +} + +/*============ RECORD LOCK CHECKS FOR ROW OPERATIONS ====================*/ + +/************************************************************************* +Checks if locks of other transactions prevent an immediate insert of +a record. If they do, first tests if the query thread should anyway +be suspended for some reason; if not, then puts the transaction and +the query thread to the lock wait state and inserts a waiting request +for a gap x-lock to the lock queue. */ + +ulint +lock_rec_insert_check_and_lock( +/*===========================*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, + DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG bit is set, + does nothing */ + rec_t* rec, /* in: record after which to insert */ + dict_index_t* index, /* in: index */ + que_thr_t* thr, /* in: query thread */ + ibool* inherit)/* out: set to TRUE if the new inserted + record maybe should inherit LOCK_GAP type + locks from the successor record */ +{ + rec_t* next_rec; + trx_t* trx; + lock_t* lock; + ulint err; + + if (flags & BTR_NO_LOCKING_FLAG) { + + return(DB_SUCCESS); + } + + ut_ad(rec); + + trx = thr_get_trx(thr); + next_rec = page_rec_get_next(rec); + + *inherit = FALSE; + + lock_mutex_enter_kernel(); + + ut_ad(lock_table_has(thr_get_trx(thr), index->table, LOCK_IX)); + + lock = lock_rec_get_first(next_rec); + + if (lock == NULL) { + /* We optimize CPU time usage in the simplest case */ + + lock_mutex_exit_kernel(); + + if (!(index->type & DICT_CLUSTERED)) { + + /* Update the page max trx id field */ + page_update_max_trx_id(buf_frame_align(rec), + thr_get_trx(thr)->id); + } + + return(DB_SUCCESS); + } + + *inherit = TRUE; + + /* If another transaction has an explicit lock request, gap or not, + waiting or granted, on the successor, the insert has to wait */ + + if (lock_rec_other_has_expl_req(LOCK_S, LOCK_GAP, LOCK_WAIT, next_rec, + trx)) { + err = lock_rec_enqueue_waiting(LOCK_X | LOCK_GAP, next_rec, + index, thr); + } else { + err = DB_SUCCESS; + } + + lock_mutex_exit_kernel(); + + if (!(index->type & DICT_CLUSTERED) && (err == DB_SUCCESS)) { + + /* Update the page max trx id field */ + page_update_max_trx_id(buf_frame_align(rec), + thr_get_trx(thr)->id); + } + + ut_ad(lock_rec_queue_validate(next_rec, index)); + + return(err); +} + +/************************************************************************* +If a transaction has an implicit x-lock on a record, but no explicit x-lock +set on the record, sets one for it. NOTE that in the case of a secondary +index, the kernel mutex may get temporarily released. */ +static +void +lock_rec_convert_impl_to_expl( +/*==========================*/ + rec_t* rec, /* in: user record on page */ + dict_index_t* index) /* in: index of record */ +{ + trx_t* impl_trx; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(page_rec_is_user_rec(rec)); + + if (index->type & DICT_CLUSTERED) { + impl_trx = lock_clust_rec_some_has_impl(rec, index); + } else { + impl_trx = lock_sec_rec_some_has_impl_off_kernel(rec, index); + } + + if (impl_trx) { + /* If the transaction has no explicit x-lock set on the + record, set one for it */ + + if (!lock_rec_has_expl(LOCK_X, rec, impl_trx)) { + + lock_rec_add_to_queue(LOCK_REC | LOCK_X, rec, index, + impl_trx); + } + } +} + +/************************************************************************* +Checks if locks of other transactions prevent an immediate modify (update, +delete mark, or delete unmark) of a clustered index record. If they do, +first tests if the query thread should anyway be suspended for some +reason; if not, then puts the transaction and the query thread to the +lock wait state and inserts a waiting request for a record x-lock to the +lock queue. */ + +ulint +lock_clust_rec_modify_check_and_lock( +/*=================================*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, + DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG bit is set, + does nothing */ + rec_t* rec, /* in: record which should be modified */ + dict_index_t* index, /* in: clustered index */ + que_thr_t* thr) /* in: query thread */ +{ + trx_t* trx; + ulint err; + + if (flags & BTR_NO_LOCKING_FLAG) { + + return(DB_SUCCESS); + } + + ut_ad(index->type & DICT_CLUSTERED); + + trx = thr_get_trx(thr); + + lock_mutex_enter_kernel(); + + ut_ad(lock_table_has(thr_get_trx(thr), index->table, LOCK_IX)); + + /* If a transaction has no explicit x-lock set on the record, set one + for it */ + + lock_rec_convert_impl_to_expl(rec, index); + + err = lock_rec_lock(TRUE, LOCK_X, rec, index, thr); + + lock_mutex_exit_kernel(); + + ut_ad(lock_rec_queue_validate(rec, index)); + + return(err); +} + +/************************************************************************* +Checks if locks of other transactions prevent an immediate modify (delete +mark or delete unmark) of a secondary index record. */ + +ulint +lock_sec_rec_modify_check_and_lock( +/*===============================*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, + DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG bit is set, + does nothing */ + rec_t* rec, /* in: record which should be modified; + NOTE: as this is a secondary index, we + always have to modify the clustered index + record first: see the comment below */ + dict_index_t* index, /* in: secondary index */ + que_thr_t* thr) /* in: query thread */ +{ + ulint err; + + if (flags & BTR_NO_LOCKING_FLAG) { + + return(DB_SUCCESS); + } + + ut_ad(!(index->type & DICT_CLUSTERED)); + + /* Another transaction cannot have an implicit lock on the record, + because when we come here, we already have modified the clustered + index record, and this would not have been possible if another active + transaction had modified this secondary index record. */ + + lock_mutex_enter_kernel(); + + ut_ad(lock_table_has(thr_get_trx(thr), index->table, LOCK_IX)); + + err = lock_rec_lock(TRUE, LOCK_X, rec, index, thr); + + lock_mutex_exit_kernel(); + + ut_ad(lock_rec_queue_validate(rec, index)); + + if (err == DB_SUCCESS) { + /* Update the page max trx id field */ + + page_update_max_trx_id(buf_frame_align(rec), + thr_get_trx(thr)->id); + } + + return(err); +} + +/************************************************************************* +Like the counterpart for a clustered index below, but now we read a +secondary index record. */ + +ulint +lock_sec_rec_read_check_and_lock( +/*=============================*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, + DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG bit is set, + does nothing */ + rec_t* rec, /* in: user record or page supremum record + which should be read or passed over by a read + cursor */ + dict_index_t* index, /* in: secondary index */ + ulint mode, /* in: mode of the lock which the read cursor + should set on records: LOCK_S or LOCK_X; the + latter is possible in SELECT FOR UPDATE */ + que_thr_t* thr) /* in: query thread */ +{ + ulint err; + + ut_ad(!(index->type & DICT_CLUSTERED)); + ut_ad(page_rec_is_user_rec(rec) || page_rec_is_supremum(rec)); + + if (flags & BTR_NO_LOCKING_FLAG) { + + return(DB_SUCCESS); + } + + lock_mutex_enter_kernel(); + + ut_ad((mode != LOCK_X) + || lock_table_has(thr_get_trx(thr), index->table, LOCK_IX)); + ut_ad((mode != LOCK_S) + || lock_table_has(thr_get_trx(thr), index->table, LOCK_IS)); + + /* Some transaction may have an implicit x-lock on the record only + if the max trx id for the page >= min trx id for the trx list or a + database recovery is running. */ + + if (((ut_dulint_cmp(page_get_max_trx_id(buf_frame_align(rec)), + trx_list_get_min_trx_id()) >= 0) + || recv_recovery_is_on()) + && !page_rec_is_supremum(rec)) { + + lock_rec_convert_impl_to_expl(rec, index); + } + + err = lock_rec_lock(FALSE, mode, rec, index, thr); + + lock_mutex_exit_kernel(); + + ut_ad(lock_rec_queue_validate(rec, index)); + + return(err); +} + +/************************************************************************* +Checks if locks of other transactions prevent an immediate read, or passing +over by a read cursor, of a clustered index record. If they do, first tests +if the query thread should anyway be suspended for some reason; if not, then +puts the transaction and the query thread to the lock wait state and inserts a +waiting request for a record lock to the lock queue. Sets the requested mode +lock on the record. */ + +ulint +lock_clust_rec_read_check_and_lock( +/*===============================*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, + DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */ + ulint flags, /* in: if BTR_NO_LOCKING_FLAG bit is set, + does nothing */ + rec_t* rec, /* in: user record or page supremum record + which should be read or passed over by a read + cursor */ + dict_index_t* index, /* in: clustered index */ + ulint mode, /* in: mode of the lock which the read cursor + should set on records: LOCK_S or LOCK_X; the + latter is possible in SELECT FOR UPDATE */ + que_thr_t* thr) /* in: query thread */ +{ + ulint err; + + ut_ad(index->type & DICT_CLUSTERED); + ut_ad(page_rec_is_user_rec(rec) || page_rec_is_supremum(rec)); + + if (flags & BTR_NO_LOCKING_FLAG) { + + return(DB_SUCCESS); + } + + lock_mutex_enter_kernel(); + + ut_ad((mode != LOCK_X) + || lock_table_has(thr_get_trx(thr), index->table, LOCK_IX)); + ut_ad((mode != LOCK_S) + || lock_table_has(thr_get_trx(thr), index->table, LOCK_IS)); + + if (!page_rec_is_supremum(rec)) { + + lock_rec_convert_impl_to_expl(rec, index); + } + + err = lock_rec_lock(FALSE, mode, rec, index, thr); + + lock_mutex_exit_kernel(); + + ut_ad(lock_rec_queue_validate(rec, index)); + + return(err); +} diff --git a/innobase/lock/makefilewin b/innobase/lock/makefilewin new file mode 100644 index 00000000000..149b0a2fed6 --- /dev/null +++ b/innobase/lock/makefilewin @@ -0,0 +1,7 @@ +include ..\include\makefile.i + +lock.lib: lock0lock.obj + lib -out:..\libs\lock.lib lock0lock.obj + +lock0lock.obj: lock0lock.c + $(CCOM) $(CFL) -c lock0lock.c |