Skip to content

MDEV-41395 fil_space_t::create() may release fil_system.mutex - #5823

Merged
dr-m merged 1 commit into
11.4from
MDEV-41395
Nov 2, 2026
Merged

dr-m merged 1 commit into
11.4from
MDEV-41395

Conversation

@dr-m

@dr-m dr-m commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

If fil_space_t::create() ends up releasing fil_system.mutex for invoking fil_crypt_threads_signal(), another thread may grab fil_system.mutex and find a tablespace that contains no data files. Let us fix this race condition and remove some checks that now are redundant.

fil_space_t::create(): Do not invoke fil_crypt_threads_init(); let the caller do that. During database startup, before fil_crypt_threads_init() has been invoked, there is no point to invoke this function; the encryption queues will be properly set up for the tablespaces that have been opened by that time.

fil_ibd_create(): Invoke fil_crypt_threads_signal() after releasing fil_system.mutex.

fil_crypt_threads_signal(): Check that fil_crypt_threads_init() has been called. The function dict_table_open_on_name() will be invoked during early server startup.

dict_table_open_on_name(): Invoke fil_crypt_threads_init() at the end if the table was loaded into the cache and the tablespace likely as well. This should be the main code path through which old tablespaces are opened via SQL.

We will not invoke fil_crypt_threads_init() in dict_load_table_on_id() or dict_sys.load_table(); it will instead be invoked at a higher level, typically when a table definition is first loaded by name, in dict_table_open_on_name().

buf_dump_load_func(): After the non-first buf_load(), invoke fil_crypt_threads_signal() in order to start the encryption of any newly loaded tablespaces.

drop_garbage_tables_after_restore(), wsrep_append_foreign_key(), ha_innobase::create(), ha_innobase::truncate(),
row_rename_table_for_mysql(), i_s_sys_tables_fill_table(), i_s_sys_tables_fill_table_stats(): Invoke fil_crypt_threads_signal() in case some tablespace metadata was loaded.

row_import_cleanup(): Invoke fil_crypt_threads_signal() to register the imported tablespace.

i_s_sys_tablespaces_fill_table(), fil_system_t::find(), Datafile::validate_first_page(), fil_space_t::name(), fil_space_t::read_page0(), fil_space_t::prepare_acquired(), fil_space_t::try_to_close(), fil_crypt_default_encrypt_tables_fill(), fil_system_t::default_encrypt_next(): Remove a now-redundant check for a tablespace that contains no files.

@dr-m
dr-m requested a review from Thirunarayanan October 6, 2026 07:42
@dr-m dr-m self-assigned this Oct 6, 2026
@CLAassistant

CLAassistant commented Oct 6, 2026 •

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@dr-m dr-m mentioned this pull request Oct 6, 2026
Comment thread storage/innobase/fil/fil0fil.cc Outdated
fil_node_t* node = space->add(path, OS_FILE_CLOSED, size, false, true);
space->set_stopped();
mysql_mutex_unlock(&fil_system.mutex);
fil_crypt_threads_signal();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Encryption threads wakes up and skips the tablespace which are stopped. We're calling fil_crypt_threads_signal() after space->set_stopped()

@Thirunarayanan Thirunarayanan Oct 15, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

