List:Commits« Previous MessageNext Message »
From:Guilhem Bichot Date:November 14 2008 2:21pm
Subject:Re: bzr commit into mysql-6.0-backup branch (ingo.struewing:2730)
Bug#38133
View as plain text  
Hello,

Ingo Struewing a écrit, Le 11/13/2008 09:55 AM:
> #At file:///home2/mydev/bzrroot/mysql-6.0-bug38133-2/
> 
>  2730 Ingo Struewing	2008-11-13
>       Bug#38133 - Myisamlog test fails on Windows
>       
>       Updating of a table from the myisam.log file was not possible in most cases.
>       - The initialization of myisamlog set the number of usable file descriptors
>         to one (1!) on Windows.
>       - Every log command to a different file required a close of the previous file.
>         After a 'close' command the closing of the previous file failed because
>         all files were closed already and myisamlog stopped.
>       - File locking on Windows failed for the sequence "exclusive lock, shared
> lock,
>         unlock, exclusive lock". It blocked on the last try to acquire an exclusive
> lock.
>       
>       Fixed by
>       - requesting a reasonable number of file descriptors,
>       - before re-open, close a file only if all file descriptors are in use,
>       - do not re-open a file, if the command is 'close',
>       - unlock a file before every lock.

> per-file messages:
>   mysys/my_lock.c
>     Bug#38133 - Myisamlog test fails on Windows
>     Added unlocking before locking to prevent multiple locks by the same process.
>   sql/handler.cc
>     Bug#38133 - Myisamlog test fails on Windows
>     Changed abort() to DBUG_ABORT().

you could say why this change was needed.

>   storage/myisam/mi_examine_log.c
>     Bug#38133 - Myisamlog test fails on Windows
>     Added member lock_type to file_info.
>     Changed tracking of allocated memory in 'buff' and 'file_info' so that
>     it can be freed in case of errors.
>     Added a check for valid data in the log file.
>     Disabled re-opening for a close command.
>     Reduced closing of files to cases where file descriptors are used up.
>     Added code to re-lock a file after re-open.
>     Pulled close_some_files() out of reopen_closed_file() to have access
>     to max_files.
>   storage/myisam/mi_locking.c
>     Bug#38133 - Myisamlog test fails on Windows
>     Disabled locking for temporary tables that do not use F_EXTRA_LCK.
>     Added DBUG.


