We need two threads, one at buf_page_get_gen and another at
buf_flush_flush_list_batch, trying to work on the same dirty
compressed (i.e. state = BUF_BLOCK_ZIP_DIRTY) page.
1) buf_flush_flush_list_batch acquires flush_list_mutex, gets a
page from the flush list, releases flush_list_mutex.
2) buf_page_get_gen attempts to read the same page, acquires
zip_mutex, gets the dirty compressed page descriptor, allocates
an uncompressed page, and replaces the descriptor with the page in
the flush_list by a buf_flush_relocate_on_flush_list() call. That
call acquires flush_list_mutex, sets in_flush_list = FALSE; but
leaves oldest_modification alone, releases the flush_list_mutex.
Caller then invalidates the descriptor memory, releases zip_mutex,
frees its memory.
3) buf_flush_flush_list_batch attempts to read from the deallocated
compressed page descriptor. Assuming that the memory was not
overwritten since deallocation, the code reads oldest_modification
!= 0 and then trips on !in_flush_list.
I have a theory based on instrumented runs.
We need two threads, one at buf_page_get_gen and another at flush_list_ batch, trying to work on the same dirty ZIP_DIRTY) page.
buf_flush_
compressed (i.e. state = BUF_BLOCK_
1) buf_flush_ flush_list_ batch acquires flush_list_mutex, gets a relocate_ on_flush_ list() call. That flush_list_ batch attempts to read from the deallocated
page from the flush list, releases flush_list_mutex.
2) buf_page_get_gen attempts to read the same page, acquires
zip_mutex, gets the dirty compressed page descriptor, allocates
an uncompressed page, and replaces the descriptor with the page in
the flush_list by a buf_flush_
call acquires flush_list_mutex, sets in_flush_list = FALSE; but
leaves oldest_modification alone, releases the flush_list_mutex.
Caller then invalidates the descriptor memory, releases zip_mutex,
frees its memory.
3) buf_flush_
compressed page descriptor. Assuming that the memory was not
overwritten since deallocation, the code reads oldest_modification
!= 0 and then trips on !in_flush_list.