1
0
mirror of https://github.com/MariaDB/server.git synced 2025-05-19 01:41:20 +03:00
unknown aeec459369 Fix for BUG#17486: IM: race condition on exit.
The problem was that IM stoped guarded instances on shutdown,
but didn't wait for them to stop.

The fix is to wait for guarded instances to stop before exitting
from the main thread.

The idea is that Instance-monitoring thread should add itself
to Thread_registry so that it will be taken into account on shutdown.
However, Thread_registry should not signal it on shutdown in order to
not interrupt wait()/waitpid().


server-tools/instance-manager/guardian.cc:
  Be more verbose.
server-tools/instance-manager/instance.cc:
  Register mysqld-monitoring thread in Thread_registry.
server-tools/instance-manager/instance.h:
  Add reference to Thread_registry.
server-tools/instance-manager/instance_map.cc:
  Pass Thread_registry reference to Instance.
server-tools/instance-manager/instance_map.h:
  Add reference to Thread_registry.
server-tools/instance-manager/listener.cc:
  Be more verbose.
server-tools/instance-manager/manager.cc:
  Be more verbose.
server-tools/instance-manager/mysql_connection.cc:
  Eliminate type-conversion warnings.
server-tools/instance-manager/thread_registry.cc:
  Be more verbose.
server-tools/instance-manager/thread_registry.h:
  Eliminate copy&paste, make impl-specific constructor private.
2006-10-24 18:23:16 +04:00

403 lines
10 KiB
C++

