diff --git a/mysql-test/suite/innodb/r/alloc_fail.result b/mysql-test/suite/innodb/r/alloc_fail.result new file mode 100644 index 00000000000..7bdd0d7ce72 --- /dev/null +++ b/mysql-test/suite/innodb/r/alloc_fail.result @@ -0,0 +1,47 @@ +# +# MDEV-36017 Alter table aborts when temporary +# directory is full +# +SET SESSION DEFAULT_STORAGE_ENGINE=InnoDB; +CREATE TABLE t1(f1 CHAR(100) NOT NULL, f2 CHAR(100) NOT NULL, +f3 CHAR(100) NOT NULL, f4 CHAR(100) NOT NULL, +f5 CHAR(100) NOT NULL)ENGINE=InnoDB; +INSERT INTO t1 SELECT 'a', 'b', 'c', 'd', 'e' FROM seq_1_to_65536; +SET STATEMENT DEBUG_DBUG="+d,write_to_tmp_file_fail" FOR +CREATE TABLE t2 as SELECT * FROM t1; +ERROR HY000: Got error 59 'Temp file write failure' from InnoDB +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, f2 CHAR(100), +f3 CHAR(100))ENGINE=InnoDB; +INSERT INTO t1 SELECT seq, 'a', 'b' FROM seq_1_to_1024; +SET STATEMENT DEBUG_DBUG="+d,write_to_tmp_file_fail" FOR +ALTER TABLE t1 FORCE, ALGORITHM=COPY; +ERROR HY000: Got error 59 'Temp file write failure' from InnoDB +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, f2 CHAR(100), +f3 CHAR(100))ENGINE=InnoDB; +INSERT INTO t1 SELECT seq, 'a', 'b' FROM seq_1_to_4096; +SET DEBUG_SYNC="inplace_after_index_build SIGNAL dml_start WAIT_FOR dml_commit"; +ALTER TABLE t1 ADD KEY(f1), ADD INDEX(f3(10)); +connect con1,localhost,root,,,; +SET DEBUG_SYNC="now WAIT_FOR dml_start"; +BEGIN; +INSERT INTO t1 SELECT * FROM t1; +SET STATEMENT DEBUG_DBUG="+d,os_file_write_fail" FOR COMMIT; +SET DEBUG_SYNC="now SIGNAL dml_commit"; +connection default; +ERROR HY000: Temporary file write failure +disconnect con1; +CHECK TABLE t1; +Table Op Msg_type Msg_text +test.t1 check status OK +DROP TABLE t1; +SET STATEMENT DEBUG_DBUG="+d,ddl_log_write_fail" FOR +CREATE TABLE t1(f1 INT NOT NULL)ENGINE=InnoDB; +DROP TABLE t1; +CREATE TABLE t1(f1 TEXT, index(f1(2)))ENGINE=InnoDB; +INSERT INTO t1 VALUES('a'); +set statement DEBUG_DBUG="+d,btr_page_alloc_fail" for +UPDATE t1 set f1= REPEAT('b', 12000); +ERROR HY000: The table 't1' is full +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/t/alloc_fail.opt b/mysql-test/suite/innodb/t/alloc_fail.opt new file mode 100644 index 00000000000..7a3969d2a37 --- /dev/null +++ b/mysql-test/suite/innodb/t/alloc_fail.opt @@ -0,0 +1,2 @@ +--innodb_sort_buffer_size=64k +--innodb_rollback_on_timeout=1 diff --git a/mysql-test/suite/innodb/t/alloc_fail.test b/mysql-test/suite/innodb/t/alloc_fail.test new file mode 100644 index 00000000000..62350872e5e --- /dev/null +++ b/mysql-test/suite/innodb/t/alloc_fail.test @@ -0,0 +1,55 @@ +--source include/have_innodb.inc +--source include/have_sequence.inc +--source include/have_debug.inc +--echo # +--echo # MDEV-36017 Alter table aborts when temporary +--echo # directory is full +--echo # +SET SESSION DEFAULT_STORAGE_ENGINE=InnoDB; +CREATE TABLE t1(f1 CHAR(100) NOT NULL, f2 CHAR(100) NOT NULL, + f3 CHAR(100) NOT NULL, f4 CHAR(100) NOT NULL, + f5 CHAR(100) NOT NULL)ENGINE=InnoDB; +INSERT INTO t1 SELECT 'a', 'b', 'c', 'd', 'e' FROM seq_1_to_65536; +--error ER_GET_ERRMSG +SET STATEMENT DEBUG_DBUG="+d,write_to_tmp_file_fail" FOR +CREATE TABLE t2 as SELECT * FROM t1; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, f2 CHAR(100), + f3 CHAR(100))ENGINE=InnoDB; +INSERT INTO t1 SELECT seq, 'a', 'b' FROM seq_1_to_1024; +--error ER_GET_ERRMSG +SET STATEMENT DEBUG_DBUG="+d,write_to_tmp_file_fail" FOR +ALTER TABLE t1 FORCE, ALGORITHM=COPY; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, f2 CHAR(100), + f3 CHAR(100))ENGINE=InnoDB; +INSERT INTO t1 SELECT seq, 'a', 'b' FROM seq_1_to_4096; +SET DEBUG_SYNC="inplace_after_index_build SIGNAL dml_start WAIT_FOR dml_commit"; +SEND ALTER TABLE t1 ADD KEY(f1), ADD INDEX(f3(10)); + +connect(con1,localhost,root,,,); +SET DEBUG_SYNC="now WAIT_FOR dml_start"; +BEGIN; +INSERT INTO t1 SELECT * FROM t1; +SET STATEMENT DEBUG_DBUG="+d,os_file_write_fail" FOR COMMIT; +SET DEBUG_SYNC="now SIGNAL dml_commit"; + +connection default; +--error ER_TEMP_FILE_WRITE_FAILURE +reap; +disconnect con1; +CHECK TABLE t1; +DROP TABLE t1; + +SET STATEMENT DEBUG_DBUG="+d,ddl_log_write_fail" FOR +CREATE TABLE t1(f1 INT NOT NULL)ENGINE=InnoDB; +DROP TABLE t1; + +CREATE TABLE t1(f1 TEXT, index(f1(2)))ENGINE=InnoDB; +INSERT INTO t1 VALUES('a'); +--error ER_RECORD_FILE_FULL +set statement DEBUG_DBUG="+d,btr_page_alloc_fail" for +UPDATE t1 set f1= REPEAT('b', 12000); +DROP TABLE t1; diff --git a/sql/ddl_log.cc b/sql/ddl_log.cc index 7c844ed5905..647e7852645 100644 --- a/sql/ddl_log.cc +++ b/sql/ddl_log.cc @@ -3051,13 +3051,15 @@ static bool ddl_log_write(DDL_LOG_STATE *ddl_state, error= ((ddl_log_write_entry(ddl_log_entry, &log_entry)) || ddl_log_write_execute_entry(log_entry->entry_pos, 0, &ddl_state->execute_entry)); - mysql_mutex_unlock(&LOCK_gdl); + DBUG_EXECUTE_IF("ddl_log_write_fail", error= true;); if (error) { if (log_entry) ddl_log_release_memory_entry(log_entry); + mysql_mutex_unlock(&LOCK_gdl); DBUG_RETURN(1); } + mysql_mutex_unlock(&LOCK_gdl); ddl_log_add_entry(ddl_state, log_entry); ddl_state->flags|= ddl_log_entry->flags; // Update cache DBUG_RETURN(0); diff --git a/storage/innobase/btr/btr0cur.cc b/storage/innobase/btr/btr0cur.cc index c55f0aaf59e..5348a67e8c9 100644 --- a/storage/innobase/btr/btr0cur.cc +++ b/storage/innobase/btr/btr0cur.cc @@ -3697,8 +3697,10 @@ btr_cur_optimistic_update( *offsets = rec_get_offsets(rec, index, *offsets, index->n_core_fields, ULINT_UNDEFINED, heap); #if defined UNIV_DEBUG || defined UNIV_BLOB_LIGHT_DEBUG + /* Blob pointer can be null if InnoDB was killed or + ran out of space while allocating a page. */ ut_a(!rec_offs_any_null_extern(rec, *offsets) - || thr_get_trx(thr) == trx_roll_crash_recv_trx); + || thr_get_trx(thr)->in_rollback); #endif /* UNIV_DEBUG || UNIV_BLOB_LIGHT_DEBUG */ if (UNIV_LIKELY(!update->is_metadata()) @@ -4371,7 +4373,12 @@ btr_cur_pessimistic_update( cursor, offsets, offsets_heap, new_entry, &rec, &dummy_big_rec, n_ext, NULL, mtr); - ut_a(err == DB_SUCCESS); + if (err) { + /* This should happen when InnoDB tries to extend the + tablespace */ + ut_ad(err == DB_OUT_OF_FILE_SPACE); + return err; + } ut_a(rec); ut_a(dummy_big_rec == NULL); ut_ad(rec_offs_validate(rec, cursor->index(), *offsets)); @@ -6240,6 +6247,9 @@ btr_store_big_rec_extern_fields( FSP_NO_DIR, 0, &mtr, &mtr, &error); + DBUG_EXECUTE_IF("btr_page_alloc_fail", + block= nullptr; + error= DB_OUT_OF_FILE_SPACE;); if (!block) { alloc_fail: mtr.commit(); diff --git a/storage/innobase/fil/fil0fil.cc b/storage/innobase/fil/fil0fil.cc index 4ba3183e371..39d6096e9cd 100644 --- a/storage/innobase/fil/fil0fil.cc +++ b/storage/innobase/fil/fil0fil.cc @@ -605,7 +605,7 @@ fil_space_extend_must_retry( *success = os_file_set_size(node->name, node->handle, new_size, node->punch_hole == 1); - os_has_said_disk_full = *success; + os_has_said_disk_full = !*success; if (*success) { os_file_flush(node->handle); last_page_no = size; diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index d03ef08d1db..bf3511f6551 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -2161,6 +2161,11 @@ convert_error_code_to_mysql( return(HA_ERR_RECORD_FILE_FULL); case DB_TEMP_FILE_WRITE_FAIL: + /* This error can happen during + copy_data_between_tables() or bulk insert operation */ + innodb_transaction_abort(thd, + innobase_rollback_on_timeout, + error); my_error(ER_GET_ERRMSG, MYF(0), DB_TEMP_FILE_WRITE_FAIL, ut_strerr(DB_TEMP_FILE_WRITE_FAIL), @@ -15931,7 +15936,7 @@ ha_innobase::extra( } m_prebuilt->table->skip_alter_undo = 0; if (dberr_t err= trx->bulk_insert_apply()) { - m_prebuilt->table->skip_alter_undo = 0; + trx->rollback(); return convert_error_code_to_mysql( err, m_prebuilt->table->flags, trx->mysql_thd); diff --git a/storage/innobase/row/row0log.cc b/storage/innobase/row/row0log.cc index a0135e4be03..8337d170e76 100644 --- a/storage/innobase/row/row0log.cc +++ b/storage/innobase/row/row0log.cc @@ -398,12 +398,17 @@ start_log: } log->tail.blocks++; + DBUG_EXECUTE_IF("os_file_write_fail", + log->error = DB_TEMP_FILE_WRITE_FAIL; + goto write_failed;); + if (os_file_write( IORequestWrite, "(modification log)", log->fd, buf, byte_offset, srv_sort_buf_size) != DB_SUCCESS) { + log->error = DB_TEMP_FILE_WRITE_FAIL; write_failed: index->type |= DICT_CORRUPT; } diff --git a/storage/innobase/row/row0merge.cc b/storage/innobase/row/row0merge.cc index fba8467f095..c180b8ac961 100644 --- a/storage/innobase/row/row0merge.cc +++ b/storage/innobase/row/row0merge.cc @@ -5177,6 +5177,9 @@ dberr_t row_merge_bulk_t::write_to_tmp_file(ulint index_no) m_block, m_crypt_block, buf->index->table->space->id)) return DB_TEMP_FILE_WRITE_FAIL; + + DBUG_EXECUTE_IF("write_to_tmp_file_fail", + return DB_TEMP_FILE_WRITE_FAIL;); MEM_UNDEFINED(&m_block[0], srv_sort_buf_size); return DB_SUCCESS; } diff --git a/storage/innobase/row/row0mysql.cc b/storage/innobase/row/row0mysql.cc index f93ad6eb3ee..7992866df67 100644 --- a/storage/innobase/row/row0mysql.cc +++ b/storage/innobase/row/row0mysql.cc @@ -708,6 +708,7 @@ handle_new_error: case DB_DEADLOCK: case DB_RECORD_CHANGED: case DB_LOCK_TABLE_FULL: + case DB_TEMP_FILE_WRITE_FAIL: rollback: /* Roll back the whole transaction; this resolution was added to version 3.23.43 */ diff --git a/storage/innobase/row/row0umod.cc b/storage/innobase/row/row0umod.cc index fa228cba93a..24d7283bfc0 100644 --- a/storage/innobase/row/row0umod.cc +++ b/storage/innobase/row/row0umod.cc @@ -1130,9 +1130,8 @@ row_undo_mod_upd_exist_sec( dtuple_t* entry = row_build_index_entry( node->row, node->ext, index, heap); if (UNIV_UNLIKELY(!entry)) { - /* The server must have crashed in - row_upd_clust_rec_by_insert() before - the updated externally stored columns (BLOBs) + /* InnoDB must have run of space or been killed + before the updated externally stored columns (BLOBs) of the new clustered index entry were written. */ /* The table must be in DYNAMIC or COMPRESSED @@ -1140,19 +1139,6 @@ row_undo_mod_upd_exist_sec( store a local 768-byte prefix of each externally stored column. */ ut_a(dict_table_has_atomic_blobs(index->table)); - - /* This is only legitimate when - rolling back an incomplete transaction - after crash recovery. */ - ut_a(thr_get_trx(thr)->is_recovered); - - /* The server must have crashed before - completing the insert of the new - clustered index entry and before - inserting to the secondary indexes. - Because node->row was not yet written - to this index, we can ignore it. But - we must restore node->undo_row. */ } else { /* NOTE that if we updated the fields of a delete-marked secondary index record so that diff --git a/storage/innobase/row/row0upd.cc b/storage/innobase/row/row0upd.cc index 2c40fdf32d4..2b37045d222 100644 --- a/storage/innobase/row/row0upd.cc +++ b/storage/innobase/row/row0upd.cc @@ -1414,16 +1414,11 @@ row_upd_changes_ord_field_binary_func( if (UNIV_LIKELY_NULL(buf)) { if (UNIV_UNLIKELY(buf == field_ref_zero)) { /* The externally stored field - was not written yet. This - record should only be seen by - trx_rollback_recovered() - when the server had crashed before - storing the field. */ - ut_ad(!thr - || thr->graph->trx->is_recovered); - ut_ad(!thr - || thr->graph->trx - == trx_roll_crash_recv_trx); + was not written yet. InnoDB must + have ran out of space or been killed + before storing the page */ + ut_ad(thr); + ut_ad(thr->graph->trx->in_rollback); return(TRUE); }