Skip to content

Commit 984606d

Browse files
committed
MDEV-34750 SET GLOBAL innodb_log_file_size is not crash safe
The recent commit 4ca355d (MDEV-33894) caused a serious regression for online InnoDB ib_logfile0 resizing, breaking crash-safety unless the memory-mapped log file interface is being used. However, the log resizing was broken also before this. To prevent such regressions in the future, we extend the test innodb.log_file_size_online with a kill and restart of the server and with some writes running concurrently with the log size change. When run enough many times, this test revealed all the bugs that are being fixed by the code changes. log_t::resize_start(): Do not allow the resized log to start before the current log sequence number. In this way, there is no need to copy anything to the first block of resize_buf. The previous logic regarding that was incorrect in two ways. First, we would have to copy from the last written buffer (buf or flush_buf). Second, we failed to ensure that the mini-transaction end marker bytes would be 1 in the buffer. If the source ib_logfile0 had wrapped around an odd number of times, the end marker would be 0. This was occasionally observed when running the test innodb.log_file_size_online. log_t::resize_write_buf(): To adjust for the resize_start() change, do not write anything that would be before the resize_lsn. Take the buffer (resize_buf or resize_flush_buf) as a parameter. Starting with commit 4ca355d we no longer swap buffers when rewriting the last log block. log_t::append(): Define as a static function; only some debug assertions need to refer to the log_sys object. innodb_log_file_size_update(): Wake up the buf_flush_page_cleaner() if needed, and wait for it to complete a batch while waiting for the log resizing to be completed. If the current LSN is behind the resize target LSN, we will write redundant FILE_CHECKPOINT records to ensure that the log resizing completes. If the buf_pool.flush_list is empty or the buf_flush_page_cleaner() is stuck for some reason, our wait will time out in 5 seconds, so that we can periodically check if the execution of SET GLOBAL innodb_log_file_size was aborted. Previously, we could get into a busy loop here while the buf_flush_page_cleaner() would remain idle.
1 parent 3a1ff73 commit 984606d

File tree

6 files changed

+89
-41
lines changed

6 files changed

+89
-41
lines changed

mysql-test/suite/innodb/r/log_file_size_online.result

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,20 @@ SHOW VARIABLES LIKE 'innodb_log_file_size';
1919
Variable_name Value
2020
innodb_log_file_size 4194304
2121
FOUND 1 /InnoDB: Resized log to 4\.000MiB/ in mysqld.1.err
22-
UPDATE t SET b='' WHERE a<10;
2322
SET GLOBAL innodb_log_file_size=5242880;
23+
connect con1,localhost,root;
24+
UPDATE t SET b='' WHERE a<10;
25+
connection default;
2426
SHOW VARIABLES LIKE 'innodb_log_file_size';
2527
Variable_name Value
2628
innodb_log_file_size 5242880
2729
SELECT global_value FROM information_schema.system_variables
2830
WHERE variable_name = 'innodb_log_file_size';
2931
global_value
3032
5242880
33+
connection con1;
34+
disconnect con1;
35+
connection default;
3136
# restart
3237
SELECT * FROM t WHERE a<10;
3338
a b
@@ -40,6 +45,10 @@ a b
4045
7
4146
8
4247
9
48+
SELECT COUNT(*),LENGTH(b) FROM t GROUP BY b;
49+
COUNT(*) LENGTH(b)
50+
9 0
51+
19991 255
4352
SHOW VARIABLES LIKE 'innodb_log_file_size';
4453
Variable_name Value
4554
innodb_log_file_size 5242880

mysql-test/suite/innodb/t/log_file_size_online.test

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,28 @@ SHOW VARIABLES LIKE 'innodb_log_file_size';
2525
let SEARCH_PATTERN = InnoDB: Resized log to 4\\.000MiB;
2626
--source include/search_pattern_in_file.inc
2727

28-
UPDATE t SET b='' WHERE a<10;
28+
send SET GLOBAL innodb_log_file_size=5242880;
2929

