[MDEV-21910] KIlling thread on Galera could cause mutex deadlock Created: 2020-03-11 Updated: 2022-12-16 Resolved: 2020-07-29 |
|
| Status: | Closed |
| Project: | MariaDB Server |
| Component/s: | Galera, Storage Engine - InnoDB |
| Affects Version/s: | 10.1, 10.2, 10.3, 10.4, 10.5 |
| Fix Version/s: | 10.4.14, 10.5.5 |
| Type: | Bug | Priority: | Blocker |
| Reporter: | Jan Lindström (Inactive) | Assignee: | Jan Lindström (Inactive) |
| Resolution: | Fixed | Votes: | 1 |
| Labels: | None | ||
| Attachments: |
|
||||||||||||||||||||||||||||
| Issue Links: |
|
||||||||||||||||||||||||||||
| Description |
|
There following issues here:
|
| Comments |
| Comment by Sergey Vojtovich [ 2020-04-14 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
It is unfortunate that we have to take this approach, but apparently it is the easiest what we can do. Speaking of d1578f7ad548aa5ecd80d313601a347641e5037f, it seem to go inline with this idea. I didn't dive much into the wsrep code, I'll leave it up to you and Galera team. One thing that doesn't look completely right is passing THD to a background thread. Neither bf_thd nor victim_thd seem to be protected against destruction. Accessing sync point action of a bf_thd from wsrep_kill() looks even worse. Solution: I don't think you need to pass bf_thd and victim_thd should be found by thread id instead (like KILL does it). Another thing is that you both enqueue and kill under the LOCK_wsrep_kill. Which introduces another "reverse" mutex locking pattern: And I guess it may eventually lead to another deadlock. I also wonder if wsrep really-really has to issue THD::awake()? Why lock_cancel_waiting_and_release() is not enough? If that's not enough, can victim commit suicide itself thereafter? Looks alright otherwise. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Jan Lindström (Inactive) [ 2020-04-28 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
seppo Can you please review patch for 10.2 on branch bb-10.2-galera-21910 same on git https://github.com/MariaDB/server/commit/2471c1fb88bdb58cae20121b2eb15c2a1ed606cd I know that 10.2 and 10.3 versions of above fix do not crash on tests or in randgen. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Jan Lindström (Inactive) [ 2020-04-29 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Analysis from 10.4
From these only lock_sys->mutex is global mutex. All others are either thread internal (thd) or transaction internal (trx). Therefore, mutex deadlock is possible if and only if thread we have selected as victim for BF abort and thread user is trying to kill are exactly the same one. While BF abort also will call thd->awake() it will have trx->lock.was_chosen_as_wsrep_victim=true and then we do not take lock_sys->mutex or trx->mutex at lock_trx_handle_wait(). Second problem is possible when in wsrep_kill_one_trx() we set trx->lock.was_chosen_as_wsrep_victim=true and release thd->LOCK_thd_data mutex. Now if we have a schedule where KILL is executed there is possibility that we either try to take lock_sys->mutex and have to wait or if this is same thread as victim no InnoDB mutexes are taken. Furthermore, in bf_abort() we might take thd->LOCK_thd_data again in wsrep-lib. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Jan Lindström (Inactive) [ 2020-04-29 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
For 10.1-10.3 it seems that I can fix the problem using background killing the victim thread. However, for 10.4 and upwards that does not work based on already regression testing (This version is on branch bb-10.4- For 10.4 I'm now suggesting a lot more simpler approach where we release InnoDB-locks before entering wsrep_kill_one_trx(). This way we can request safely thd->LOCK_thd_data mutex and later on wsrep_thd_bf_abort call thd->awake(). Replacing thd->wake() with e.g. thd->set_killed(KILL_QUERY) did not work for some tests already in 10.4. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Jan Lindström (Inactive) [ 2020-04-29 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
marko Can you have a look of https://github.com/MariaDB/server/commit/06629988a22ac022d517dcc6d03c7f4f257de43a | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2020-04-30 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
jplindst, here are some quick comments or questions:
I think that your proposed change could be a step to the right direction, but it needs some more work. Studying | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Jan Lindström (Inactive) [ 2020-04-30 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Thank you for your detailed review. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Jan Lindström (Inactive) [ 2020-05-06 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Current status:
From these 10.1-10.3 looks better on both regression testing as well as randgen. However, 10.4 version does not look good on regression testing. Answers to review questions:
In my opinion the biggest open questions is the fact that if we release lock_sys->mutex and trx->mutex how to make sure that victim does not continue executing operations (possible up to commit/rollback)? 10.4 in regression testing locally has at least following problem (test case: galera.galera_bf_lock_wait)
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Jan Lindström (Inactive) [ 2020-05-06 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
seppo marko Based on above comment, I'm again stuck and do not know how to proceed from here. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Jan Lindström (Inactive) [ 2020-05-07 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Additional discussion: There was a question do we need to call thd::awake(). Firstly, avoiding that call in 10.4 will not help as in wsrep_thd_bf_abort() and below wsrep-lib will take thd->LOCK_thd_data mutex anyway. And in my understanding we can't avoid taking thd->LOCK_thd_data mutex on KILL and lock_sys->mutex and trx->mutex when canceling victim locks. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2020-05-08 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
jplindst, in the 10.2 fix of
I do not see any reference-count operations in the proposed 10.2 or 10.3 fixes (c0f7d3783254240fa6eeb59248dafc8b55518de2, 1af58dfb1b959c2f7ae3747b078e1c31f6169701). In the proposed 10.4 fix, I do not see any check of victim_trx->state in wsrep_innobase_kill_one_trx() after acquiring the mutex:
The victim_trx may already have been committed at that point! We must not pollute trx->lock.was_chosen_as_deadlock_victim for the next user who would allocate the transaction object from the pool (see
Note: wsrep_kill_victim() is missing an assertion or check lock->trx->is_wsrep(). Are internal (non-Galera) transactions supposed to be killed by replicated transactions? If yes, then the clearing of that flag in my proposed patch for General remarks:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Seppo Jaakola [ 2020-05-22 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
I am able to reproduce a hang with KILL QUERY and victim transaction, which is in committing state, by running galera.MW-328A test repeatedly (~50 rounds should make it surface) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Seppo Jaakola [ 2020-05-27 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
I was able to create a mtr test for reproducing a debug assert, when both BF abort (issued by wsrep applier thread) and manual KILL command try to kill same victim. The symptom is debug level assert, not a deadlock, so somewhat different that this issue description has. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Jan Lindström (Inactive) [ 2020-06-02 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
seppo I have attached necessary changes to code and idea of test case that should repeat the mutex deadlock if test case is correct. Idea is to start transaction on node_2 so that it will take conflicting row locks and then execute normal update on node_1 that will be executed by applier on node_2 and will conflict against open transaction on node_2. Idea is to execute these so that, BF transaction will execute up to point before we take LOCK_thd_data(victim) to victim but it still owns lock_sys->mutex and trx->mutex(victim). Then, from another connection to node_2 we try to kill open update query on node_2 up to point it already own LOCK_thd_data(victim) and then we let BF to try to take LOCK_thd_data(victim) that it can't have as it is already taken and continue KILL that will try to take lock_sys->mutex that is held by BF thread so both BF and KILL are waiting each other. My code changes and test case against 10.2 commit 50641db2d11ad8a2228f7938d851e52decb71a9b debug build | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Seppo Jaakola [ 2020-06-04 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Submitted a PR for MariaDB 10.4. It contains a mtr test for reproducing an issue in scenario where an idle client connection is victim for simultaneous KILL command and BF abort and a potential fix for the issue. The PR is against 10.4, and had good test response in our jenkins testing. PR is here: https://github.com/MariaDB/server/pull/1577 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Jan Lindström (Inactive) [ 2020-06-04 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2020-06-04 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
I would like to see a detailed explanation of the failure scenario, and root cause analysis. Concurrency is hard. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Jan Lindström (Inactive) [ 2020-06-23 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Consider following table and data:
We start a open transaction on node_2 from connection node_2a, lets call this thread_1.
Now we start transaction on node_1, this transaction will be certified successfully on node_1 and write-set is sent to node_2 to be executed by the applier using brute force (BF) lets call this thread_2. However, applier will find out that this write-set will conflict with thread_1.
In applier execution we notice that there is conflicting lock request where a=1 in lock0lock.cc: lock_rec_other_has_conflicting() BF thread_2 has already locked lock_sys->mutex and when it finds out conflicting record lock it will lock trx->mutex(victim) where victim is same as thread_1. Then we go to wsrep_kill_victim() that will call ha_innodb.cc:wsrep_kill_one_trx(). Lets assume that CPU executing thread_2 will do context switch to execute other threads, especially on test case we want it to execute KILL to certain point:
Execution of this thread (lets call it thread_3) will start from sql_parse.cc:sql_kill() that will call kill_one_thread() and then find_thread_by_id(). In all current versions this function will find the victim thread (thread_3) and will lock victim->LOCK_thd_data(). At this point we let CPU again do context switch and continue BF thread (thread_2) execution on ha_innobase::wsrep_kill_one_trx(). There BF thread will try to lock victim->LOCK_thd_data(), but it cant as this victim thread is the same thread that we try to KILL in thread_3, so victim->LOCK_thd_data() is already held. For this BF thread we are holding following mutexes:
As BF thread can't continue we let CPU again do context switch and continue KILL execution (thread_3) from kill_one_thread(). This thread will call victim->awake() and that will call ha_innodb.cc:innobase_kill_query(). There, if thread is found we try to lock lock_sys->mutex. However, that is not possible for this KILL thread (thread_3) as BF thread (thread_2) already has locked it. Therefore, KILL thread (thread_3) has following mutexes:
This means that thread_2 we have mutex ordering of:
and thread_3 has mutex ordering of
As both thread are waiting a mutex that is held by other thread both can't continue execution. Real problem is that these two execution paths take lock_sys->mutex and victim->LOCK_thd_data mutexes in different order, exactly opposite order. Thus we have thread_3(BF) waiting thread_2(KILL) waiting thread_3(BF). By, iteration this mean thread_3(BF) -> thread_3(BF), and that is impossible, we have mutex deadlock. Idea of the fix is to set thd->wsrep_killed=true by first thread that is executing either the KILL or BF victim kill. We let only one thread to continue further, the other one just exits. We protect thd->wsrep_killed by thd->LOCK_thd_data so if KILL has executed find_thread_by_id() where it locks thd->LOCK_thd_data we first check is thd->wsrep_killed == true. If it is someone has already started kill operation and we release thd->LOCK_thd_data and exit from KILL, if not we set thd->wsrep_killed=true and continue. Similarly, for BF kill. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2020-07-13 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
How exactly is the THD::wsrep_killed protected? I suppose that it would be zero-initialized at THD object instantiation. After the field has been set, when exactly will it be reset? Keep in mind that KILL QUERY would retain the connection, and we would probably want to reset the flag before starting to execute the next statement. Could we allocate the flag in trx_t instead of THD? It is specific to InnoDB, after all. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Jan Lindström (Inactive) [ 2020-07-14 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
THD::wsrep_killed is protected by THD::LOCK_thd_data mutex in my bb-10-4- | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2020-07-16 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
I see. I am also glad to see that the InnoDB changes to are limited to the function wsrep_innobase_kill_one_trx(). I only have a minor nitpick about mysql_mutex_assert_not_owner(), which I believe to be redundant:
The function safe_mutex_lock() would already guard against recursive acquisition:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2020-07-20 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
A fix of this was pushed to the 10.4 branch, and there are conflicts when merging to the 10.5 branch. Because I needed to merge something else, I performed a null merge of this to 10.5, and this change will have to be applied to 10.5 (as well as 10.1) separately. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Jan Lindström (Inactive) [ 2020-07-22 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
10.5 version pushed on 7bffe468b239645d2f27d1d5625cb9c914ae994d | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Jan Lindström (Inactive) [ 2020-07-28 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Attached patch wsrep_kill.diff to create a test case that repeats the problem on current implementation in 10.4. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Jan Lindström (Inactive) [ 2020-07-29 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Problem with current implementation can be repeated with following very high level test case that requires at least 2-node Galera cluster:
Problem is that both kill transaction and applier transaction try to kill the same victim transaction while they hold necessary mutexes. However, mutexes are taken in different order in kill transaction compared to applier transaction leading to mutex deadlock. To further analyze the bug lets use 10.4 and start from kill transaction. Here user has issued KILL <thread> where thread id is same as victim transaction's thread id. Interesting point starts from sql_parse.cc at function kill_one_thread().
If we did find the thread we are trying to kill it will be protected from concurrent kill by LOCK_thd_kill mutex and THD's data members are protected from concurrent change by LOCK_thd_data mutex. Thus, user can't issue another concurrent KILL for this thread anymore, that KILL statement would have to wait and will not anymore find the thread. However, there is other method that can kill the same thread as the thread user is trying to kill. In problematic scheduling we assume that user kill is executed to GAP1 point and then it will pause. Let's now consider other method. For this we need conflicting transaction that is paused at node_2.
This paused update is the victim transaction that user will try to kill and another method will try to kill. Let's now move to node_2 in Galera cluster.
We can assume that in node_1 there is no conflicting transactions that access t1. Thus, this transaction will pass it certification and write set is send to node_2 to be executed by the applier. In node_2 update to row (1,0) has a conflicting lock request i.e exclusive lock. This is found in lock0lock.cc function lock_rec_other_has_conflicting() when we try to create lock request for applier transaction.
At the point we have found the conflicting lock request we are holding lock_sys->mutex that is global mutex and trx_mutex for victim transaction. Lock_sys mutex protect so that no new lock request may be created and lock request we have found can't be released. Trx_mutex does not allow victim to commit or abort. Applier transaction is BF (brute force) transaction and we would not like to wait long, so we try to kill victim transaction to get this record lock released. KIlling is done in ha_innodb.cc in function wsrep_innobase_kill_one_trx.
At the GAP2 point we are still holding global lock_sys->mutex and trx_mutex for the victim transaction but but these do not restrict the other method of killing victim threads i.e. user KILL statement to be executed. Bad CPU scheduler in node_2 can first execute applier transaction (BF) up to GAP2 and pause. Then it could start executing user kill transaction up to GAP1 and pause. Finally, we can try to execute applier transaction up to GAP3 but we cant as kill transaction is holding victim_thd->LOCK_thd_data and applier is also requesting it so applier must wait.
Execution of user KILL statement will pause when kill transaction requests lock_sys->mutex as that mutex is already locked by applier transaction. Now both user kill statement transaction and applier transaction execution has been paused on mutex wait and they wait each other.
This is a mutex deadlock. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Jan Lindström (Inactive) [ 2020-07-29 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Design how to avoid mutex deadlock. We will still use thd->wsrep_aborter but with atomic operations so that
We need to change both kill_one_thread() function as well as wsrep_innobase_kill_one_trx() function.
Condition for wsrep_aborter must be done using atomic operation with strong consistency so that if it is 0 we set our current bf_thd->thread_id as a marker that execution of kill operation has started. If it is not 0 i.e. free executing thread i.e. bf_thd->thread_id must be same as wsrep_aborter value.
Here again condition for wsrep_aborter must be done using atomic operation with strong consistency so that if it is 0 we set our current bf_thd->thread_id as a marker that execution of kill operation has started. If it is not 0 i.e. free executing thread i.e. bf_thd->id must be same as wsrep_aborter value. How this high-level description of the algorithm avoids the mutex deadlock presented earlier? Remember that reason for mutex deadlock was that both kill transaction and applier transaction was trying to lock victim_thd->LOCK_thd_data and lock_sys->mutex. For user KILL statement transaction execution we can't avoid locking victim_thd->LOCK_thd_data BUT we can avoid request to lock_sys->mutex at innobase_kill_query(). This is done by atomic operation to wsrep_aborter before we even enter victim_thd->awake_no_mutex():
Similarly on applier transaction executing wsrep_innobase_kill_one_trx function before we even request victim_thd->LOCK_thd_data mutex. Idea is to control what thread can continue kill operation execution so that only one thread will be able to do it and so that first thread who can set wsrep_aborter variable will do it.
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2020-07-29 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
An incomplete fix for this has been pushed to 10.4.14 and 10.5.5, and it does not look like we would be able to revise that fix before the next releases. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Rick Pizzi [ 2022-11-22 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
jplindst I may have a case for this issue on 10.6 |