Bug #116319 When locks exhaust the BP, an incorret assert about semi_consistent causes crash
Submitted: 9 Oct 2024 6:50 Modified: 9 Oct 2024 12:24
Reporter: mengchu shi (OCA) Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: InnoDB storage engine Severity:S2 (Serious)
Version:8.0, 8.4, 8.0.39 OS:Any
Assigned to: CPU Architecture:Any
Tags: Contribution, DML, lock, semi_consistent

[9 Oct 2024 6:50] mengchu shi
Description:
an assert about semi_consistent in function `row_search_mvcc` is incorrect and may cause crash when the lock structs exhaust the BP.

The code analysis as following:
In function `row_search_mvcc`, if `use_semi_consistent` is true, call `sel_set_rec_lock` with `SELECT_SKIP_LOCKED` mode and `DB_SKIP_LOCKED` is excepted to be returned.
However, `sel_set_rec_lock` may return `DB_LOCK_TABLE_FULL` if locks exhaust the BP so that less than 25 % of BP is available.
So the following `switch` of row_search_mvcc will enter `default:`, assert !use_semi_consistent failed and crash.

How to repeat:
Use debug injection to simulate the situation of lock table full:

```
--- a/storage/innobase/row/row0sel.cc
+++ b/storage/innobase/row/row0sel.cc
@@ -1149,6 +1149,10 @@ static inline dberr_t sel_set_rec_lock(btr_pcur_t *pcur, const rec_t *rec,
   trx = thr_get_trx(thr);
   ut_ad(trx_can_be_handled_by_current_thread(trx));
 
+  DBUG_EXECUTE_IF(
+      "simulate_lock_table_full",
+      if (sel_mode == SELECT_SKIP_LOCKED) return (DB_LOCK_TABLE_FULL););
+
   if (UT_LIST_GET_LEN(trx->lock.trx_locks) > 10000) {
     if (buf_LRU_buf_pool_running_out()) {
       return (DB_LOCK_TABLE_FULL);
```

And test as following:
```
##### Prepare
# Try to use semi-consistent read:
# 1. transaction_isolation="READ-COMMITTED"
# 2. do update
# 3. mustn't use the unique search
SET SESSION transaction_isolation="READ-COMMITTED";
CREATE TABLE t1(id INT, name VARCHAR(200));
INSERT INTO t1 VALUES(1, "before");

##### Debug inject
# Simulate the situation of lock table full
SET GLOBAL debug="+d,simulate_lock_table_full";

##### Test
# Before this patch, a crash will occur here
# After this patch, this query will fail
--error ER_LOCK_TABLE_FULL
UPDATE t1 SET name="after" WHERE id=1;

##### Clear
DROP TABLE t1;
SET GLOBAL debug="-d,simulate_lock_table_full";
```

Suggested fix:
Correct the assert
```
--- a/storage/innobase/row/row0sel.cc
+++ b/storage/innobase/row/row0sel.cc
@@ -5287,7 +5291,7 @@ rec_loop:
         }
 
       default:
-        ut_a(!use_semi_consistent);
+        ut_a(!use_semi_consistent || err == DB_LOCK_TABLE_FULL);
         goto lock_wait_or_error;
     }
     if (err == DB_SUCCESS && !row_to_range_relation.row_can_be_in_range) {
```
[9 Oct 2024 12:24] MySQL Verification Team
Hello mengchu shi,

Thank you for the report and feedback.

regards,
Umesh
[9 Oct 2024 12:25] MySQL Verification Team
8.0.39 test results

Attachment: 116319.results.txt (text/plain), 2.78 MiB.

[10 Oct 2024 2:19] mengchu shi
patch to fix the bug incorrect_assert_if_use_semi_consistent_when_table_lock_full

(*) I confirm the code being submitted is offered under the terms of the OCA, and that I am authorized to contribute it.

Contribution: bugfix_incorrect_assert_if_use_semi_consistent_when_table_lock_full.gitlog (application/octet-stream, text), 3.26 KiB.

[10 Oct 2024 2:19] mengchu shi
patch to fix the bug incorrect_assert_if_use_semi_consistent_when_table_lock_full

(*) I confirm the code being submitted is offered under the terms of the OCA, and that I am authorized to contribute it.

Contribution: bugfix_incorrect_assert_if_use_semi_consistent_when_table_lock_full.gitlog (application/octet-stream, text), 3.26 KiB.

[10 Oct 2024 3:58] MySQL Verification Team
Thank you for the Contribution.

regards,
Umesh