io: implement socket watch for win32 using WSAEventSelect+select

On Win32 we cannot directly poll on socket handles. Instead we
create a Win32 event object and associate the socket handle with
the event. When the event signals readyness we then have to
use select to determine which events are ready. Creating Win32
events is moderately heavyweight, so we don't want todo it
every time we create a GSource, so this associates a single
event with a QIOChannel.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
This commit is contained in:
Paolo Bonzini 2016-03-07 12:12:36 +01:00 committed by Daniel P. Berrange
parent 30fd3e2790
commit a589720567
4 changed files with 170 additions and 7 deletions

View File

@ -78,6 +78,9 @@ typedef gboolean (*QIOChannelFunc)(QIOChannel *ioc,
struct QIOChannel {
Object parent;
unsigned int features; /* bitmask of QIOChannelFeatures */
#ifdef _WIN32
HANDLE event; /* For use with GSource on Win32 */
#endif
};
/**

View File

@ -55,6 +55,10 @@ qio_channel_socket_new(void)
ioc = QIO_CHANNEL(sioc);
ioc->features |= (1 << QIO_CHANNEL_FEATURE_SHUTDOWN);
#ifdef WIN32
ioc->event = CreateEvent(NULL, FALSE, FALSE, NULL);
#endif
trace_qio_channel_socket_new(sioc);
return sioc;
@ -341,6 +345,11 @@ qio_channel_socket_accept(QIOChannelSocket *ioc,
cioc->remoteAddrLen = sizeof(ioc->remoteAddr);
cioc->localAddrLen = sizeof(ioc->localAddr);
#ifdef WIN32
QIO_CHANNEL(cioc)->event = CreateEvent(NULL, FALSE, FALSE, NULL);
#endif
retry:
trace_qio_channel_socket_accept(ioc);
cioc->fd = qemu_accept(ioc->fd, (struct sockaddr *)&cioc->remoteAddr,
@ -384,7 +393,10 @@ static void qio_channel_socket_finalize(Object *obj)
{
QIOChannelSocket *ioc = QIO_CHANNEL_SOCKET(obj);
if (ioc->fd != -1) {
close(ioc->fd);
#ifdef WIN32
WSAEventSelect(ioc->fd, NULL, 0);
#endif
closesocket(ioc->fd);
ioc->fd = -1;
}
}
@ -634,6 +646,11 @@ qio_channel_socket_set_blocking(QIOChannel *ioc,
qemu_set_block(sioc->fd);
} else {
qemu_set_nonblock(sioc->fd);
#ifdef WIN32
WSAEventSelect(sioc->fd, ioc->event,
FD_READ | FD_ACCEPT | FD_CLOSE |
FD_CONNECT | FD_WRITE | FD_OOB);
#endif
}
return 0;
}
@ -669,13 +686,18 @@ qio_channel_socket_close(QIOChannel *ioc,
{
QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
if (closesocket(sioc->fd) < 0) {
if (sioc->fd != -1) {
#ifdef WIN32
WSAEventSelect(sioc->fd, NULL, 0);
#endif
if (closesocket(sioc->fd) < 0) {
sioc->fd = -1;
error_setg_errno(errp, socket_error(),
"Unable to close socket");
return -1;
}
sioc->fd = -1;
error_setg_errno(errp, socket_error(),
"Unable to close socket");
return -1;
}
sioc->fd = -1;
return 0;
}

View File

@ -30,6 +30,20 @@ struct QIOChannelFDSource {
};
#ifdef CONFIG_WIN32
typedef struct QIOChannelSocketSource QIOChannelSocketSource;
struct QIOChannelSocketSource {
GSource parent;
GPollFD fd;
QIOChannel *ioc;
SOCKET socket;
int revents;
GIOCondition condition;
};
#endif
typedef struct QIOChannelFDPairSource QIOChannelFDPairSource;
struct QIOChannelFDPairSource {
GSource parent;
@ -82,6 +96,97 @@ qio_channel_fd_source_finalize(GSource *source)
}
#ifdef CONFIG_WIN32
static gboolean
qio_channel_socket_source_prepare(GSource *source G_GNUC_UNUSED,
gint *timeout)
{
*timeout = -1;
return FALSE;
}
/*
* NB, this impl only works when the socket is in non-blocking
* mode on Win32
*/
static gboolean
qio_channel_socket_source_check(GSource *source)
{
static struct timeval tv0;
QIOChannelSocketSource *ssource = (QIOChannelSocketSource *)source;
WSANETWORKEVENTS ev;
fd_set rfds, wfds, xfds;
if (!ssource->condition) {
return 0;
}
WSAEnumNetworkEvents(ssource->socket, ssource->ioc->event, &ev);
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&xfds);
if (ssource->condition & G_IO_IN) {
FD_SET((SOCKET)ssource->socket, &rfds);
}
if (ssource->condition & G_IO_OUT) {
FD_SET((SOCKET)ssource->socket, &wfds);
}
if (ssource->condition & G_IO_PRI) {
FD_SET((SOCKET)ssource->socket, &xfds);
}
ssource->revents = 0;
if (select(0, &rfds, &wfds, &xfds, &tv0) == 0) {
return 0;
}
if (FD_ISSET(ssource->socket, &rfds)) {
ssource->revents |= G_IO_IN;
}
if (FD_ISSET(ssource->socket, &wfds)) {
ssource->revents |= G_IO_OUT;
}
if (FD_ISSET(ssource->socket, &xfds)) {
ssource->revents |= G_IO_PRI;
}
return ssource->revents;
}
static gboolean
qio_channel_socket_source_dispatch(GSource *source,
GSourceFunc callback,
gpointer user_data)
{
QIOChannelFunc func = (QIOChannelFunc)callback;
QIOChannelSocketSource *ssource = (QIOChannelSocketSource *)source;
return (*func)(ssource->ioc, ssource->revents, user_data);
}
static void
qio_channel_socket_source_finalize(GSource *source)
{
QIOChannelSocketSource *ssource = (QIOChannelSocketSource *)source;
object_unref(OBJECT(ssource->ioc));
}
GSourceFuncs qio_channel_socket_source_funcs = {
qio_channel_socket_source_prepare,
qio_channel_socket_source_check,
qio_channel_socket_source_dispatch,
qio_channel_socket_source_finalize
};
#endif
static gboolean
qio_channel_fd_pair_source_prepare(GSource *source G_GNUC_UNUSED,
gint *timeout)
@ -177,7 +282,26 @@ GSource *qio_channel_create_socket_watch(QIOChannel *ioc,
int socket,
GIOCondition condition)
{
abort();
GSource *source;
QIOChannelSocketSource *ssource;
source = g_source_new(&qio_channel_socket_source_funcs,
sizeof(QIOChannelSocketSource));
ssource = (QIOChannelSocketSource *)source;
ssource->ioc = ioc;
object_ref(OBJECT(ioc));
ssource->condition = condition;
ssource->socket = socket;
ssource->revents = 0;
ssource->fd.fd = (gintptr)ioc->event;
ssource->fd.events = G_IO_IN;
g_source_add_poll(source, &ssource->fd);
return source;
}
#else
GSource *qio_channel_create_socket_watch(QIOChannel *ioc,

View File

@ -274,10 +274,24 @@ void qio_channel_wait(QIOChannel *ioc,
}
#ifdef _WIN32
static void qio_channel_finalize(Object *obj)
{
QIOChannel *ioc = QIO_CHANNEL(obj);
if (ioc->event) {
CloseHandle(ioc->event);
}
}
#endif
static const TypeInfo qio_channel_info = {
.parent = TYPE_OBJECT,
.name = TYPE_QIO_CHANNEL,
.instance_size = sizeof(QIOChannel),
#ifdef _WIN32
.instance_finalize = qio_channel_finalize,
#endif
.abstract = true,
.class_size = sizeof(QIOChannelClass),
};