Commit aa240f8b authored by Inaam Rana's avatar Inaam Rana

Bug#14594600 ASSERT FROM DROP TABLE CONCURRENT WITH IBUF MERGES

rb://1293
approved by: Marko Makela

There is race when dropping a single table tablespace where a reader
thread can initiate a read request before the delete flag is set and
before it is finished the deleting thread can attempt to free the
fil_node.

This patch checks the status in fil_io() to make sure that the
tablespace is not being deleted. If it is being deleted then
an error is returned instead of attempting IO.
parent 1b38b7a6
...@@ -187,14 +187,16 @@ struct fil_space_struct { ...@@ -187,14 +187,16 @@ struct fil_space_struct {
requests on the file */ requests on the file */
ibool stop_new_ops; ibool stop_new_ops;
/*!< we set this TRUE when we start /*!< we set this TRUE when we start
deleting a single-table tablespace */ deleting a single-table tablespace.
ibool is_being_deleted; When this is set following new ops
/*!< this is set to TRUE when we start are not allowed:
deleting a single-table tablespace and its * read IO request
file; when this flag is set no further i/o * ibuf merge
or flush requests can be placed on this space, * file flush
though there may be such requests still being Note that we can still possibly have
processed on this space */ new write operations because we don't
check this flag when doing flush
batches. */
ulint purpose;/*!< FIL_TABLESPACE, FIL_LOG, or ulint purpose;/*!< FIL_TABLESPACE, FIL_LOG, or
FIL_ARCH_LOG */ FIL_ARCH_LOG */
UT_LIST_BASE_NODE_T(fil_node_t) chain; UT_LIST_BASE_NODE_T(fil_node_t) chain;
...@@ -1286,7 +1288,6 @@ fil_space_create( ...@@ -1286,7 +1288,6 @@ fil_space_create(
space->stop_ios = FALSE; space->stop_ios = FALSE;
space->stop_new_ops = FALSE; space->stop_new_ops = FALSE;
space->is_being_deleted = FALSE;
space->purpose = purpose; space->purpose = purpose;
space->size = 0; space->size = 0;
space->flags = flags; space->flags = flags;
...@@ -2301,11 +2302,9 @@ fil_delete_tablespace( ...@@ -2301,11 +2302,9 @@ fil_delete_tablespace(
return(FALSE); return(FALSE);
} }
ut_a(space); ut_a(space->stop_new_ops);
ut_a(space->n_pending_ops == 0); ut_a(space->n_pending_ops == 0);
space->is_being_deleted = TRUE;
ut_a(UT_LIST_GET_LEN(space->chain) == 1); ut_a(UT_LIST_GET_LEN(space->chain) == 1);
node = UT_LIST_GET_FIRST(space->chain); node = UT_LIST_GET_FIRST(space->chain);
...@@ -2348,13 +2347,26 @@ fil_delete_tablespace( ...@@ -2348,13 +2347,26 @@ fil_delete_tablespace(
rw_lock_x_lock(&space->latch); rw_lock_x_lock(&space->latch);
#ifndef UNIV_HOTBACKUP #ifndef UNIV_HOTBACKUP
/* Invalidate in the buffer pool all pages belonging to the /* IMPORTANT: Because we have set space::stop_new_ops there
tablespace. Since we have set space->is_being_deleted = TRUE, readahead can't be any new ibuf merges, reads or flushes. We are here
or ibuf merge can no longer read more pages of this tablespace to the because node::n_pending was zero above. However, it is still
buffer pool. Thus we can clean the tablespace out of the buffer pool possible to have pending read and write requests:
completely and permanently. The flag is_being_deleted also prevents
fil_flush() from being applied to this tablespace. */ A read request can happen because the reader thread has
gone through the ::stop_new_ops check in buf_page_init_for_read()
before the flag was set and has not yet incremented ::n_pending
when we checked it above.
A write request can be issued any time because we don't check
the ::stop_new_ops flag when queueing a block for write.
We deal with pending write requests in the following function
where we'd minimally evict all dirty pages belonging to this
space from the flush_list. Not that if a block is IO-fixed
we'll wait for IO to complete.
To deal with potential read requests by checking the
::stop_new_ops flag in fil_io() */
buf_LRU_flush_or_remove_pages( buf_LRU_flush_or_remove_pages(
id, evict_all id, evict_all
? BUF_REMOVE_ALL_NO_WRITE ? BUF_REMOVE_ALL_NO_WRITE
...@@ -2364,6 +2376,15 @@ fil_delete_tablespace( ...@@ -2364,6 +2376,15 @@ fil_delete_tablespace(
mutex_enter(&fil_system->mutex); mutex_enter(&fil_system->mutex);
/* Double check the sanity of pending ops after reacquiring
the fil_system::mutex. */
if (fil_space_get_by_id(id)) {
ut_a(space->n_pending_ops == 0);
ut_a(UT_LIST_GET_LEN(space->chain) == 1);
node = UT_LIST_GET_FIRST(space->chain);
ut_a(node->n_pending == 0);
}
success = fil_space_free(id, TRUE); success = fil_space_free(id, TRUE);
mutex_exit(&fil_system->mutex); mutex_exit(&fil_system->mutex);
...@@ -2421,7 +2442,7 @@ fil_tablespace_is_being_deleted( ...@@ -2421,7 +2442,7 @@ fil_tablespace_is_being_deleted(
ut_a(space != NULL); ut_a(space != NULL);
is_being_deleted = space->is_being_deleted; is_being_deleted = space->stop_new_ops;
mutex_exit(&fil_system->mutex); mutex_exit(&fil_system->mutex);
...@@ -3695,7 +3716,7 @@ fil_tablespace_deleted_or_being_deleted_in_mem( ...@@ -3695,7 +3716,7 @@ fil_tablespace_deleted_or_being_deleted_in_mem(
space = fil_space_get_by_id(id); space = fil_space_get_by_id(id);
if (space == NULL || space->is_being_deleted) { if (space == NULL || space->stop_new_ops) {
mutex_exit(&fil_system->mutex); mutex_exit(&fil_system->mutex);
return(TRUE); return(TRUE);
...@@ -4408,7 +4429,9 @@ fil_io( ...@@ -4408,7 +4429,9 @@ fil_io(
space = fil_space_get_by_id(space_id); space = fil_space_get_by_id(space_id);
if (!space) { /* If we are deleting a tablespace we don't allow any read
operations on that. However, we do allow write operations. */
if (!space || (type == OS_FILE_READ && space->stop_new_ops)) {
mutex_exit(&fil_system->mutex); mutex_exit(&fil_system->mutex);
ut_print_timestamp(stderr); ut_print_timestamp(stderr);
...@@ -4624,7 +4647,7 @@ fil_flush( ...@@ -4624,7 +4647,7 @@ fil_flush(
space = fil_space_get_by_id(space_id); space = fil_space_get_by_id(space_id);
if (!space || space->is_being_deleted) { if (!space || space->stop_new_ops) {
mutex_exit(&fil_system->mutex); mutex_exit(&fil_system->mutex);
return; return;
...@@ -4755,7 +4778,7 @@ fil_flush_file_spaces( ...@@ -4755,7 +4778,7 @@ fil_flush_file_spaces(
space; space;
space = UT_LIST_GET_NEXT(unflushed_spaces, space)) { space = UT_LIST_GET_NEXT(unflushed_spaces, space)) {
if (space->purpose == purpose && !space->is_being_deleted) { if (space->purpose == purpose && !space->stop_new_ops) {
space_ids[n_space_ids++] = space->id; space_ids[n_space_ids++] = space->id;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment