List:Commits« Previous MessageNext Message »
From:Davi Arnaut Date:November 4 2009 2:11pm
Subject:Re: bzr commit into mysql-5.1-bugteam branch (vvaintroub:3156)
Bug#31621 Bug#47571
View as plain text  
Hi Vladislav,

On 11/2/09 8:20 PM, Vladislav Vaintroub wrote:
> #At file:///H:/bzr/mysql-5.1-bugteam/ based on
> revid:mattias.jonsson@stripped
>
>   3156 Vladislav Vaintroub	2009-11-02
>        Bug#47571: idle named pipe connection is unkillable
>        Bug#31621: Windows server hanging during shutdown using named pipes
>                   and idle connection
>
>        Problem: when idle pipe connection is forcefully closed with KILL
>        statement or when the server goes down, thread that is closing connection
>        would hang infinitely in CloseHandle(). The reason for the hang is that
>        named pipe operations are performed synchronously. In this mode all IOs
>        on pipe are serialized, that is CloseHandle() will not abort ReadFile()
>        in another thread, but wait for ReadFile() to complete.
>
>        The fix implements asynchrnous mode for named pipes, where operation of file

asynchrnous -> asynchronous

>        are not synchronized. Read/Write operation would fire an async IO and wait
> for
>        either IO completion or timeout.
>
>        Note, that with this patch timeouts are properly handled for named pipes.
>
>        Post-review: Win32 timeout code has been fixed for named pipes and shared
>        memory. We do not store pointer to NET in vio structure, only the read and
>        write timeouts.
>       @ include/violite.h
>          Add pipe_overlapped to Vio structure for async IO for named pipes.
>       @ sql-common/client.c
>          Use asynchronous pipe IO.
>       @ sql/mysqld.cc
>          Use asynchronous pipe IO.
>       @ vio/vio.c
>          -Refactor timeouts for win32 protocols: shared memory and named pipes.
>          Store read/write timeout in VIO structure, instead of storing pointer
>          to NET. New function vio_win32_timeout called indirectly via
>          vio_timeout changes these values.
>       @ vio/vio_priv.h
>          Remove vio_ignore_timeout.
>          Add vio_win32_timeout to be used for named pipes and shared memory.
>       @ vio/viosocket.c
>          Use async IO for named pipes.
>          After issuing IO, wait for either IO completion, pipe_close_event
>          or timeout.
>
>          Refactor timeouts for named pipe and shared memory.
>
>      modified:
>        include/violite.h
>        sql-common/client.c
>        sql/mysqld.cc
>        vio/vio.c
>        vio/vio_priv.h
>        vio/viosocket.c
> === modified file 'include/violite.h'
> --- a/include/violite.h	2009-07-23 11:53:28 +0000
> +++ b/include/violite.h	2009-11-02 22:19:58 +0000
> @@ -44,7 +44,7 @@ enum enum_vio_type
>   Vio*	vio_new(my_socket sd, enum enum_vio_type type, uint flags);
>   #ifdef __WIN__
>   Vio* vio_new_win32pipe(HANDLE hPipe);
> -Vio* vio_new_win32shared_memory(NET *net,HANDLE handle_file_map,
> +Vio* vio_new_win32shared_memory(HANDLE handle_file_map,
>                                   HANDLE handle_map,
>                                   HANDLE event_server_wrote,
>                                   HANDLE event_server_read,
> @@ -221,7 +221,11 @@ struct st_vio
>     HANDLE  event_conn_closed;
>     size_t  shared_memory_remain;
>     char    *shared_memory_pos;
> -  NET     *net;
>   #endif /* HAVE_SMEM */
> +#ifdef _WIN32
> +  OVERLAPPED pipe_overlapped;
> +  DWORD read_timeout_millis;
> +  DWORD write_timeout_millis;

The official abbreviation of milliseconds is ms.

> +#endif
>   };
>   #endif /* vio_violite_h_ */
>
> === modified file 'sql-common/client.c'
> --- a/sql-common/client.c	2009-08-31 19:40:33 +0000
> +++ b/sql-common/client.c	2009-11-02 22:19:58 +0000
> @@ -389,7 +389,7 @@ HANDLE create_named_pipe(MYSQL *mysql, u
>   			    0,
>   			    NULL,
>   			    OPEN_EXISTING,
> -			    0,
> +			    FILE_FLAG_OVERLAPPED,
>   			    NULL )) != INVALID_HANDLE_VALUE)
>         break;
>       if (GetLastError() != ERROR_PIPE_BUSY)
> @@ -623,7 +623,7 @@ HANDLE create_shared_memory(MYSQL *mysql
>   err2:
>     if (error_allow == 0)
>     {
> -    net->vio= vio_new_win32shared_memory(net,handle_file_map,handle_map,
> +    net->vio= vio_new_win32shared_memory(handle_file_map,handle_map,
>                                            event_server_wrote,
>                                            event_server_read,event_client_wrote,
>                                            event_client_read,event_conn_closed);
> @@ -2028,7 +2028,7 @@ CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,cons
>       }
>       else
>       {
> -      net->vio=vio_new_win32pipe(hPipe);
> +      net->vio= vio_new_win32pipe(hPipe);
>         my_snprintf(host_info=buff, sizeof(buff)-1,
>                     ER(CR_NAMEDPIPE_CONNECTION), unix_socket);
>       }
>
> === modified file 'sql/mysqld.cc'
> --- a/sql/mysqld.cc	2009-09-29 15:38:40 +0000
> +++ b/sql/mysqld.cc	2009-11-02 22:19:58 +0000
> @@ -1717,7 +1717,7 @@ static void network_init(void)
>       saPipeSecurity.lpSecurityDescriptor =&sdPipeDescriptor;
>       saPipeSecurity.bInheritHandle = FALSE;
>       if ((hPipe= CreateNamedPipe(pipe_name,
> -				PIPE_ACCESS_DUPLEX,
> +				PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,
>   				PIPE_TYPE_BYTE |
>   				PIPE_READMODE_BYTE |
>   				PIPE_WAIT,
> @@ -5203,17 +5203,26 @@ pthread_handler_t handle_connections_soc
>   pthread_handler_t handle_connections_namedpipes(void *arg)
>   {
>     HANDLE hConnectedPipe;
> -  BOOL fConnected;
> +  OVERLAPPED connectOverlapped = {0};
>     THD *thd;
>     my_thread_init();
>     DBUG_ENTER("handle_connections_namedpipes");
> -  (void) my_pthread_getprio(pthread_self());		// For debugging
> +  connectOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

Missing check for failure to create the event.

>     DBUG_PRINT("general",("Waiting for named pipe connections."));
>     while (!abort_loop)
>     {
>       /* wait for named pipe connection */
> -    fConnected = ConnectNamedPipe(hPipe, NULL);
> +    BOOL fConnected= ConnectNamedPipe(hPipe,&connectOverlapped);
> +    if (!fConnected&&  (GetLastError() == ERROR_IO_PENDING))
> +    {
> +        /*
> +          ERROR_IO_PENDING says async IO has started but not yet finished.
> +          GetOverlappedResult will wait for completion.
> +        */
> +        DWORD bytes;
> +        fConnected= GetOverlappedResult(hPipe,&connectOverlapped,&bytes,
> TRUE);
> +    }
>       if (abort_loop)
>         break;
>       if (!fConnected)
> @@ -5222,7 +5231,7 @@ pthread_handler_t handle_connections_nam
>       {
>         CloseHandle(hPipe);
>         if ((hPipe= CreateNamedPipe(pipe_name,
> -                                  PIPE_ACCESS_DUPLEX,
> +                                  PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,

Please add space (between |) and follow the style.

>                                     PIPE_TYPE_BYTE |
>                                     PIPE_READMODE_BYTE |
>                                     PIPE_WAIT,
> @@ -5242,7 +5251,7 @@ pthread_handler_t handle_connections_nam
>       hConnectedPipe = hPipe;
>       /* create new pipe for new connection */
>       if ((hPipe = CreateNamedPipe(pipe_name,
> -				 PIPE_ACCESS_DUPLEX,
> +				 PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,
>   				 PIPE_TYPE_BYTE |
>   				 PIPE_READMODE_BYTE |
>   				 PIPE_WAIT,

What a strange code... why it creates two new pipes if it fails to 
establish a new connection? Could you add some comments to the code?

> @@ -5264,7 +5273,7 @@ pthread_handler_t handle_connections_nam
>         CloseHandle(hConnectedPipe);
>         continue;
>       }
> -    if (!(thd->net.vio = vio_new_win32pipe(hConnectedPipe)) ||
> +    if (!(thd->net.vio= vio_new_win32pipe(hConnectedPipe)) ||
>   	my_net_init(&thd->net, thd->net.vio))
>       {
>         close_connection(thd, ER_OUT_OF_RESOURCES, 1);
> @@ -5275,7 +5284,7 @@ pthread_handler_t handle_connections_nam
>       thd->security_ctx->host= my_strdup(my_localhost, MYF(0));
>       create_new_thread(thd);
>     }
> -
> +  CloseHandle(connectOverlapped.hEvent);
>     decrement_handler_count();
>     DBUG_RETURN(0);
>   }
> @@ -5452,8 +5461,7 @@ pthread_handler_t handle_connections_sha
>         errmsg= "Could not set client to read mode";
>         goto errorconn;
>       }
> -    if (!(thd->net.vio= vio_new_win32shared_memory(&thd->net,
> -                                                   handle_client_file_map,
> +    if (!(thd->net.vio= vio_new_win32shared_memory(handle_client_file_map,
>                                                      handle_client_map,
>                                                      event_client_wrote,
>                                                      event_client_read,
>
> === modified file 'vio/vio.c'
> --- a/vio/vio.c	2007-05-10 09:59:39 +0000
> +++ b/vio/vio.c	2009-11-02 22:19:58 +0000
> @@ -43,7 +43,7 @@ static void vio_init(Vio* vio, enum enum
>     if ((flags&  VIO_BUFFERED_READ)&&
>         !(vio->read_buffer= (char*)my_malloc(VIO_READ_BUFFER_SIZE, MYF(MY_WME))))
>       flags&= ~VIO_BUFFERED_READ;
> -#ifdef __WIN__
> +#ifdef _WIN32
>     if (type == VIO_TYPE_NAMEDPIPE)
>     {
>       vio->viodelete	=vio_delete;
> @@ -59,9 +59,16 @@ static void vio_init(Vio* vio, enum enum
>       vio->in_addr	=vio_in_addr;
>       vio->vioblocking	=vio_blocking;
>       vio->is_blocking	=vio_is_blocking;
> -    vio->timeout	=vio_ignore_timeout;
> +
> +    vio->timeout=vio_win32_timeout;
> +    /* Set default timeout */
> +    vio->read_timeout_millis = INFINITE;
> +    vio->write_timeout_millis = INFINITE;

Coding style...

> +    memset(&(vio->pipe_overlapped), 0, sizeof(OVERLAPPED));

No need. The entire vio is zeroed.

> +    vio->pipe_overlapped.hEvent= CreateEvent(NULL, TRUE, FALSE, NULL);
> +    DBUG_VOID_RETURN;
>     }
> -  else					/* default is VIO_TYPE_TCPIP */
>   #endif
>   #ifdef HAVE_SMEM
>     if (type == VIO_TYPE_SHARED_MEMORY)
> @@ -79,9 +86,14 @@ static void vio_init(Vio* vio, enum enum
>       vio->in_addr	=vio_in_addr;
>       vio->vioblocking	=vio_blocking;
>       vio->is_blocking	=vio_is_blocking;
> -    vio->timeout	=vio_ignore_timeout;
> +
> +    /* Currently, shared memory is on Windows only, hence the below is ok*/
> +    vio->timeout= vio_win32_timeout;
> +    /* Set default timeout */
> +    vio->read_timeout_millis= INFINITE;
> +    vio->write_timeout_millis= INFINITE;
> +    DBUG_VOID_RETURN;
>     }
> -  else
>   #endif
>   #ifdef HAVE_OPENSSL
>     if (type == VIO_TYPE_SSL)
> @@ -100,8 +112,8 @@ static void vio_init(Vio* vio, enum enum
>       vio->vioblocking	=vio_ssl_blocking;
>       vio->is_blocking	=vio_is_blocking;
>       vio->timeout	=vio_timeout;
> +    DBUG_VOID_RETURN;
>     }
> -  else					/* default is VIO_TYPE_TCPIP */
>   #endif /* HAVE_OPENSSL */
>     {

Since you removed the if / else logic, this switch becomes unecessary.

>       vio->viodelete	=vio_delete;
> @@ -193,7 +205,7 @@ Vio *vio_new_win32pipe(HANDLE hPipe)
>   }
>
>   #ifdef HAVE_SMEM
> -Vio *vio_new_win32shared_memory(NET *net,HANDLE handle_file_map, HANDLE handle_map,
> +Vio *vio_new_win32shared_memory(HANDLE handle_file_map, HANDLE handle_map,
>                                   HANDLE event_server_wrote, HANDLE
> event_server_read,
>                                   HANDLE event_client_wrote, HANDLE
> event_client_read,
>                                   HANDLE event_conn_closed)
> @@ -212,7 +224,6 @@ Vio *vio_new_win32shared_memory(NET *net
>       vio->event_conn_closed= event_conn_closed;
>       vio->shared_memory_remain= 0;
>       vio->shared_memory_pos= handle_map;
> -    vio->net= net;
>       strmov(vio->desc, "shared memory");
>     }
>     DBUG_RETURN(vio);
>
> === modified file 'vio/vio_priv.h'
> --- a/vio/vio_priv.h	2007-06-05 15:51:30 +0000
> +++ b/vio/vio_priv.h	2009-11-02 22:19:58 +0000
> @@ -22,7 +22,10 @@
>   #include<m_string.h>
>   #include<violite.h>
>
> -void	vio_ignore_timeout(Vio *vio, uint which, uint timeout);
> +#ifdef _WIN32
> +void	vio_win32_timeout(Vio *vio, uint which, uint timeout);
> +#endif
> +
>   void	vio_timeout(Vio *vio,uint which, uint timeout);
>
>   #ifdef HAVE_OPENSSL
>
> === modified file 'vio/viosocket.c'
> --- a/vio/viosocket.c	2009-03-26 23:25:10 +0000
> +++ b/vio/viosocket.c	2009-11-02 22:19:58 +0000
> @@ -261,19 +261,13 @@ int vio_close(Vio * vio)
>   {
>     int r=0;
>     DBUG_ENTER("vio_close");
> -#ifdef __WIN__
> -  if (vio->type == VIO_TYPE_NAMEDPIPE)
> -  {
> -#if defined(__NT__)&&  defined(MYSQL_SERVER)
> -    CancelIo(vio->hPipe);
> -    DisconnectNamedPipe(vio->hPipe);
> -#endif
> -    r=CloseHandle(vio->hPipe);
> -  }
> -  else
> -#endif /* __WIN__ */
> +
>    if (vio->type != VIO_CLOSED)
>     {
> +    DBUG_ASSERT(vio->type ==  VIO_TYPE_TCPIP ||
> +      vio->type == VIO_TYPE_SOCKET ||
> +      vio->type == VIO_TYPE_SSL);

Indentation..

>       DBUG_ASSERT(vio->sd>= 0);
>       if (shutdown(vio->sd, SHUT_RDWR))
>         r= -1;
> @@ -417,44 +411,97 @@ void vio_timeout(Vio *vio, uint which, u
>
>
>   #ifdef __WIN__
> -size_t vio_read_pipe(Vio * vio, uchar* buf, size_t size)
> +
> +/*
> +  Finish pending IO on pipe. Honor wait timeout
> +*/
> +static int pipe_complete_io(Vio* vio, char* buf, size_t size, DWORD timeout_millis)
>   {
>     DWORD length;
> +  DWORD ret;
> +
> +  DBUG_ENTER("pipe_complete_io");
> +
> +  ret= WaitForSingleObject(vio->pipe_overlapped.hEvent, timeout_millis);
> +  /*
> +    WaitForSingleObjects will normally return WAIT_OBJECT_O (success, IO completed)
> +    or WAIT_TIMEOUT.
> +  */
> +  if(ret != WAIT_OBJECT_0)
> +  {
> +    CancelIo(vio->hPipe);
> +    DBUG_PRINT("error",("WaitForSingleObject() returned  %d", ret));
> +    DBUG_RETURN(-1);
> +  }
> +
> +  if (!GetOverlappedResult(vio->hPipe,&(vio->pipe_overlapped),&length,
> FALSE))
> +  {
> +    DBUG_PRINT("error",("GetOverlappedResult() returned last error  %d",
> +      GetLastError()));
> +    DBUG_RETURN(-1);
> +  }
> +
> +  DBUG_RETURN(length);
> +}
> +

Perhaps it would be better to start moving this to a separate file.

> +size_t vio_read_pipe(Vio * vio, uchar *buf, size_t size)
> +{
> +  DWORD bytes_read;
>     DBUG_ENTER("vio_read_pipe");
>     DBUG_PRINT("enter", ("sd: %d  buf: 0x%lx  size: %u", vio->sd, (long) buf,
>                          (uint) size));
>
> -  if (!ReadFile(vio->hPipe, buf, size,&length, NULL))
> -    DBUG_RETURN(-1);
> +  if (!ReadFile(vio->hPipe, buf, (DWORD)size,&bytes_read,
> +&(vio->pipe_overlapped)))
> +  {
> +    if (GetLastError() != ERROR_IO_PENDING)
> +    {
> +      DBUG_PRINT("error",("ReadFile() returned last error %d",
> +        GetLastError()));
> +      DBUG_RETURN((size_t)-1);
> +    }
> +    bytes_read= pipe_complete_io(vio, buf, size,vio->read_timeout_millis);

pipe_complete_io returns signed int (ie -1) and DWORD is unsigned. is 
size_t and DWORD guaranted to be of the same size?

> +  }
>
> -  DBUG_PRINT("exit", ("%d", length));
> -  DBUG_RETURN((size_t) length);
> +  DBUG_PRINT("exit", ("%d", bytes_read));
> +  DBUG_RETURN(bytes_read);
>   }
>
>
>   size_t vio_write_pipe(Vio * vio, const uchar* buf, size_t size)
>   {
> -  DWORD length;
> +  DWORD bytes_written;
>     DBUG_ENTER("vio_write_pipe");
>     DBUG_PRINT("enter", ("sd: %d  buf: 0x%lx  size: %u", vio->sd, (long) buf,
>                          (uint) size));
>
> -  if (!WriteFile(vio->hPipe, (char*) buf, size,&length, NULL))
> -    DBUG_RETURN(-1);
> +  if (!WriteFile(vio->hPipe, buf, (DWORD)size,&bytes_written,
> +&(vio->pipe_overlapped)))
> +  {
> +    if (GetLastError() != ERROR_IO_PENDING)
> +    {
> +      DBUG_PRINT("vio_error",("WriteFile() returned last error %d",
> +        GetLastError()));
> +      DBUG_RETURN((size_t)-1);
> +    }
> +    bytes_written = pipe_complete_io(vio, (char *)buf, size,
> +        vio->write_timeout_millis);
> +  }

Coding style..

> -  DBUG_PRINT("exit", ("%d", length));
> -  DBUG_RETURN((size_t) length);
> +  DBUG_PRINT("exit", ("%d", bytes_written));
> +  DBUG_RETURN(bytes_written);
>   }
>
> +
>   int vio_close_pipe(Vio * vio)
>   {
>     int r;
>     DBUG_ENTER("vio_close_pipe");
> -#if defined(__NT__)&&  defined(MYSQL_SERVER)
> -  CancelIo(vio->hPipe);
> +
> +  CloseHandle(vio->pipe_overlapped.hEvent);

Shouldn't this be the last thing to be closed? or it doesn't matter as 
CloseHandle is async?

>     DisconnectNamedPipe(vio->hPipe);
> -#endif
> -  r=CloseHandle(vio->hPipe);
> +  r= CloseHandle(vio->hPipe);
>     if (r)
>     {
>       DBUG_PRINT("vio_error", ("close() failed, error: %d",GetLastError()));
> @@ -466,10 +513,23 @@ int vio_close_pipe(Vio * vio)
>   }
>
>
> -void vio_ignore_timeout(Vio *vio __attribute__((unused)),
> -			uint which __attribute__((unused)),
> -			uint timeout __attribute__((unused)))
> +void vio_win32_timeout(Vio *vio, uint which , uint timeout_sec)
>   {
> +    DWORD timeout_millis;
> +    /*
> +      Windows is measuring timeouts in milliseconds. Check for possible int
> +      overflow.
> +    */
> +    if (timeout_sec>  UINT_MAX/1000)
> +      timeout_millis= INFINITE;
> +    else
> +      timeout_millis= timeout_sec * 1000;
> +
> +    /* which == 1 means "write", which == 0 means "read".*/
> +    if(which)
> +      vio->write_timeout_millis= timeout_millis;
> +    else
> +      vio->read_timeout_millis= timeout_millis;

Four space indentation -- we use only two.

>   }
>
>
> @@ -504,7 +564,7 @@ size_t vio_read_shared_memory(Vio * vio,
>            WAIT_ABANDONED_0 and WAIT_TIMEOUT - fail.  We can't read anything
>         */
>         if (WaitForMultipleObjects(array_elements(events), events, FALSE,
> -                                 vio->net->read_timeout*1000) !=
> WAIT_OBJECT_0)
> +                                 vio->read_timeout_millis) != WAIT_OBJECT_0)
>         {
>           DBUG_RETURN(-1);
>         };
> @@ -561,7 +621,7 @@ size_t vio_write_shared_memory(Vio * vio
>     while (remain != 0)
>     {
>       if (WaitForMultipleObjects(array_elements(events), events, FALSE,
> -                               vio->net->write_timeout*1000) !=
> WAIT_OBJECT_0)
> +                               vio->write_timeout_millis) != WAIT_OBJECT_0)
>       {
>         DBUG_RETURN((size_t) -1);
>       }
>
>
>
>
>

Otherwise looks good.
Thread
bzr commit into mysql-5.1-bugteam branch (vvaintroub:3156) Bug#31621Bug#47571Vladislav Vaintroub2 Nov
  • Re: bzr commit into mysql-5.1-bugteam branch (vvaintroub:3156)Bug#31621 Bug#47571Davi Arnaut4 Nov