30-
SET GLOBAL innodb_log_file_size=5242880;
30+
--connect con1,localhost,root
31+
send UPDATE t SET b='' WHERE a<10;
32+
33+
--connection default
34+
reap;
3135
SHOW VARIABLES LIKE 'innodb_log_file_size';
3236
SELECT global_value FROM information_schema.system_variables
3337
WHERE variable_name = 'innodb_log_file_size';
3438

39+
--connection con1
40+
reap;
41+
--disconnect con1
42+
--connection default
43+
44+
--let $shutdown_timeout=0
3545
--let $restart_parameters=
3646
--source include/restart_mysqld.inc
3747

3848
SELECT * FROM t WHERE a<10;
49+
SELECT COUNT(*),LENGTH(b) FROM t GROUP BY b;
3950

4051
SHOW VARIABLES LIKE 'innodb_log_file_size';
4152
let SEARCH_PATTERN = InnoDB: Resized log to 5\\.000MiB;

storage/innobase/handler/ha_innodb.cc

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18551,6 +18551,7 @@ static void innodb_log_file_size_update(THD *thd, st_mysql_sys_var*,
1855118551
ib_senderrf(thd, IB_LOG_LEVEL_ERROR, ER_CANT_CREATE_HANDLER_FILE);
1855218552
break;
1855318553
case log_t::RESIZE_STARTED:
18554+
const lsn_t start{log_sys.resize_in_progress()};
1855418555
for (timespec abstime;;)
1855518556
{
1855618557
if (thd_kill_level(thd))
@@ -18561,13 +18562,30 @@ static void innodb_log_file_size_update(THD *thd, st_mysql_sys_var*,
1856118562

1856218563
set_timespec(abstime, 5);
1856318564
mysql_mutex_lock(&buf_pool.flush_list_mutex);
18564-
const bool in_progress(buf_pool.get_oldest_modification(LSN_MAX) <
18565-
log_sys.resize_in_progress());
18566-
if (in_progress)
18565+
lsn_t resizing= log_sys.resize_in_progress();
18566+
if (resizing > buf_pool.get_oldest_modification(0))
18567+
{
18568+
buf_pool.page_cleaner_wakeup(true);
1856718569
my_cond_timedwait(&buf_pool.done_flush_list,
1856818570
&buf_pool.flush_list_mutex.m_mutex, &abstime);
18571+
resizing= log_sys.resize_in_progress();
18572+
}
1856918573
mysql_mutex_unlock(&buf_pool.flush_list_mutex);
18570-
if (!log_sys.resize_in_progress())
18574+
if (start > log_sys.get_lsn())
18575+
{
18576+
ut_ad(!log_sys.is_pmem());
18577+
/* The server is almost idle. Write dummy FILE_CHECKPOINT records
18578+
to ensure that the log resizing will complete. */
18579+
log_sys.latch.wr_lock(SRW_LOCK_CALL);
18580+
while (start > log_sys.get_lsn())
18581+
{
18582+
mtr_t mtr;
18583+
mtr.start();
18584+
mtr.commit_files(log_sys.last_checkpoint_lsn);
18585+
}
18586+
log_sys.latch.wr_unlock();
18587+
}
18588+
if (!resizing || resizing > start /* only wait for our resize */)
1857118589
break;
1857218590
}
1857318591
}

storage/innobase/include/log0log.h

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -224,16 +224,17 @@ struct log_t
224224
/** exclusive latch for checkpoint, shared for mtr_t::commit() to buf */
225225
alignas(CPU_LEVEL1_DCACHE_LINESIZE) log_rwlock latch;
226226

227-
/** number of std::swap(buf, flush_buf) and writes from buf to log;
227+
/** number of writes from buf or flush_buf to log;
228228
protected by latch.wr_lock() */
229229
ulint write_to_log;
230230

231231
/** Last written LSN */
232232
lsn_t write_lsn;
233233

234234
/** buffer for writing data to ib_logfile0, or nullptr if is_pmem()
235-
In write_buf(), buf and flush_buf are swapped */
235+
In write_buf(), buf and flush_buf may be swapped */
236236
byte *flush_buf;
237+
237238
/** set when there may be need to initiate a log checkpoint.
238239
This must hold if lsn - last_checkpoint_lsn > max_checkpoint_age. */
239240
std::atomic<bool> need_checkpoint;
@@ -370,9 +371,10 @@ struct log_t
370371

371372
private:
372373
/** Write resize_buf to resize_log.
373-
@param length the used length of resize_buf */
374+
@param b resize_buf or resize_flush_buf
375+
@param length the used length of b */
374376
ATTRIBUTE_COLD ATTRIBUTE_NOINLINE
375-
void resize_write_buf(size_t length) noexcept;
377+
void resize_write_buf(const byte *b, size_t length) noexcept;
376378
public:
377379

378380
/** Rename a log file after resizing.
@@ -502,13 +504,7 @@ struct log_t
502504
@param d destination
503505
@param s string of bytes
504506
@param size length of str, in bytes */
505-
void append(byte *&d, const void *s, size_t size) noexcept
506-
{
507-
ut_ad(latch_have_any());
508-
ut_ad(d + size <= buf + (is_pmem() ? file_size : buf_size));
509-
memcpy(d, s, size);
510-
d+= size;
511-
}
507+
static inline void append(byte *&d, const void *s, size_t size) noexcept;
512508

513509
/** Set the log file format. */
514510
void set_latest_format(bool encrypted) noexcept

storage/innobase/log/log0log.cc

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -516,17 +516,14 @@ log_t::resize_start_status log_t::resize_start(os_offset_t size) noexcept
516516
resize_target= size;
517517
resize_buf= static_cast<byte*>(ptr);
518518
resize_flush_buf= static_cast<byte*>(ptr2);
519+
start_lsn= get_lsn();
520+
519521
if (is_pmem())
520-
{
521522
resize_log.close();
522-
start_lsn= get_lsn();
523-
}
524523
else
525-
{
526-
memcpy_aligned<16>(resize_buf, buf, (buf_free + 15) & ~15);
527524
start_lsn= first_lsn +
528-
(~lsn_t{write_size - 1} & (write_lsn - first_lsn));
529-
}
525+
(~lsn_t{write_size - 1} &
526+
(lsn_t{write_size - 1} + start_lsn - first_lsn));
530527
}
531528
resize_lsn.store(start_lsn, std::memory_order_relaxed);
532529
status= success ? RESIZE_STARTED : RESIZE_FAILED;
@@ -780,19 +777,26 @@ inline void log_t::persist(lsn_t lsn) noexcept
780777
#endif
781778

