MDEV-41207 Acquire metadata locks for recovered transaction - #5705
Thirunarayanan wants to merge 2 commits into
Conversation
…e failure Problem: ======= For fulltext index, row_create_index_for_mysql() calls fts_create_index_tables(). If creating FTS auxiliary table fails, error handling performs trx->rollback() of the dictionary transaction. Rollback removes the parent table from dictionary cache and frees it. After that, convert_error_code_to_mysql() reads table->flags after table->heap. This leads to read of freed memory. Solution: ======== create_index(): Read table->flags into a local variable before calling row_create_index_for_mysql()
|
|
| @@ -6694,6 +6692,7 @@ | |||
| goto error_handling; | |||
| } | |||
|
|
|||
| ut_ad(user_table->get_ref_count() == 1); | |||
There was a problem hiding this comment.
What difference does this move make? The call to lock_sys_tables only impacts data dictionary tables, not user_table.
| if (!ctx->online) { | ||
| acquire_lock: | ||
| ctx->prebuilt->trx->op_info = "acquiring table lock"; | ||
| error = lock_table_for_trx(user_table, ctx->trx, LOCK_S); |
There was a problem hiding this comment.
If we unconditionally acquired an exclusive table lock here, we would duly wait for the recovered transaction to be rolled back. But we would not want to interfere with normal LOCK=NONE operation.
Can the reported scenario be fixed by acquiring MDL for each affected table in trx_resurrect_table_locks()?
Problem: ======= A transaction being rolled back during recovery holds LOCK_IX on the table(not the metadata locks), and the rollback thread holds a reference on it. An online ALTER TABLE on that table falls back to acquiring LOCK_S, which conflicts and fails. prepare_inplace_alter_table_dict() asserted that the reference count is 1 before checking whether the table lock was acquired, so the reference still held by the rollback thread makes the assertion fail. Solution: ======== trx_recovery_thd: A background connection that owns the metadata locks of the recovered transactions. One connection is shared by all of recovering transaction. trx_lists_init_at_db_start(): Create trx_recovery_thd, before any metadata lock can be acquired. trx_resurrect_table_locks(): Acquire a shared metadata lock on each table that the recovered transaction had modified, and remember the locks in trx_recovery_mdl. Recovered XA PREPARED transactions are excluded, because they are completed by a user connection. trx_recovery_mdl: The metadata locks of the recovered transactions, by transaction. They are kept outside trx_t. Only a recovered transaction ever has an entry. trx_recovery_mdl_exists: Whether trx_recovery_mdl is not empty. It is read for every transaction in trx_t::free(), so that the map will only be consulted while some recovered transaction still holds metadata locks. It becomes false as soon as the rollback of the recovered transactions has been completed, long before trx_recovery_thd is destroyed. trx_t::free(): Release the metadata locks of a recovered transaction, once its rollback has been completed. This is the only place that releases them, so that a transaction which was rolled back by trx_rollback_recovered(false) will not keep its locks until shutdown. trx_recovery_thd_destroy(): Destroy trx_recovery_thd. It is invoked by innodb_shutdown() only, after trx_sys.close() has freed any recovered transaction that was left. row_undo_mod(): Add the debug injection rollback_wait
cea6c3d to
b74c362
Compare
Problem:
=======
A transaction being rolled back during recovery holds LOCK_IX on the
table(not the metadata locks), and the rollback thread holds
a reference on it.