> === modified file 'mysys/my_lock.c'
> --- a/mysys/my_lock.c	2008-09-12 12:57:19 +0000
> +++ b/mysys/my_lock.c	2008-11-13 08:55:37 +0000
> @@ -71,13 +71,23 @@ static int win_lock(File fd, int locktyp
>      /* write lock is mapped to an exclusive lock. */
>      dwFlags= LOCKFILE_EXCLUSIVE_LOCK;
>  
> +  /*
> +    Drop old lock first to avoid double locking.
> +    During analyze of Bug#38133 (Myisamlog test fails on Windows)
> +    I met the situation that the program myisamlog locked the file
> +    exclusively, then additionally shared, then did one unlock, and
> +    then blocked on an attempt to lock it exclusively again.
> +    Unlocking before every lock fixed the problem.
> +  */
> +  (void) UnlockFileEx(hFile, 0, liLength.LowPart, liLength.HighPart, &ov);

I'm anxious to have a fix like this which will in theory affect the 
entire server, only to fix something exhibited by myisamlog, without 
having a clear understanding of what is actually wrong (what module is 
guilty).
When I read the comment above, my impression is that myisamlog is doing 
something wrong:
- why lock shared if already has an exclusive lock?
- why re-lock exclusive if already has an exclusive lock?
It is "expected" that myisamlog does weird things: it's code which was 
dead for >6 years, is very little tested in mysql-test, it may have old 
bugs or new bugs due to other code's evolution. So I'd prefer if we dug 
into myisamlog and fixed myisamlog than the server-wide locking code.

In other words, I believe the bug could be in the user of mysys 
(myisamlog) more than in mysys. If the server didn't have any problem 
with mysys of before-the-patch, given that the server is much more used 
than myisamlog, I'd trust mysys more than myisamlog.

> === modified file 'storage/myisam/mi_examine_log.c'
> --- a/storage/myisam/mi_examine_log.c	2008-10-20 09:16:47 +0000
> +++ b/storage/myisam/mi_examine_log.c	2008-11-13 08:55:37 +0000
> @@ -69,6 +69,13 @@ struct file_info {
>    my_bool closed;
>    /** If this table matches the inclusion rules (or has to be ignored) */
>    my_bool used;
> +  /**
> +    Lock type, set by last MI_LOG_LOCK command. Initialized to F_UNLCK
> +    at MI_LOG_OPEN. This is not changed if the file is temporarily
> +    closed. So it can be re-locked on re-open.
> +  */
> +  int lock_type;
> +  /* File was last accessed at this log entry number. */

could use /** */

>    ulong accessed;
>  };
>  
> @@ -186,6 +194,14 @@ int mi_examine_log(MI_EXAMINE_LOG_PARAM 
>    (void) init_key_cache(dflt_key_cache,KEY_CACHE_BLOCK_SIZE,KEY_CACHE_SIZE,
>                          0, 0);
>  
> +  /*
> +    Initialize members of file_info that are used for pointing to
> +    allocated memory. At the error labels we want to be able to free it.
> +  */
> +  file_info.name= NULL;
> +  file_info.show_name= NULL;
> +  file_info.record= NULL;

A super-critical comment: you could have put all inits on one line:
file_info.name= file_info.show_name= file_info.record= NULL;

>    files_open=0; access_time=0;
>    while (access_time++ != mi_exl->number_of_commands &&
>  	 !my_b_read(&cache, head, 1))
 > @@ -194,6 +210,18 @@ int mi_examine_log(MI_EXAMINE_LOG_PARAM
 >      head_ptr= head;
 >      command=(uint) head_ptr[0];
 >      command-= (big_numbers= (command & MI_LOG_BIG_NUMBERS));
 > +    /*
 > +      'command' is a number that is used to index arrays. Better check
 > +      it for range.
 > +    */
 > +    if (command >= MI_LOG_END_SENTINEL)
 > +    {
 > +      my_errno= HA_ERR_WRONG_COMMAND;
 > +      fprintf(stderr,"Unknown command %u in logfile at position %s\n",
 > +              command, llstr(isamlog_filepos, llbuff));
 > +      fflush(stderr);
 > +      goto end;

After "end:" we don't make use of my_errno, so no need to set it.
Or, set it, but use "goto err", in that case you can remove the 
fflush(stderr).

 > +    }
 >      if (big_numbers != 0)
 >        big_numbers= 1;
 >      if (my_b_read(&cache, head, head_len[command][big_numbers] - 1))

> @@ -219,23 +247,64 @@ int mi_examine_log(MI_EXAMINE_LOG_PARAM 
>      }
>      else
>        isamlog_process= file_info.process= result= 0;
> +
> +    DBUG_PRINT("myisamlog", ("command: %u '%s'  process: %ld  filenr: %d",
> +                             command, mi_log_command_name[command],
> +                             file_info.process, file_info.filenr));
> +    /*
> +      Try to find the file with file_info.process and file_info.filenr
> +      in the file tree. The search function, as registered with
> +      init_tree() is file_info_compare(). If the file does not exist in
> +      the tree, most commands will be ignored for this file.
> +    */
>      if ((curr_file_info=(struct file_info*) tree_search(&tree, &file_info,
>  							tree.custom_arg)))
>      {
> +      DBUG_PRINT("myisamlog", ("found info: 0x%lx  file: '%s'  "
> +                               "used: %d  closed: %d",
> +                               (long) curr_file_info, curr_file_info->name,
> +                               curr_file_info->used,
> curr_file_info->closed));

You can change 0x%lx to %p.

>        curr_file_info->accessed=access_time;
> -      if (mi_exl->update && curr_file_info->used &&
> curr_file_info->closed)
> +      /*
> +        If the file has been closed due to lack of file descriptors,
> +        re-open it to execute the command.
> +        No need to re-open for the MI_LOG_CLOSE command.

Ok, but you still go into "case MI_LOG_CLOSE" which is good as it does a 
tree_delete(), which we don't want to skip.

> @@ -255,13 +324,19 @@ int mi_examine_log(MI_EXAMINE_LOG_PARAM 
>      }
>      switch ((enum myisam_log_commands) command) {
>      case MI_LOG_OPEN:
> +      DBUG_PRINT("myisamlog", ("command MI_LOG_OPEN"));

I believe you don't need such line per command, because you already have:
     DBUG_PRINT("info",("command: %u '%s' curr_file_info: 0x%lx used:
%u",
                       command, mi_log_command_name[command],
                       (ulong)curr_file_info,
                       curr_file_info ? curr_file_info->used : 0));
before the "switch".

>        if (curr_file_info)
>  	printf("\nWarning: %s is opened with same process and filenumber\n"
>                 "Maybe you should use the -P option ?\n",
>  	       curr_file_info->show_name);
> -      file_info.name=0;
> -      file_info.show_name=0;
> -      file_info.record=0;
> +      /*
> +        These file_info memebers should be non-null only during an open

members

> +        operation. Initially and after open they should be nulled.
> +        That way we can free them in case of a jump to an error label.
> +      */
> +      DBUG_ASSERT(!file_info.name);
> +      DBUG_ASSERT(!file_info.show_name);
> +      DBUG_ASSERT(!file_info.record);
>        length= big_numbers ? mi_uint4korr(head_ptr) : mi_uint2korr(head_ptr);
>        if (read_string(&cache, (uchar **)&file_info.name, length))
>  	goto err;
> @@ -330,8 +405,19 @@ int mi_examine_log(MI_EXAMINE_LOG_PARAM 
>  	  goto end;
>  	files_open++;
>  	file_info.closed=0;
> +        /* After explicit open, file is not locked. */
> +        file_info.lock_type= F_UNLCK;
>        }
>        (void) tree_insert(&tree, (uchar*) &file_info, 0, tree.custom_arg);

interesting... we don't trap malloc failure here :(

> +
> +      /*
> +        tree_insert() copied file_info. Avoid that the allocated members
> +        are freed while in use.

Could you please emphasize by writing:
"tree_insert() copied file_info (copied pointers)"
If not NULL-ed, they would not be freed while in use: the my_free() at 
the end of the program would rather double-free them, because 
file_info_free() (called via delete_tree()) would already have freed them.

> +      */
> +      file_info.name= NULL;
> +      file_info.show_name= NULL;
> +      file_info.record= NULL;
> +
>        if (file_info.used)
>        {
>  	if (mi_exl->verbose && !mi_exl->record_pos_file)
> @@ -595,6 +687,7 @@ int mi_examine_log(MI_EXAMINE_LOG_PARAM 
>        }
>        break;
>      case MI_LOG_LOCK:
> +      DBUG_PRINT("myisamlog", ("command MI_LOG_LOCK"));
>        length= big_numbers ? mi_uint4korr(head_ptr) : mi_uint2korr(head_ptr);
>        DBUG_ASSERT(length == 4);
>        if (my_b_read(&cache, head, length))
> @@ -607,14 +700,24 @@ int mi_examine_log(MI_EXAMINE_LOG_PARAM 
>  	printf_log(mi_exl->verbose, isamlog_process, isamlog_filepos,
>                     "%s: %s(%d) -> %d",FILENAME(curr_file_info),
>  		   mi_log_command_name[command],lock_command,result);
> -      if (mi_exl->update && curr_file_info &&
> !curr_file_info->closed)
> +      if (mi_exl->update && curr_file_info)
>        {
> -	if (mi_lock_database(curr_file_info->isam,lock_command) !=
> -	    (int) result)
> -	  goto com_err;
> +        /* Remember lock type for re-open. */
> +        curr_file_info->lock_type= lock_command;
> +        if (!curr_file_info->closed)

This is not something you changed, but normally when we come in "case 
MI_LOG_LOCK" the table should be open (if it was closed due to shortage 
of file descriptors, we would have reopened it before the "switch") so 
could you please put a DBUG_ASSERT(!curr_file_info->closed) ?

> +        {
> +          DBUG_PRINT("myisamlog",
> +                     ("lock info: 0x%lx  process: %ld  filenr: %d  file: '%s'",
> +                      (long) curr_file_info, curr_file_info->process,
> +                      curr_file_info->filenr, curr_file_info->name));
> +          if (mi_lock_database(curr_file_info->isam,lock_command) !=
> +              (int) result)
> +            goto com_err;
> +        }
>        }
>        break;
> @@ -742,6 +855,8 @@ static int test_when_accessed (struct fi
>  static void file_info_free(struct file_info *fileinfo)
>  {
>    DBUG_ENTER("file_info_free");
> +  DBUG_PRINT("myisamlog", ("freeing info: 0x%lx  file: '%s'",
> +                           (long) fileinfo, fileinfo->name));

you could use %p

>    /* The 2 conditions below can be true only if 'update' */
>    if (!fileinfo->closed)
>      (void) mi_close_care_state(fileinfo->isam);
> @@ -765,6 +880,9 @@ static int close_some_file(TREE *tree)
>                     (void*) &access_param,left_root_right);
>    if (!access_param.found)
>      return 1;			/* No open file that is possibly to close */
> +  DBUG_PRINT("myisamlog", ("closing info: 0x%lx  file: '%s'",
> +                           (long) access_param.found,
> +                           access_param.found->name));

you could use %p

>    if (mi_close_care_state(access_param.found->isam))
>      return 1;
>    access_param.found->closed=1;
> @@ -772,20 +890,35 @@ static int close_some_file(TREE *tree)
>  }
>  
>  
> -static int reopen_closed_file(TREE *tree, struct file_info *fileinfo)
> +static int reopen_closed_file(struct file_info *fileinfo)
>  {
>    char name[FN_REFLEN];
> -  if (close_some_file(tree))
> -    return 1;				/* No file to close */
> +  DBUG_ENTER("reopen_closed_file");
> +
>    strmov(name,fileinfo->show_name);
>    if (fileinfo->id > 1)
>      *strrchr(name,'<')='\0';		/* Remove "<id>" */
>  
>    if (!(fileinfo->isam= mi_open(name, O_RDWR,
>                                  HA_OPEN_FOR_REPAIR | HA_OPEN_WAIT_IF_LOCKED)))
> -    return 1;
> +    DBUG_RETURN(1);
>    fileinfo->closed=0;
> -  return 0;
> +  /*
> +    If the file was explicitly locked when we needed to close it due to
> +    lack of file descriptors, we re-lock it after re-open.
> +  */
> +  if (fileinfo->lock_type != F_UNLCK)
> +  {
> +    DBUG_PRINT("myisamlog",
> +               ("lock info: 0x%lx  process: %ld  filenr: %d  file: '%s'",

you could use %p

> +                (long) fileinfo, fileinfo->process,
> +                fileinfo->filenr, fileinfo->name));
> +    (void) mi_lock_database(fileinfo->isam, fileinfo->lock_type);

why not catch errors here and return 1?

> +  }
> +  DBUG_PRINT("myisamlog", ("re-opened info: 0x%lx  lock_type: %d  file: '%s'",

you could use %p

> +                           (long) fileinfo, fileinfo->lock_type,
> +                           fileinfo->name));
> +  DBUG_RETURN(0);
>  }
>  
>  	/* Try to find record with uniq key */
> 
> === modified file 'storage/myisam/mi_locking.c'
> --- a/storage/myisam/mi_locking.c	2008-09-09 19:02:38 +0000
> +++ b/storage/myisam/mi_locking.c	2008-11-13 08:55:37 +0000
> @@ -104,7 +104,11 @@ int mi_lock_database(MI_INFO *info, int 
>            mi_print_error(share, HA_ERR_CRASHED);
>            mi_mark_crashed(info);
>          }
> -	if (info->lock_type != F_EXTRA_LCK)
> +        /*
> +          If we have a pseudo lock (F_EXTRA_LCK) or a temporary table,
> +          skip file locking.
> +        */
> +        if ((info->lock_type != F_EXTRA_LCK) && !share->temporary)
>  	{
>  	  if (share->r_locks)
>  	  {					/* Only read locks left */

You explained that this change's reason was:
"I believe, I have seen cases where a temporary table did not take a
F_EXTRA_LCK lock. I want to prevent locking in this case too."

It is a desirable goal, but more of an optimization than a bug fix.
Also, normally, there should not be such cases: temporary tables should 
always use F_EXTRA_LCK, like here:

int ha_myisam::external_lock(THD *thd, int lock_type)
{
   file->in_use.data= thd;
   return mi_lock_database(file, !table->s->tmp_table ?
			  lock_type : ((lock_type == F_UNLCK) ?
				       F_UNLCK : F_EXTRA_LCK));
}

There is this code at start of mi_lock_database():
   if (lock_type == F_EXTRA_LCK)                 /* Used by TMP tables */
   {
     ++share->w_locks;
     ++share->tot_locks;
     info->lock_type= lock_type;
     info->s->in_use= list_add(info->s->in_use, &info->in_use);
     DBUG_RETURN(0);
   }
I believe this is the optimized code which is intended for temporary 
tables. If myisamlog does not pass F_EXTRA_LCK, it may be because it's a 
too old program which needs some fixing. By the way, myisamlog should 
not log anything about temporary tables at all.

I suggest to add

else
   DBUG_ASSERT(!share->temporary);

to the if() block just above, run the tests and see what happens.

Another note: isn't it worrying that if short of file descriptors, 
close_some_file() closes a file which may be locked? I hope that the 
mi_close() code is ready for such awkward situation, but I'm not sure. 
Maybe close_some_file() should, if appropriate, unlock the table before 
closing it.

-- 
Mr. Guilhem Bichot <guilhem@stripped>
Sun Microsystems / MySQL, Lead Software Engineer
Bordeaux, France
www.sun.com / www.mysql.com

Thread
bzr commit into mysql-6.0-backup branch (ingo.struewing:2730) Bug#38133Ingo Struewing13 Nov
  • Re: bzr commit into mysql-6.0-backup branch (ingo.struewing:2730)Bug#38133Guilhem Bichot14 Nov
    • Re: bzr commit into mysql-6.0-backup branch (ingo.struewing:2730)Bug#38133Ingo Strüwing21 Nov
      • Re: bzr commit into mysql-6.0-backup branch (ingo.struewing:2730)Bug#38133Guilhem Bichot27 Nov
        • Re: bzr commit into mysql-6.0-backup branch (ingo.struewing:2730)Bug#38133Ingo Strüwing29 Nov
          • Re: bzr commit into mysql-6.0-backup branch (ingo.struewing:2730)Bug#38133Guilhem Bichot4 Dec
            • Re: bzr commit into mysql-6.0-backup branch (ingo.struewing:2730)Bug#38133Ingo Strüwing5 Dec