Skip to content

Commit 0d6d63e

Browse files
committed
MDEV-22027 Assertion oldest_lsn >= log_sys.last_checkpoint_lsn failed
log_buf_pool_get_oldest_modification(): Acquire log_sys_t::flush_order_mutex in order to prevent a race condition that was introduced in commit 1a6f708 (MDEV-15058). Before that change, log_buf_pool_get_oldest_modification() was protected by both log_sys.mutex and log_sys.flush_order_mutex like it was supposed to be ever since commit a52c482 (MySQL 5.5.10). buf_pool_t::get_oldest_modification(): Replaces buf_pool_get_oldest_modification(), to emphasize that log_sys.flush_order_mutex must be acquired by the caller if needed. log_close(): Invoke log_buf_pool_get_oldest_modification() in order to ensure a clean shutdown. The scenario of the race condition is as follows: 1. The buffer pool is clean (no writes are pending). 2. mtr_add_dirtied_pages_to_flush_list() releases log_sys.mutex. 3. log_buf_pool_get_oldest_modification() observes that the buffer pool is clean and returns log_sys.lsn. 4. log_checkpoint() completes, writing a wrong checkpoint header according to which everything up to log_sys.lsn was clean. 5. mtr_add_dirtied_pages_to_flush_list() completes the execution of mtr_memo_note_modifications(), releases the page latches and the flush_order_mutex. 6. On a subsequent log_checkpoint(), the assertion could fail if the page modifications had not been flushed yet. The failing assertion (which is valid) was added in MySQL 5.7 mysql/mysql-server@5c6c6ec and merged to MariaDB Server 10.2.2 in commit fec844a.
1 parent 661ebd4 commit 0d6d63e

File tree

6 files changed

+40
-54
lines changed

6 files changed

+40
-54
lines changed

storage/innobase/buf/buf0buf.cc

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -483,31 +483,27 @@ static bool buf_page_decrypt_after_read(buf_page_t* bpage, fil_space_t* space)
483483

484484
/**
485485
@return the smallest oldest_modification lsn for any page.
486-
@retval 0 if all modified persistent pages have been flushed */
487-
lsn_t
488-
buf_pool_get_oldest_modification()
486+
@retval 0 if all modified persistent pages have been flushed */
487+
lsn_t buf_pool_t::get_oldest_modification()
489488
{
490-
mutex_enter(&buf_pool.flush_list_mutex);
491-
492-
buf_page_t* bpage;
493-
494-
/* FIXME: Keep temporary tablespace pages in a separate flush
495-
list. We would only need to write out temporary pages if the
496-
page is about to be evicted from the buffer pool, and the page
497-
contents is still needed (the page has not been freed). */
498-
for (bpage = UT_LIST_GET_LAST(buf_pool.flush_list);
499-
bpage != NULL && fsp_is_system_temporary(bpage->id.space());
500-
bpage = UT_LIST_GET_PREV(list, bpage)) {
501-
ut_ad(bpage->in_flush_list);
502-
}
503-
504-
lsn_t oldest_lsn = bpage ? bpage->oldest_modification : 0;
505-
mutex_exit(&buf_pool.flush_list_mutex);
506-
507-
/* The returned answer may be out of date: the flush_list can
508-
change after the mutex has been released. */
509-
510-
return(oldest_lsn);
489+
mutex_enter(&flush_list_mutex);
490+
491+
/* FIXME: Keep temporary tablespace pages in a separate flush
492+
list. We would only need to write out temporary pages if the
493+
page is about to be evicted from the buffer pool, and the page
494+
contents is still needed (the page has not been freed). */
495+
const buf_page_t *bpage;
496+
for (bpage= UT_LIST_GET_LAST(flush_list);
497+
bpage && fsp_is_system_temporary(bpage->id.space());
498+
bpage= UT_LIST_GET_PREV(list, bpage))
499+
ut_ad(bpage->in_flush_list);
500+
501+
lsn_t oldest_lsn= bpage ? bpage->oldest_modification : 0;
502+
mutex_exit(&flush_list_mutex);
503+
504+
/* The result may become stale as soon as we released the mutex.
505+
On log checkpoint, also log_sys.flush_order_mutex will be needed. */
506+
return oldest_lsn;
511507
}
512508

513509
/** Allocate a buffer block.

storage/innobase/buf/buf0flu.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2441,7 +2441,7 @@ page_cleaner_flush_pages_recommendation(ulint last_pages_in)
24412441
sum_pages = 0;
24422442
}
24432443

2444-
oldest_lsn = buf_pool_get_oldest_modification();
2444+
oldest_lsn = buf_pool.get_oldest_modification();
24452445

24462446
ut_ad(oldest_lsn <= log_get_lsn());
24472447

storage/innobase/include/buf0buf.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,11 +198,6 @@ UNIV_INLINE
198198
ulint
199199
buf_pool_get_curr_size(void);
200200
/*========================*/
201-
/**
202-
@return the smallest oldest_modification lsn for any page.
203-
@retval 0 if all modified persistent pages have been flushed */
204-
lsn_t
205-
buf_pool_get_oldest_modification();
206201

