mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 20:12:31 +01:00
ce30c99478
Part of MDEV-19515 - Improve connect speed
557 lines
15 KiB
C++
557 lines
15 KiB
C++
/* Copyright (c) 2018 MariaDB Corporation.
|
|
|
|
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; version 2 of the License.
|
|
|
|
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
|
|
|
|
/* Accepting connections on Windows */
|
|
|
|
#include <my_global.h>
|
|
#include <sql_class.h>
|
|
#include <sql_connect.h>
|
|
#include <mysqld.h>
|
|
#include <mswsock.h>
|
|
#include <mysql/psi/mysql_socket.h>
|
|
#include <sddl.h>
|
|
|
|
#include <handle_connections_win.h>
|
|
|
|
/* From mysqld.cc */
|
|
extern HANDLE hEventShutdown;
|
|
extern MYSQL_SOCKET base_ip_sock, extra_ip_sock;
|
|
#ifdef HAVE_POOL_OF_THREADS
|
|
extern PTP_CALLBACK_ENVIRON get_threadpool_win_callback_environ();
|
|
extern void tp_win_callback_prolog();
|
|
#else
|
|
#define get_threadpool_win_callback_environ() 0
|
|
#define tp_win_callback_prolog() do{}while(0)
|
|
#endif
|
|
static SECURITY_ATTRIBUTES pipe_security;
|
|
|
|
/**
|
|
Abstract base class for accepting new connection,
|
|
asynchronously (i.e the accept() operation can be posted,
|
|
and result is retrieved later) , and creating a new connection.
|
|
*/
|
|
|
|
struct Listener
|
|
{
|
|
/** Windows handle of the Listener.
|
|
Subclasses would use SOCKET or named pipe handle
|
|
*/
|
|
HANDLE m_handle;
|
|
/** Required for all async IO*/
|
|
OVERLAPPED m_overlapped;
|
|
|
|
/** Create new listener
|
|
@param handle - @see m_handle
|
|
@param wait_handle - usually, event handle or INVALID_HANDLE_VALUE
|
|
@see wait_handle
|
|
*/
|
|
Listener(HANDLE handle, HANDLE wait_handle):
|
|
m_handle(handle), m_overlapped()
|
|
{
|
|
m_overlapped.hEvent= wait_handle;
|
|
}
|
|
|
|
/**
|
|
if not NULL, this handle can be be used in WaitForSingle/MultipleObject(s).
|
|
This handle will be closed when object is destroyed.
|
|
|
|
If NULL, the completion notification happens in threadpool.
|
|
*/
|
|
HANDLE wait_handle()
|
|
{
|
|
return m_overlapped.hEvent;
|
|
}
|
|
|
|
/* Start waiting for new client connection. */
|
|
virtual void begin_accept()= 0;
|
|
|
|
/**
|
|
Completion callback,called whenever IO posted by begin_accept is finisjed
|
|
Listener needs to create a new THD then (or, call scheduler so it creates one)
|
|
|
|
@param success - whether IO completed successfull
|
|
*/
|
|
virtual void completion_callback(bool success)= 0;
|
|
|
|
/**
|
|
Completion callback for Listener, that uses events for waiting
|
|
to IO. Not suitable for threadpool etc. Retrieves the status of
|
|
completed IO from the OVERLAPPED structure
|
|
*/
|
|
void completion_callback()
|
|
{
|
|
DBUG_ASSERT(wait_handle() && (wait_handle() != INVALID_HANDLE_VALUE));
|
|
DWORD bytes;
|
|
return completion_callback(
|
|
GetOverlappedResult(wait_handle(), &m_overlapped, &bytes, FALSE));
|
|
}
|
|
|
|
/** Cancel an in-progress IO. Useful for threadpool-bound IO */
|
|
void cancel()
|
|
{
|
|
CancelIoEx(m_handle, &m_overlapped);
|
|
}
|
|
|
|
/* Destructor. Closes wait handle, if it was passed in constructor */
|
|
virtual ~Listener()
|
|
{
|
|
if (m_overlapped.hEvent)
|
|
CloseHandle(m_overlapped.hEvent);
|
|
};
|
|
};
|
|
|
|
/* Winsock extension finctions. */
|
|
static LPFN_ACCEPTEX my_AcceptEx;
|
|
static LPFN_GETACCEPTEXSOCKADDRS my_GetAcceptExSockaddrs;
|
|
|
|
/**
|
|
Listener that handles socket connections.
|
|
Can be threadpool-bound (i.e the completion is executed in threadpool thread),
|
|
or use events for waits.
|
|
|
|
Threadpool-bound listener should be used with theradpool scheduler, for better
|
|
performance.
|
|
*/
|
|
struct Socket_Listener: public Listener
|
|
{
|
|
/** Client socket passed to AcceptEx() call.*/
|
|
SOCKET m_client_socket;
|
|
|
|
/** Buffer for sockaddrs passed to AcceptEx()/GetAcceptExSockaddrs() */
|
|
char m_buffer[2 * sizeof(sockaddr_storage) + 32];
|
|
|
|
/* Threadpool IO struct.*/
|
|
PTP_IO m_tp_io;
|
|
|
|
/**
|
|
Callback for Windows threadpool's StartThreadpoolIo() function.
|
|
*/
|
|
static void CALLBACK tp_accept_completion_callback(
|
|
PTP_CALLBACK_INSTANCE, PVOID context, PVOID , ULONG io_result,
|
|
ULONG_PTR, PTP_IO io)
|
|
{
|
|
tp_win_callback_prolog();
|
|
Listener *listener= (Listener *)context;
|
|
|
|
if (io_result == ERROR_OPERATION_ABORTED)
|
|
{
|
|
/* ERROR_OPERATION_ABORTED caused by CancelIoEx()*/
|
|
CloseThreadpoolIo(io);
|
|
delete listener;
|
|
return;
|
|
}
|
|
listener->completion_callback(io_result == 0);
|
|
}
|
|
|
|
/**
|
|
Constructor
|
|
@param listen_socket - listening socket
|
|
@PTP_CALLBACK_ENVIRON callback_environ - threadpool environment, or NULL
|
|
if threadpool is not used for completion callbacks.
|
|
*/
|
|
Socket_Listener(MYSQL_SOCKET listen_socket, PTP_CALLBACK_ENVIRON callback_environ) :
|
|
Listener((HANDLE)listen_socket.fd,0),
|
|
m_client_socket(INVALID_SOCKET)
|
|
{
|
|
if (callback_environ)
|
|
{
|
|
/* Accept executed in threadpool. */
|
|
m_tp_io= CreateThreadpoolIo(m_handle,
|
|
tp_accept_completion_callback, this, callback_environ);
|
|
}
|
|
else
|
|
{
|
|
/* Completion signaled via event. */
|
|
m_tp_io= 0;
|
|
m_overlapped.hEvent= CreateEvent(0, FALSE , FALSE, 0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
Use AcceptEx to asynchronously wait for new connection;
|
|
*/
|
|
void begin_accept()
|
|
{
|
|
retry :
|
|
m_client_socket= socket(server_socket_ai_family, SOCK_STREAM, IPPROTO_TCP);
|
|
if (m_client_socket == INVALID_SOCKET)
|
|
{
|
|
sql_perror("socket() call failed.");
|
|
unireg_abort(1);
|
|
}
|
|
|
|
DWORD bytes_received;
|
|
if (m_tp_io)
|
|
StartThreadpoolIo(m_tp_io);
|
|
|
|
BOOL ret= my_AcceptEx(
|
|
(SOCKET)m_handle,
|
|
m_client_socket,
|
|
m_buffer,
|
|
0,
|
|
sizeof(sockaddr_storage) + 16,
|
|
sizeof(sockaddr_storage) + 16,
|
|
&bytes_received,
|
|
&m_overlapped);
|
|
|
|
DWORD last_error= ret? 0: WSAGetLastError();
|
|
if (last_error == WSAECONNRESET)
|
|
{
|
|
if (m_tp_io)
|
|
CancelThreadpoolIo(m_tp_io);
|
|
goto retry;
|
|
}
|
|
|
|
if (ret || last_error == ERROR_IO_PENDING || abort_loop)
|
|
return;
|
|
|
|
sql_print_error("my_AcceptEx failed, last error %u", last_error);
|
|
abort();
|
|
}
|
|
|
|
/* Create new socket connection.*/
|
|
void completion_callback(bool success)
|
|
{
|
|
if (!success)
|
|
{
|
|
/* my_AcceptEx() returned error */
|
|
closesocket(m_client_socket);
|
|
begin_accept();
|
|
return;
|
|
}
|
|
|
|
MYSQL_SOCKET s_client{m_client_socket};
|
|
MYSQL_SOCKET s_listen{(SOCKET)m_handle};
|
|
|
|
#ifdef HAVE_PSI_SOCKET_INTERFACE
|
|
/* Parse socket addresses buffer filled by AcceptEx(),
|
|
only needed for PSI instrumentation. */
|
|
sockaddr *local_addr, *remote_addr;
|
|
int local_addr_len, remote_addr_len;
|
|
|
|
my_GetAcceptExSockaddrs(m_buffer,
|
|
0, sizeof(sockaddr_storage) + 16, sizeof(sockaddr_storage) + 16,
|
|
&local_addr, &local_addr_len, &remote_addr, &remote_addr_len);
|
|
|
|
s_client.m_psi= PSI_SOCKET_CALL(init_socket)
|
|
(key_socket_client_connection, (const my_socket*)&s_listen.fd, remote_addr, remote_addr_len);
|
|
#endif
|
|
|
|
/* Start accepting new connection. After this point, do not use
|
|
any member data, they could be used by a different (threadpool) thread. */
|
|
begin_accept();
|
|
|
|
/* Some chores post-AcceptEx() that we need to create a normal socket.*/
|
|
if (setsockopt(s_client.fd, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT,
|
|
(char *)&s_listen.fd, sizeof(s_listen.fd)))
|
|
{
|
|
if (!abort_loop)
|
|
{
|
|
sql_perror("setsockopt(SO_UPDATE_ACCEPT_CONTEXT) failed.");
|
|
abort();
|
|
}
|
|
}
|
|
|
|
/* Create a new connection.*/
|
|
handle_accepted_socket(s_client, s_listen);
|
|
}
|
|
|
|
~Socket_Listener()
|
|
{
|
|
if (m_client_socket != INVALID_SOCKET)
|
|
closesocket(m_client_socket);
|
|
}
|
|
|
|
/*
|
|
Retrieve the pointer to the Winsock extension functions
|
|
AcceptEx and GetAcceptExSockaddrs.
|
|
*/
|
|
static void init_winsock_extensions()
|
|
{
|
|
SOCKET s= mysql_socket_getfd(base_ip_sock);
|
|
if (s == INVALID_SOCKET)
|
|
s= mysql_socket_getfd(extra_ip_sock);
|
|
if (s == INVALID_SOCKET)
|
|
{
|
|
/* --skip-networking was used*/
|
|
return;
|
|
}
|
|
GUID guid_AcceptEx= WSAID_ACCEPTEX;
|
|
GUID guid_GetAcceptExSockaddrs= WSAID_GETACCEPTEXSOCKADDRS;
|
|
|
|
GUID *guids[]= { &guid_AcceptEx, &guid_GetAcceptExSockaddrs };
|
|
void *funcs[]= { &my_AcceptEx, &my_GetAcceptExSockaddrs };
|
|
DWORD bytes;
|
|
for (int i= 0; i < array_elements(guids); i++)
|
|
{
|
|
if (WSAIoctl(s,
|
|
SIO_GET_EXTENSION_FUNCTION_POINTER,
|
|
guids[i], sizeof(GUID),
|
|
funcs[i], sizeof(void *),
|
|
&bytes, 0, 0) == -1)
|
|
{
|
|
sql_print_error("WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER) failed");
|
|
unireg_abort(1);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
Pipe Listener.
|
|
Only event notification mode is implemented, no threadpool
|
|
*/
|
|
struct Pipe_Listener : public Listener
|
|
{
|
|
PTP_CALLBACK_ENVIRON m_tp_env;
|
|
Pipe_Listener():
|
|
Listener(INVALID_HANDLE_VALUE, CreateEvent(0, FALSE, FALSE, 0)),
|
|
m_tp_env(get_threadpool_win_callback_environ())
|
|
{
|
|
}
|
|
|
|
/*
|
|
Creates local named pipe instance \\.\pipe\$socket for named pipe connection.
|
|
*/
|
|
static HANDLE create_named_pipe()
|
|
{
|
|
static bool first_instance= true;
|
|
static char pipe_name[512];
|
|
DWORD open_mode= PIPE_ACCESS_DUPLEX |
|
|
FILE_FLAG_OVERLAPPED;
|
|
|
|
if (first_instance)
|
|
{
|
|
snprintf(pipe_name, sizeof(pipe_name), "\\\\.\\pipe\\%s", mysqld_unix_port);
|
|
open_mode |= FILE_FLAG_FIRST_PIPE_INSTANCE;
|
|
if (!ConvertStringSecurityDescriptorToSecurityDescriptorA(
|
|
"S:(ML;; NW;;; LW) D:(A;; FRFW;;; WD)",
|
|
1, &pipe_security.lpSecurityDescriptor, NULL))
|
|
{
|
|
sql_perror("Can't start server : Initialize security descriptor");
|
|
unireg_abort(1);
|
|
}
|
|
pipe_security.nLength= sizeof(SECURITY_ATTRIBUTES);
|
|
pipe_security.bInheritHandle= FALSE;
|
|
}
|
|
HANDLE pipe_handle= CreateNamedPipe(pipe_name,
|
|
open_mode,
|
|
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
|
|
PIPE_UNLIMITED_INSTANCES,
|
|
(int)global_system_variables.net_buffer_length,
|
|
(int)global_system_variables.net_buffer_length,
|
|
NMPWAIT_USE_DEFAULT_WAIT,
|
|
&pipe_security);
|
|
if (pipe_handle == INVALID_HANDLE_VALUE)
|
|
{
|
|
sql_perror("Create named pipe failed");
|
|
sql_print_error("Aborting");
|
|
exit(1);
|
|
}
|
|
first_instance= false;
|
|
return pipe_handle;
|
|
}
|
|
|
|
static void create_pipe_connection(HANDLE pipe)
|
|
{
|
|
if (auto connect= new CONNECT(pipe))
|
|
create_new_thread(connect);
|
|
else
|
|
{
|
|
CloseHandle(pipe);
|
|
statistic_increment(aborted_connects, &LOCK_status);
|
|
statistic_increment(connection_errors_internal, &LOCK_status);
|
|
}
|
|
}
|
|
|
|
/* Threadpool callback.*/
|
|
static void CALLBACK tp_create_pipe_connection(
|
|
PTP_CALLBACK_INSTANCE,void *Context)
|
|
{
|
|
tp_win_callback_prolog();
|
|
create_pipe_connection(Context);
|
|
}
|
|
|
|
void begin_accept()
|
|
{
|
|
m_handle= create_named_pipe();
|
|
BOOL connected= ConnectNamedPipe(m_handle, &m_overlapped);
|
|
if (connected)
|
|
{
|
|
/* Overlapped ConnectNamedPipe should return zero. */
|
|
sql_perror("Overlapped ConnectNamedPipe() already connected.");
|
|
abort();
|
|
}
|
|
DWORD last_error= GetLastError();
|
|
switch (last_error)
|
|
{
|
|
case ERROR_PIPE_CONNECTED:
|
|
/* Client is already connected, so signal an event.*/
|
|
{
|
|
/*
|
|
Cleanup overlapped (so that subsequent GetOverlappedResult()
|
|
does not show results of previous IO
|
|
*/
|
|
HANDLE e= m_overlapped.hEvent;
|
|
memset(&m_overlapped, 0, sizeof(m_overlapped));
|
|
m_overlapped.hEvent = e;
|
|
}
|
|
if (!SetEvent(m_overlapped.hEvent))
|
|
{
|
|
sql_perror("SetEvent() failed for connected pipe.");
|
|
abort();
|
|
}
|
|
break;
|
|
case ERROR_IO_PENDING:
|
|
break;
|
|
default:
|
|
sql_perror("ConnectNamedPipe() failed.");
|
|
abort();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void completion_callback(bool success)
|
|
{
|
|
if (!success)
|
|
{
|
|
#ifdef DBUG_OFF
|
|
sql_print_warning("ConnectNamedPipe completed with %u", GetLastError());
|
|
#endif
|
|
CloseHandle(m_handle);
|
|
m_handle= INVALID_HANDLE_VALUE;
|
|
begin_accept();
|
|
return;
|
|
}
|
|
HANDLE pipe= m_handle;
|
|
begin_accept();
|
|
// If threadpool is on, create connection in threadpool thread
|
|
if (!m_tp_env || !TrySubmitThreadpoolCallback(tp_create_pipe_connection, pipe, m_tp_env))
|
|
create_pipe_connection(pipe);
|
|
}
|
|
|
|
~Pipe_Listener()
|
|
{
|
|
if (m_handle != INVALID_HANDLE_VALUE)
|
|
{
|
|
CloseHandle(m_handle);
|
|
}
|
|
}
|
|
|
|
static void cleanup()
|
|
{
|
|
LocalFree(pipe_security.lpSecurityDescriptor);
|
|
}
|
|
};
|
|
|
|
/**
|
|
Accept new client connections on Windows.
|
|
|
|
Since we deal with pipe and sockets, they cannot be put into a select/loop.
|
|
But we can use asynchronous IO, and WaitForMultipleObject() loop.
|
|
|
|
In addition, for slightly better performance, if we're using threadpool,
|
|
socket connections are accepted directly in the threadpool.
|
|
|
|
The mode of operation is therefore
|
|
|
|
1. There is WaitForMultipleObject() loop that waits for shutdown notification
|
|
(hEventShutdown),and possibly pipes and sockets(e.g if threadpool is not used)
|
|
This loop ends when shutdown notification is detected.
|
|
|
|
2. If threadpool is used, new socket connections are accepted there.
|
|
*/
|
|
|
|
|
|
#define MAX_WAIT_HANDLES 32
|
|
#define NUM_PIPE_LISTENERS 24
|
|
#define SHUTDOWN_IDX 0
|
|
#define LISTENER_START_IDX 1
|
|
|
|
void handle_connections_win()
|
|
{
|
|
Listener* all_listeners[MAX_WAIT_HANDLES]= {};
|
|
HANDLE wait_events[MAX_WAIT_HANDLES]= {};
|
|
int n_listeners= 0;
|
|
int n_waits= 0;
|
|
|
|
Socket_Listener::init_winsock_extensions();
|
|
|
|
/* Listen for TCP connections on "extra-port" (no threadpool).*/
|
|
if (extra_ip_sock.fd != INVALID_SOCKET)
|
|
all_listeners[n_listeners++]= new Socket_Listener(extra_ip_sock, 0);
|
|
|
|
/* Listen for named pipe connections */
|
|
if (mysqld_unix_port[0] && !opt_bootstrap && opt_enable_named_pipe)
|
|
{
|
|
/*
|
|
Use several listeners for pipe, to reduce ERROR_PIPE_BUSY on client side.
|
|
*/
|
|
for (int i= 0; i < NUM_PIPE_LISTENERS; i++)
|
|
all_listeners[n_listeners++]= new Pipe_Listener();
|
|
}
|
|
|
|
if (base_ip_sock.fd != INVALID_SOCKET)
|
|
{
|
|
/* Wait for TCP connections.*/
|
|
SetFileCompletionNotificationModes((HANDLE)base_ip_sock.fd, FILE_SKIP_SET_EVENT_ON_HANDLE);
|
|
all_listeners[n_listeners++]= new Socket_Listener(base_ip_sock, get_threadpool_win_callback_environ());
|
|
}
|
|
|
|
if (!n_listeners && !opt_bootstrap)
|
|
{
|
|
sql_print_error("Either TCP connections or named pipe connections must be enabled.");
|
|
unireg_abort(1);
|
|
}
|
|
|
|
wait_events[SHUTDOWN_IDX]= hEventShutdown;
|
|
n_waits = 1;
|
|
|
|
for (int i= 0; i < n_listeners; i++)
|
|
{
|
|
HANDLE wait_handle= all_listeners[i]->wait_handle();
|
|
if(wait_handle)
|
|
{
|
|
DBUG_ASSERT((i == 0) || (all_listeners[i-1]->wait_handle() != 0));
|
|
wait_events[n_waits++]= wait_handle;
|
|
}
|
|
all_listeners[i]->begin_accept();
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
DWORD idx = WaitForMultipleObjects(n_waits ,wait_events, FALSE, INFINITE);
|
|
DBUG_ASSERT((int)idx >= 0 && (int)idx < n_waits);
|
|
|
|
if (idx == SHUTDOWN_IDX)
|
|
break;
|
|
|
|
all_listeners[idx - LISTENER_START_IDX]->completion_callback();
|
|
}
|
|
|
|
/* Cleanup */
|
|
for (int i= 0; i < n_listeners; i++)
|
|
{
|
|
Listener *listener= all_listeners[i];
|
|
if (listener->wait_handle())
|
|
delete listener;
|
|
else
|
|
// Threadpool-bound listener will be deleted in threadpool
|
|
// Do not call destructor, because callback maybe running.
|
|
listener->cancel();
|
|
}
|
|
Pipe_Listener::cleanup();
|
|
}
|