782779
ATTRIBUTE_COLD ATTRIBUTE_NOINLINE
783-
/** Write resize_buf to resize_log.
784-
@param length the used length of resize_buf */
785-
void log_t::resize_write_buf(size_t length) noexcept
780+
void log_t::resize_write_buf(const byte *b, size_t length) noexcept
786781
{
787782
const size_t block_size_1= write_size - 1;
783+
ut_ad(b == resize_buf || b == resize_flush_buf);
788784
ut_ad(!(resize_target & block_size_1));
789785
ut_ad(!(length & block_size_1));
790786
ut_ad(length > block_size_1);
791787
ut_ad(length <= resize_target);
792-
const lsn_t resizing{resize_in_progress()};
793-
ut_ad(resizing <= write_lsn);
794-
lsn_t offset= START_OFFSET +
795-
((write_lsn - resizing) & ~lsn_t{block_size_1}) %
788+
789+
int64_t d= int64_t(write_lsn - resize_in_progress());
790+
if (UNIV_UNLIKELY(d <= 0))
791+
{
792+
d&= ~int64_t(block_size_1);
793+
if (int64_t(d + length) <= 0)
794+
return;
795+
length+= d;
796+
b-= d;
797+
d= 0;
798+
}
799+
lsn_t offset= START_OFFSET + (lsn_t(d) & ~lsn_t{block_size_1}) %
796800
(resize_target - START_OFFSET);
797801

798802
if (UNIV_UNLIKELY(offset + length > resize_target))
@@ -804,7 +808,7 @@ void log_t::resize_write_buf(size_t length) noexcept
804808
}
805809

