Skip to content
  • Sivert Sorumgard's avatar
    a97f39ba
    WL#7593: New data dictionary: don't hold LOCK_open while · a97f39ba
    Sivert Sorumgard authored
    reading table definition
    
    While retrieving a table definition when getting a table
    share, the LOCK_open mutex may be temporarily released.
    This allows a higher degree of concurrency when opening
    tables, and is needed for the new data dictionary where
    dictionary tables may need to be opened recursively.
    
    The following changes have been implemented:
    
    1. A new TABLE_SHARE member m_open_in_progress is introduced
    to flag that a share is being initialized. Concurrent threads
    opening the same share must check this flag to see whether
    they must wait for the share to be initialized.
    
    2. A condition variable COND_open is introduced for threads
    that need to wait for a share which is being opened.
    
    3. We must wait for the share to be completely opened
    before returning from get_cached_table_share().
    
    4. There are loops iterating over all elements in the
    table definition cache. One loop is in close_cached_tables(),
    this is safe to keep as is because shares being opened are
    handled. The other loop is in list_open_tables(), here we found
    it better to ignore shares being opened to avoid situations
    where we would incorrectly present a table as being open
    (e.g. if the opening actually failed).
    
    5. We must assert that the newly inserted share is actually
    present in the cache before returning from get_table_share().
    
    6. Shares being opened (m_open_in_progress == true) in
    close_cached_connection_tables() are skipped. Otherwise, due
    to the way TABLE_SHARE::connect_string is initialized in
    open_binary_frm(), there is a risk that connect_string.length
    is initialized while connect_string.str is not, hence
    making us read into uninitialized memory further down in
    close_cached_connection_tables().
    
    Thus, in theory, there is a risk that shares are left in the
    cache that should really be closed (matching the submitted
    connection string), and this risk is already present since
    LOCK_open is unlocked before calling
    close_cached_connection_tables().
    
    Now, close_cached_connection_tables() is called as the final
    step of DROP/ALTER SERVER, so its goal is to flush all tables
    which were open before DROP/ALTER SERVER started. Thus, if a
    share gets opened after close_cached_connection_tables() has
    started, the information about the server has already been
    updated, so the new table will use the new definition of the
    server.
    
    It might have been an issue, however if one thread started
    opening a federated table, read the old server definition into a
    share, and then a switch to another thread doing ALTER SERVER
    happened right before setting m_open_in_progress to false for
    the share. Because in this case ALTER SERVER would not flush
    the share opened by the first thread as it should have been. But
    luckily, server definitions affected by * SERVER statements are
    not read into TABLE_SHARE structures, but are read when we
    create the TABLE object in ha_federated::open().
    
    This means that in close_cached_connection_tables(), ignoring
    shares that are in the process of being opened is safe, because
    such shares don't have TABLE objects associated with them yet.
    a97f39ba
    WL#7593: New data dictionary: don't hold LOCK_open while
    Sivert Sorumgard authored
    reading table definition
    
    While retrieving a table definition when getting a table
    share, the LOCK_open mutex may be temporarily released.
    This allows a higher degree of concurrency when opening
    tables, and is needed for the new data dictionary where
    dictionary tables may need to be opened recursively.
    
    The following changes have been implemented:
    
    1. A new TABLE_SHARE member m_open_in_progress is introduced
    to flag that a share is being initialized. Concurrent threads
    opening the same share must check this flag to see whether
    they must wait for the share to be initialized.
    
    2. A condition variable COND_open is introduced for threads
    that need to wait for a share which is being opened.
    
    3. We must wait for the share to be completely opened
    before returning from get_cached_table_share().
    
    4. There are loops iterating over all elements in the
    table definition cache. One loop is in close_cached_tables(),
    this is safe to keep as is because shares being opened are
    handled. The other loop is in list_open_tables(), here we found
    it better to ignore shares being opened to avoid situations
    where we would incorrectly present a table as being open
    (e.g. if the opening actually failed).
    
    5. We must assert that the newly inserted share is actually
    present in the cache before returning from get_table_share().
    
    6. Shares being opened (m_open_in_progress == true) in
    close_cached_connection_tables() are skipped. Otherwise, due
    to the way TABLE_SHARE::connect_string is initialized in
    open_binary_frm(), there is a risk that connect_string.length
    is initialized while connect_string.str is not, hence
    making us read into uninitialized memory further down in
    close_cached_connection_tables().
    
    Thus, in theory, there is a risk that shares are left in the
    cache that should really be closed (matching the submitted
    connection string), and this risk is already present since
    LOCK_open is unlocked before calling
    close_cached_connection_tables().
    
    Now, close_cached_connection_tables() is called as the final
    step of DROP/ALTER SERVER, so its goal is to flush all tables
    which were open before DROP/ALTER SERVER started. Thus, if a
    share gets opened after close_cached_connection_tables() has
    started, the information about the server has already been
    updated, so the new table will use the new definition of the
    server.
    
    It might have been an issue, however if one thread started
    opening a federated table, read the old server definition into a
    share, and then a switch to another thread doing ALTER SERVER
    happened right before setting m_open_in_progress to false for
    the share. Because in this case ALTER SERVER would not flush
    the share opened by the first thread as it should have been. But
    luckily, server definitions affected by * SERVER statements are
    not read into TABLE_SHARE structures, but are read when we
    create the TABLE object in ha_federated::open().
    
    This means that in close_cached_connection_tables(), ignoring
    shares that are in the process of being opened is safe, because
    such shares don't have TABLE objects associated with them yet.
Loading