207202
/********************************************************************//**
208203
Allocates a buf_page_t descriptor. This function must succeed. In case
@@ -1868,6 +1863,11 @@ class buf_pool_t
18681863
bool is_block_lock(const BPageLock *l) const
18691864
{ return is_block_field(reinterpret_cast<const void*>(l)); }
18701865

1866+
/**
1867+
@return the smallest oldest_modification lsn for any page
1868+
@retval 0 if all modified persistent pages have been flushed */
1869+
lsn_t get_oldest_modification();
1870+
18711871
/** Determine if a buffer block was created by chunk_t::create().
18721872
@param block block descriptor (not dereferenced)
18731873
@return whether block has been created by chunk_t::create() */

storage/innobase/include/log0log.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ log_get_max_modified_age_async(void);
139139
/*================================*/
140140

141141
/** Calculate the recommended highest values for lsn - last_checkpoint_lsn
142-
and lsn - buf_get_oldest_modification().
142+
and lsn - buf_pool.get_oldest_modification().
143143
@param[in] file_size requested innodb_log_file_size
144144
@retval true on success
145145
@retval false if the smallest log is too small to
@@ -667,13 +667,13 @@ struct log_t{
667667
lsn_t max_modified_age_async;
668668
/*!< when this recommended
669669
value for lsn -
670-
buf_pool_get_oldest_modification()
670+
buf_pool.get_oldest_modification()
671671
is exceeded, we start an
672672
asynchronous preflush of pool pages */
673673
lsn_t max_modified_age_sync;
674674
/*!< when this recommended
675675
value for lsn -
676-
buf_pool_get_oldest_modification()
676+
buf_pool.get_oldest_modification()
677677
is exceeded, we start a
678678
synchronous preflush of pool pages */
679679
lsn_t max_checkpoint_age_async;

storage/innobase/log/log0log.cc

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -91,27 +91,17 @@ should be bigger than LOG_POOL_PREFLUSH_RATIO_SYNC */
9191
the previous */
9292
#define LOG_POOL_PREFLUSH_RATIO_ASYNC 8
9393

94-
/****************************************************************//**
95-
Returns the oldest modified block lsn in the pool, or log_sys.lsn if none
96-
exists.
94+
/** Return the oldest modified LSN in buf_pool.flush_list,
95+
or the latest LSN if all pages are clean.
9796
@return LSN of oldest modification */
98-
static
99-
lsn_t
100-
log_buf_pool_get_oldest_modification(void)
101-
/*======================================*/
97+
static lsn_t log_buf_pool_get_oldest_modification()
10298
{
103-
lsn_t lsn;
104-
105-
ut_ad(log_mutex_own());
106-
107-
lsn = buf_pool_get_oldest_modification();
99+
ut_ad(log_mutex_own());
100+
log_flush_order_mutex_enter();
101+
lsn_t lsn= buf_pool.get_oldest_modification();
102+
log_flush_order_mutex_exit();
108103

109-
if (!lsn) {
110-
111-
lsn = log_sys.get_lsn();
112-
}
113-
114-
return(lsn);
104+
return lsn ? lsn : log_sys.get_lsn();
115105
}
116106

117107
/** Extends the log buffer.
@@ -419,7 +409,7 @@ log_close(void)
419409
goto function_exit;
420410
}
421411

422-
oldest_lsn = buf_pool_get_oldest_modification();
412+
oldest_lsn = log_buf_pool_get_oldest_modification();
423413

424414
if (!oldest_lsn
425415
|| lsn - oldest_lsn > log_sys.max_modified_age_sync
@@ -432,7 +422,7 @@ log_close(void)
432422
}
433423

434424
/** Calculate the recommended highest values for lsn - last_checkpoint_lsn
435-
and lsn - buf_get_oldest_modification().
425+
and lsn - buf_pool.get_oldest_modification().
436426
@param[in] file_size requested innodb_log_file_size
437427
@retval true on success
438428
@retval false if the smallest log group is too small to

storage/innobase/srv/srv0mon.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2002,7 +2002,7 @@ srv_mon_process_existing_counter(
20022002
break;
20032003

20042004
case MONITOR_OVLD_BUF_OLDEST_LSN:
2005-
value = (mon_type_t) buf_pool_get_oldest_modification();
2005+
value = (mon_type_t) buf_pool.get_oldest_modification();
20062006
break;
20072007

20082008
case MONITOR_OVLD_LSN_CHECKPOINT:

0 commit comments

Comments
 (0)