/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#if defined(__GNUC__) && defined(USE_PRAGMA_IMPLEMENTATION)
#pragma implementation
#endif
#include "listener.h"
#include <my_global.h>
#include <mysql.h>
#include <violite.h>
#include <sys/stat.h>
#ifndef __WIN__
#include <sys/un.h>
#endif
#include "instance_map.h"
#include "log.h"
#include "mysql_connection.h"
#include "options.h"
#include "portability.h"
#include "priv.h"
#include "thread_registry.h"
static void set_non_blocking(int socket)
{
#ifndef __WIN__
int flags= fcntl(socket, F_GETFL, 0);
fcntl(socket, F_SETFL, flags | O_NONBLOCK);
#else
u_long arg= 1;
ioctlsocket(socket, FIONBIO, &arg);
#endif
}
static void set_no_inherit(int socket)
{
#ifndef __WIN__
int flags= fcntl(socket, F_GETFD, 0);
fcntl(socket, F_SETFD, flags | FD_CLOEXEC);
#endif
}
/*
Listener_thread - incapsulates listening functionality
*/
class Listener_thread: public Listener_thread_args
{
public:
Listener_thread(const Listener_thread_args &args);
~Listener_thread();
void run();
private:
static const int LISTEN_BACK_LOG_SIZE= 5; /* standard backlog size */
ulong total_connection_count;
Thread_info thread_info;
int sockets[2];
int num_sockets;
fd_set read_fds;
private:
void handle_new_mysql_connection(Vio *vio);
int create_tcp_socket();
int create_unix_socket(struct sockaddr_un &unix_socket_address);
};
Listener_thread::Listener_thread(const Listener_thread_args &args) :
Listener_thread_args(args.thread_registry, args.user_map, args.instance_map)
,total_connection_count(0)
,thread_info(pthread_self(), TRUE)
,num_sockets(0)
{
}
Listener_thread::~Listener_thread()
{
}
/*
Listener_thread::run() - listen all supported sockets and spawn a thread
to handle incoming connection.
Using 'die' in case of syscall failure is OK now - we don't hold any
resources and 'die' kills the signal thread automatically. To be rewritten
one day.
See also comments in mysqlmanager.cc to picture general Instance Manager
architecture.
*/
void Listener_thread::run()
{
int i, n= 0;
log_info("Listener_thread: started.");
#ifndef __WIN__
/* we use this var to check whether we are running on LinuxThreads */
pid_t thread_pid;
thread_pid= getpid();
struct sockaddr_un unix_socket_address;
/* set global variable */
linuxthreads= (thread_pid != manager_pid);
#endif
thread_registry.register_thread(&thread_info);
my_thread_init();
FD_ZERO(&read_fds);
/* I. prepare 'listen' sockets */
if (create_tcp_socket())
goto err;
#ifndef __WIN__
if (create_unix_socket(unix_socket_address))
goto err;
#endif
/* II. Listen sockets and spawn childs */
for (i= 0; i < num_sockets; i++)
n= max(n, sockets[i]);
n++;
timeval tv;
while (!thread_registry.is_shutdown())
{
fd_set read_fds_arg= read_fds;
/*
We should reintialize timer as on linux it is modified
to reflect amount of time not slept.
*/
tv.tv_sec= 0;
tv.tv_usec= 100000;
/*
When using valgrind 2.0 this syscall doesn't get kicked off by a
signal during shutdown. This results in failing assert
(Thread_registry::~Thread_registry). Valgrind 2.2 works fine.
*/
int rc= select(n, &read_fds_arg, 0, 0, &tv);
if (rc == 0 || rc == -1)
{
if (rc == -1 && errno != EINTR)
log_error("Listener_thread: select() failed, %s",
strerror(errno));
continue;
}
for (int socket_index= 0; socket_index < num_sockets; socket_index++)
{
/* Assuming that rc > 0 as we asked to wait forever */
if (FD_ISSET(sockets[socket_index], &read_fds_arg))
{
int client_fd= accept(sockets[socket_index], 0, 0);
/* accept may return -1 (failure or spurious wakeup) */
if (client_fd >= 0) // connection established
{
set_no_inherit(client_fd);
Vio *vio= vio_new(client_fd, socket_index == 0 ?
VIO_TYPE_SOCKET : VIO_TYPE_TCPIP,
socket_index == 0 ? 1 : 0);
if (vio != 0)
handle_new_mysql_connection(vio);
else
{
shutdown(client_fd, SHUT_RDWR);
close(client_fd);
}
}
}
}
}
/* III. Release all resources and exit */
log_info("Listener_thread: shutdown requested, exiting...");
for (i= 0; i < num_sockets; i++)
close(sockets[i]);
#ifndef __WIN__
unlink(unix_socket_address.sun_path);
#endif
thread_registry.unregister_thread(&thread_info);
my_thread_end();
log_info("Listener_thread: finished.");
return;
err:
// we have to close the ip sockets in case of error
for (i= 0; i < num_sockets; i++)
close(sockets[i]);
thread_registry.unregister_thread(&thread_info);
thread_registry.request_shutdown();
my_thread_end();
return;
}
int Listener_thread::create_tcp_socket()
{
/* value to be set by setsockopt */
int arg= 1;
int ip_socket= socket(AF_INET, SOCK_STREAM, 0);
if (ip_socket == INVALID_SOCKET)
{
log_error("Listener_thead: socket(AF_INET) failed, %s",
strerror(errno));
return -1;
}
struct sockaddr_in ip_socket_address;
bzero(&ip_socket_address, sizeof(ip_socket_address));
ulong im_bind_addr;
if (Options::Main::bind_address != 0)
{
im_bind_addr= (ulong) inet_addr(Options::Main::bind_address);
if (im_bind_addr == INADDR_NONE)
im_bind_addr= htonl(INADDR_ANY);
}
else
im_bind_addr= htonl(INADDR_ANY);
uint im_port= Options::Main::port_number;
ip_socket_address.sin_family= AF_INET;
ip_socket_address.sin_addr.s_addr= im_bind_addr;
ip_socket_address.sin_port= (unsigned short)
htons((unsigned short) im_port);
setsockopt(ip_socket, SOL_SOCKET, SO_REUSEADDR, (char*) &arg, sizeof(arg));
if (bind(ip_socket, (struct sockaddr *) &ip_socket_address,
sizeof(ip_socket_address)))
{
log_error("Listener_thread: bind(ip socket) failed, '%s'",
strerror(errno));
close(ip_socket);
return -1;
}
if (listen(ip_socket, LISTEN_BACK_LOG_SIZE))
{
log_error("Listener_thread: listen(ip socket) failed, %s",
strerror(errno));
close(ip_socket);
return -1;
}
/* set the socket nonblocking */
set_non_blocking(ip_socket);
/* make sure that instances won't be listening our sockets */
set_no_inherit(ip_socket);
FD_SET(ip_socket, &read_fds);
sockets[num_sockets++]= ip_socket;
log_info("accepting connections on ip socket (port: %d)", (int) im_port);
return 0;
}
#ifndef __WIN__
int Listener_thread::
create_unix_socket(struct sockaddr_un &unix_socket_address)
{
int unix_socket= socket(AF_UNIX, SOCK_STREAM, 0);
if (unix_socket == INVALID_SOCKET)
{
log_error("Listener_thead: socket(AF_UNIX) failed, %s",
strerror(errno));
return -1;
}
bzero(&unix_socket_address, sizeof(unix_socket_address));
unix_socket_address.sun_family= AF_UNIX;
strmake(unix_socket_address.sun_path, Options::Main::socket_file_name,
sizeof(unix_socket_address.sun_path));
unlink(unix_socket_address.sun_path); // in case we have stale socket file
/*
POSIX specifies default permissions for a pathname created by bind
to be 0777. We need everybody to have access to the socket.
*/
mode_t old_mask= umask(0);
if (bind(unix_socket, (struct sockaddr *) &unix_socket_address,
sizeof(unix_socket_address)))
{
log_error("Listener_thread: bind(unix socket) failed, "
"socket file name is '%s', error '%s'",
unix_socket_address.sun_path, strerror(errno));
close(unix_socket);
return -1;
}
umask(old_mask);
if (listen(unix_socket, LISTEN_BACK_LOG_SIZE))
{
log_error("Listener_thread: listen(unix socket) failed, %s",
strerror(errno));
close(unix_socket);
return -1;
}
/* set the socket nonblocking */
set_non_blocking(unix_socket);
/* make sure that instances won't be listening our sockets */
set_no_inherit(unix_socket);
log_info("accepting connections on unix socket '%s'",
unix_socket_address.sun_path);
sockets[num_sockets++]= unix_socket;
FD_SET(unix_socket, &read_fds);
return 0;
}
#endif
/*
Create new mysql connection. Created thread is responsible for deletion of
the Mysql_connection_thread_args and Vio instances passed to it.
SYNOPSYS
handle_new_mysql_connection()
*/
void Listener_thread::handle_new_mysql_connection(Vio *vio)
{
if (Mysql_connection_thread_args *mysql_thread_args=
new Mysql_connection_thread_args(vio, thread_registry, user_map,
++total_connection_count,
instance_map)
)
{
/*
Initialize thread attributes to create detached thread; it seems
easier to do it ad-hoc than have a global variable for attributes.
*/
pthread_t mysql_thd_id;
pthread_attr_t mysql_thd_attr;
pthread_attr_init(&mysql_thd_attr);
pthread_attr_setdetachstate(&mysql_thd_attr, PTHREAD_CREATE_DETACHED);
if (set_stacksize_n_create_thread(&mysql_thd_id, &mysql_thd_attr,
mysql_connection, mysql_thread_args))
{
delete mysql_thread_args;
vio_delete(vio);
log_error("handle_one_mysql_connection():"
"set_stacksize_n_create_thread(mysql) failed");
}
pthread_attr_destroy(&mysql_thd_attr);
}
else
vio_delete(vio);
}
pthread_handler_t listener(void *arg)
{
Listener_thread_args *args= (Listener_thread_args *) arg;
Listener_thread listener(*args);
listener.run();
/*
args is a stack variable because listener thread lives as long as the
manager process itself
*/
return 0;
}