806810
ut_a(os_file_write_func(IORequestWrite, "ib_logfile101", resize_log.m_file,
807-
buf, offset, length) == DB_SUCCESS);
811+
b, offset, length) == DB_SUCCESS);
808812
}
809813

810814
/** Write buf to ib_logfile0.
@@ -838,6 +842,7 @@ template<bool release_latch> inline lsn_t log_t::write_buf() noexcept
838842
ut_ad(write_size_1 >= 511);
839843

840844
const byte *const write_buf{buf};
845+
const byte *const re_write_buf{resize_buf};
841846
offset&= ~lsn_t{write_size_1};
842847

843848
if (length <= write_size_1)
@@ -851,8 +856,8 @@ template<bool release_latch> inline lsn_t log_t::write_buf() noexcept
851856
#else
852857
# ifdef HAVE_valgrind
853858
MEM_MAKE_DEFINED(buf + length, (write_size_1 + 1) - length);
854-
if (UNIV_LIKELY_NULL(resize_buf))
855-
MEM_MAKE_DEFINED(resize_buf + length, (write_size_1 + 1) - length);
859+
if (UNIV_LIKELY_NULL(re_write_buf))
860+
MEM_MAKE_DEFINED(re_write_buf + length, (write_size_1 + 1) - length);
856861
# endif
857862
buf[length]= 0; /* allow recovery to catch EOF faster */
858863
#endif
@@ -872,15 +877,15 @@ template<bool release_latch> inline lsn_t log_t::write_buf() noexcept
872877
the current LSN are generated. */
873878
#ifdef HAVE_valgrind
874879
MEM_MAKE_DEFINED(buf + length, (write_size_1 + 1) - new_buf_free);
875-
if (UNIV_LIKELY_NULL(resize_buf))
876-
MEM_MAKE_DEFINED(resize_buf + length, (write_size_1 + 1) -
880+
if (UNIV_LIKELY_NULL(re_write_buf))
881+
MEM_MAKE_DEFINED(re_write_buf + length, (write_size_1 + 1) -
877882
new_buf_free);
878883
#endif
879884
buf[length]= 0; /* allow recovery to catch EOF faster */
880885
length&= ~write_size_1;
881886
memcpy_aligned<16>(flush_buf, buf + length, (new_buf_free + 15) & ~15);
882-
if (UNIV_LIKELY_NULL(resize_buf))
883-
memcpy_aligned<16>(resize_flush_buf, resize_buf + length,
887+
if (UNIV_LIKELY_NULL(re_write_buf))
888+
memcpy_aligned<16>(resize_flush_buf, re_write_buf + length,
884889
(new_buf_free + 15) & ~15);
885890
length+= write_size_1 + 1;
886891
}
@@ -899,8 +904,8 @@ template<bool release_latch> inline lsn_t log_t::write_buf() noexcept
899904
/* Do the write to the log file */
900905
log_write_buf(write_buf, length, offset);
901906

902-
if (UNIV_LIKELY_NULL(resize_buf))
903-
resize_write_buf(length);
907+
if (UNIV_LIKELY_NULL(re_write_buf))
908+
resize_write_buf(re_write_buf, length);
904909
write_lsn= lsn;
905910

906911
if (UNIV_UNLIKELY(srv_shutdown_state > SRV_SHUTDOWN_INITIATED))

storage/innobase/mtr/mtr0mtr.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,6 +1310,15 @@ inline void log_t::resize_write(lsn_t lsn, const byte *end, size_t len,
13101310
}
13111311
}
13121312

1313+
inline void log_t::append(byte *&d, const void *s, size_t size) noexcept
1314+
{
1315+
ut_ad(log_sys.latch_have_any());
1316+
ut_ad(d + size <= log_sys.buf +
1317+
(log_sys.is_pmem() ? log_sys.file_size : log_sys.buf_size));
1318+
memcpy(d, s, size);
1319+
d+= size;
1320+
}
1321+
13131322
template<bool spin,bool pmem>
13141323
std::pair<lsn_t,mtr_t::page_flush_ahead>
13151324
mtr_t::finish_writer(mtr_t *mtr, size_t len)

0 commit comments

Comments
 (0)