diff --git a/storage/innobase/fil/fil0fil.cc b/storage/innobase/fil/fil0fil.cc
index 3b6d8282aba..9878b2854d3 100644
--- a/storage/innobase/fil/fil0fil.cc
+++ b/storage/innobase/fil/fil0fil.cc
@@ -2022,7 +2022,6 @@ fil_ibd_create(
        fil_node_t* node = space->add(path, OS_FILE_CLOSED, size, false, true);
        space->set_stopped();
        mysql_mutex_unlock(&fil_system.mutex);
-       fil_crypt_threads_signal();
 
        buf_block_t *header[2];
        {
@@ -2132,6 +2131,7 @@ fil_ibd_create(
                space->release();
        }
        mysql_mutex_unlock(&fil_system.mutex);
+       fil_crypt_threads_signal();
        goto func_exit;
 }


mysql_mutex_lock(&fil_system.mutex);
space->clear_stopped();
node->handle = file;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to signal here after clear_stopped?

Comment thread storage/innobase/row/row0import.cc Outdated
prebuilt->trx->op_info = "";

DBUG_EXECUTE_IF("ib_import_before_checkpoint_crash", DBUG_SUICIDE(););
fil_crypt_threads_signal();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

                err = row_import_for_mysql(m_prebuilt->table, m_prebuilt);

                if (err == DB_SUCCESS) {
  
                        info(HA_STATUS_TIME
                             | HA_STATUS_CONST
                             | HA_STATUS_VARIABLE
                             | HA_STATUS_AUTO);

                        fil_crypt_add_imported_space(m_prebuilt->table->space);
                }

fil_crypt_add_imported_space() is already signaling the fil_crypt_threads_cond

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the signal here?

If fil_space_t::create() ends up releasing fil_system.mutex for
invoking fil_crypt_threads_signal(), another thread may grab
fil_system.mutex and find a tablespace that contains no data files.
Let us fix this race condition and remove some checks that now
are redundant.

By default fil_crypt_must_default_encrypt() holds, and then
fil_crypt_thread() waits indefinitely in wait_for_work() once the
default_encrypt list has been exhausted. A tablespace for which no
fil_crypt_threads_signal() is issued would therefore never be
encrypted at all.

fil_space_t::create(): Do not invoke fil_crypt_threads_init();
let the caller do that. During database startup, before
fil_crypt_threads_init() has been invoked, there is no point to
invoke this function; the encryption queues will be properly
set up for the tablespaces that have been opened by that time.

fil_ibd_create(): Invoke fil_crypt_threads_signal() after
invoking fil_space_t::clear_stopped() and releasing fil_system.mutex.

fil_crypt_threads_signal(): Check that fil_crypt_threads_init()
has been called. The function dict_table_open_on_name() will be
invoked during early server startup.

dict_table_open_on_name(): Invoke fil_crypt_threads_init()
at the end if the table was loaded into the cache and the
tablespace likely as well. This should be the main code path
through which old tablespaces are opened via SQL.

dict_load_tablespace(): Invoke fil_crypt_threads_signal(), because
fil_ibd_open() may have added the tablespace to
fil_system.default_encrypt_tables. This is the single point through
which every dictionary-driven tablespace open passes; it is only
invoked by dict_load_table_one(), hence by dict_sys_t::load_table().

We will not invoke fil_crypt_threads_init() in
dict_load_table_on_id() or dict_sys.load_table(); it will instead
be invoked at a higher level, typically when a table definition is
first loaded by name, in dict_table_open_on_name().

buf_dump_load_func(): After the non-first buf_load(), invoke
fil_crypt_threads_signal() in order to start the encryption
of any newly loaded tablespaces.

drop_garbage_tables_after_restore(), wsrep_append_foreign_key(),
ha_innobase::create(), ha_innobase::truncate(),
row_rename_table_for_mysql(), i_s_sys_tables_fill_table(),
i_s_sys_tables_fill_table_stats(): Invoke fil_crypt_threads_signal()
in case some tablespace metadata was loaded.

row_import_cleanup(): Invoke fil_crypt_threads_signal() to register
the imported tablespace.

i_s_sys_tablespaces_fill_table(), fil_system_t::find(),
Datafile::validate_first_page(), fil_space_t::name(),
fil_space_t::read_page0(), fil_space_t::prepare_acquired(),
fil_space_t::try_to_close(), fil_crypt_default_encrypt_tables_fill(),
fil_system_t::default_encrypt_next(): Remove a now-redundant check
for a tablespace that contains no files.

srv_n_fil_crypt_threads_started: Declare as Atomic_relaxed<uint>,
for the unlocked read in fil_crypt_threads_signal().

Co-developed by: Thirunarayanan Balathandayuthapani
@dr-m
dr-m merged commit b874c28 into 11.4 Nov 2, 2026
15 of 18 checks passed
@dr-m
dr-m deleted the MDEV-41395 branch November 2, 2026 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

3 participants