Bug #107769 It's wrong when io_cache calculate info->write_end
Submitted: 6 Jul 2022 8:07 Modified: 6 Jul 2022 11:34
Reporter: shang canfang Email Updates:
Status: Not a Bug Impact on me:
None 
Category:MySQL Server Severity:S3 (Non-critical)
Version:MySQL5.7.26 OS:Linux
Assigned to: CPU Architecture:x86
Tags: io_cache, write

[6 Jul 2022 8:07] shang canfang
Description:

in mf_iocache.c file

my_b_flush_io_cache func

{
    if ((length=(size_t) (info->write_pos - info->write_buffer)))
    {
      if (append_cache)
      {
        DBUG_ASSERT(!(info->myflags & MY_ENCRYPT));
        if (mysql_file_write(info->file, info->write_buffer, length,
                             info->myflags | MY_NABP))
          info->error= -1;
        else
          info->error= 0;

        info->end_of_file+= info->write_pos - info->append_read_pos;
        info->append_read_pos= info->write_buffer;
        DBUG_ASSERT(info->end_of_file ==
                    mysql_file_tell(info->file, MYF(0)));
      }
      else
      {
        int res= info->write_function(info, info->write_buffer, length);
        if (res)
        {
          UNLOCK_APPEND_BUFFER;
          DBUG_RETURN(res);
        }

        set_if_bigger(info->end_of_file, info->pos_in_file);
      }
      info->write_end= (info->write_buffer + info->buffer_length -
                        ((pos_in_file + length) & (IO_SIZE - 1)));
      info->write_pos= info->write_buffer;
      ++info->disk_writes;
      UNLOCK_APPEND_BUFFER;
      DBUG_RETURN(info->error);
    }
}

in info->write_function(info, info->write_buffer, length), info has already updated pos_in_file(+=length),but here add length again,As a result, the next write cannot be aligned by 4K.

info->write_end= (info->write_buffer + info->buffer_length -
                        ((info->pos_in_file + length) & (IO_SIZE - 1)));

How to repeat:
use sysbench to insert some data, then use gdb to print relevant information.

Suggested fix:
I think it should be changed like this:
use tmp variable to save old pos_in_file, then use pos_in_file + length to calculate info->write_end
{
      pos_in_file = info->pos_in_file;
      if (append_cache)
      {
        DBUG_ASSERT(!(info->myflags & MY_ENCRYPT));
        if (mysql_file_write(info->file, info->write_buffer, length,
                             info->myflags | MY_NABP))
          info->error= -1;
        else
          info->error= 0;

        info->end_of_file+= info->write_pos - info->append_read_pos;
        info->append_read_pos= info->write_buffer;
        DBUG_ASSERT(info->end_of_file ==
                    mysql_file_tell(info->file, MYF(0)));
      }
      else
      {
        int res= info->write_function(info, info->write_buffer, length);
        if (res)
        {
          UNLOCK_APPEND_BUFFER;
          DBUG_RETURN(res);
        }

        set_if_bigger(info->end_of_file, info->pos_in_file);
      }
      info->write_end= (info->write_buffer + info->buffer_length -
                        ((pos_in_file + length) & (IO_SIZE - 1)));
}
[6 Jul 2022 11:34] MySQL Verification Team
Hi Mr. ye,

Thank you for your bug report.

However, we can not change the manner in which write_end is calculated in our IO_CACHE system. Several of our storage engines and temporary files use this algorithm instead of the simple unbuffered FILE operation. That means that any changes would created huge problems for any upgrade to some newer 5.7 release. Simply, those tables or files would suddenly become unreadable and non-writeable in newer releases. 

Changes like these can only be done in newer versions, like 8.0, but 8.0 does not use those source files any more. Hence, after 5.7 this code is not used any more ......

Not a bug.