From 21f627ad0adeb41e49112ff071c40e5609f93736 Mon Sep 17 00:00:00 2001 From: "Nicolas \"Pixel\" Noble" Date: Wed, 4 Feb 2015 01:31:14 +0100 Subject: [PATCH 01/26] First draft of the win32 implementation of iomgr. Caveats: -) The win32 pollset isn't threadsafe (yet). -) Only client code is implemented. -) Only very simple code has been tested with it yet. --- Makefile | 21 ++ build.json | 8 + include/grpc/support/log_win32.h | 53 +++ include/grpc/support/port_platform.h | 2 + include/grpc/support/time_win32.h | 2 +- src/core/iomgr/iomgr_posix.c | 6 + src/core/iomgr/iomgr_windows.c | 67 ++++ src/core/iomgr/iomgr_windows.h | 42 +++ src/core/iomgr/pollset_windows.c | 185 ++++++++- src/core/iomgr/pollset_windows.h | 19 +- src/core/iomgr/sockaddr_win32.h | 2 + src/core/iomgr/socket_windows.c | 76 ++++ src/core/iomgr/socket_windows.h | 73 ++++ src/core/iomgr/tcp_client_windows.c | 219 +++++++++++ src/core/iomgr/tcp_windows.c | 357 ++++++++++++++++++ src/core/iomgr/tcp_windows.h | 57 +++ src/core/support/log_win32.c | 37 +- test/core/end2end/cq_verifier.c | 7 - vsprojects/vs2013/global.props | 24 +- vsprojects/vs2013/gpr.vcxproj | 1 + vsprojects/vs2013/gpr.vcxproj.filters | 3 + vsprojects/vs2013/grpc.vcxproj | 11 + vsprojects/vs2013/grpc.vcxproj.filters | 21 ++ vsprojects/vs2013/grpc_unsecure.vcxproj | 11 + .../vs2013/grpc_unsecure.vcxproj.filters | 21 ++ 25 files changed, 1299 insertions(+), 26 deletions(-) create mode 100644 include/grpc/support/log_win32.h create mode 100644 src/core/iomgr/iomgr_windows.c create mode 100644 src/core/iomgr/iomgr_windows.h create mode 100644 src/core/iomgr/socket_windows.c create mode 100644 src/core/iomgr/socket_windows.h create mode 100644 src/core/iomgr/tcp_client_windows.c create mode 100644 src/core/iomgr/tcp_windows.c create mode 100644 src/core/iomgr/tcp_windows.h diff --git a/Makefile b/Makefile index bd094150b77..fb46a936749 100644 --- a/Makefile +++ b/Makefile @@ -1246,6 +1246,7 @@ PUBLIC_HEADERS_C += \ include/grpc/support/histogram.h \ include/grpc/support/host_port.h \ include/grpc/support/log.h \ + include/grpc/support/log_win32.h \ include/grpc/support/port_platform.h \ include/grpc/support/slice.h \ include/grpc/support/slice_buffer.h \ @@ -1408,6 +1409,7 @@ LIBGRPC_SRC = \ src/core/iomgr/fd_posix.c \ src/core/iomgr/iomgr.c \ src/core/iomgr/iomgr_posix.c \ + src/core/iomgr/iomgr_windows.c \ src/core/iomgr/pollset_kick.c \ src/core/iomgr/pollset_multipoller_with_poll_posix.c \ src/core/iomgr/pollset_posix.c \ @@ -1417,9 +1419,12 @@ LIBGRPC_SRC = \ src/core/iomgr/socket_utils_common_posix.c \ src/core/iomgr/socket_utils_linux.c \ src/core/iomgr/socket_utils_posix.c \ + src/core/iomgr/socket_windows.c \ src/core/iomgr/tcp_client_posix.c \ + src/core/iomgr/tcp_client_windows.c \ src/core/iomgr/tcp_posix.c \ src/core/iomgr/tcp_server_posix.c \ + src/core/iomgr/tcp_windows.c \ src/core/iomgr/time_averaged_stats.c \ src/core/iomgr/wakeup_fd_eventfd.c \ src/core/iomgr/wakeup_fd_nospecial.c \ @@ -1534,6 +1539,7 @@ src/core/iomgr/endpoint_pair_posix.c: $(OPENSSL_DEP) src/core/iomgr/fd_posix.c: $(OPENSSL_DEP) src/core/iomgr/iomgr.c: $(OPENSSL_DEP) src/core/iomgr/iomgr_posix.c: $(OPENSSL_DEP) +src/core/iomgr/iomgr_windows.c: $(OPENSSL_DEP) src/core/iomgr/pollset_kick.c: $(OPENSSL_DEP) src/core/iomgr/pollset_multipoller_with_poll_posix.c: $(OPENSSL_DEP) src/core/iomgr/pollset_posix.c: $(OPENSSL_DEP) @@ -1543,9 +1549,12 @@ src/core/iomgr/sockaddr_utils.c: $(OPENSSL_DEP) src/core/iomgr/socket_utils_common_posix.c: $(OPENSSL_DEP) src/core/iomgr/socket_utils_linux.c: $(OPENSSL_DEP) src/core/iomgr/socket_utils_posix.c: $(OPENSSL_DEP) +src/core/iomgr/socket_windows.c: $(OPENSSL_DEP) src/core/iomgr/tcp_client_posix.c: $(OPENSSL_DEP) +src/core/iomgr/tcp_client_windows.c: $(OPENSSL_DEP) src/core/iomgr/tcp_posix.c: $(OPENSSL_DEP) src/core/iomgr/tcp_server_posix.c: $(OPENSSL_DEP) +src/core/iomgr/tcp_windows.c: $(OPENSSL_DEP) src/core/iomgr/time_averaged_stats.c: $(OPENSSL_DEP) src/core/iomgr/wakeup_fd_eventfd.c: $(OPENSSL_DEP) src/core/iomgr/wakeup_fd_nospecial.c: $(OPENSSL_DEP) @@ -1682,6 +1691,7 @@ objs/$(CONFIG)/src/core/iomgr/endpoint_pair_posix.o: objs/$(CONFIG)/src/core/iomgr/fd_posix.o: objs/$(CONFIG)/src/core/iomgr/iomgr.o: objs/$(CONFIG)/src/core/iomgr/iomgr_posix.o: +objs/$(CONFIG)/src/core/iomgr/iomgr_windows.o: objs/$(CONFIG)/src/core/iomgr/pollset_kick.o: objs/$(CONFIG)/src/core/iomgr/pollset_multipoller_with_poll_posix.o: objs/$(CONFIG)/src/core/iomgr/pollset_posix.o: @@ -1691,9 +1701,12 @@ objs/$(CONFIG)/src/core/iomgr/sockaddr_utils.o: objs/$(CONFIG)/src/core/iomgr/socket_utils_common_posix.o: objs/$(CONFIG)/src/core/iomgr/socket_utils_linux.o: objs/$(CONFIG)/src/core/iomgr/socket_utils_posix.o: +objs/$(CONFIG)/src/core/iomgr/socket_windows.o: objs/$(CONFIG)/src/core/iomgr/tcp_client_posix.o: +objs/$(CONFIG)/src/core/iomgr/tcp_client_windows.o: objs/$(CONFIG)/src/core/iomgr/tcp_posix.o: objs/$(CONFIG)/src/core/iomgr/tcp_server_posix.o: +objs/$(CONFIG)/src/core/iomgr/tcp_windows.o: objs/$(CONFIG)/src/core/iomgr/time_averaged_stats.o: objs/$(CONFIG)/src/core/iomgr/wakeup_fd_eventfd.o: objs/$(CONFIG)/src/core/iomgr/wakeup_fd_nospecial.o: @@ -1849,6 +1862,7 @@ LIBGRPC_UNSECURE_SRC = \ src/core/iomgr/fd_posix.c \ src/core/iomgr/iomgr.c \ src/core/iomgr/iomgr_posix.c \ + src/core/iomgr/iomgr_windows.c \ src/core/iomgr/pollset_kick.c \ src/core/iomgr/pollset_multipoller_with_poll_posix.c \ src/core/iomgr/pollset_posix.c \ @@ -1858,9 +1872,12 @@ LIBGRPC_UNSECURE_SRC = \ src/core/iomgr/socket_utils_common_posix.c \ src/core/iomgr/socket_utils_linux.c \ src/core/iomgr/socket_utils_posix.c \ + src/core/iomgr/socket_windows.c \ src/core/iomgr/tcp_client_posix.c \ + src/core/iomgr/tcp_client_windows.c \ src/core/iomgr/tcp_posix.c \ src/core/iomgr/tcp_server_posix.c \ + src/core/iomgr/tcp_windows.c \ src/core/iomgr/time_averaged_stats.c \ src/core/iomgr/wakeup_fd_eventfd.c \ src/core/iomgr/wakeup_fd_nospecial.c \ @@ -1980,6 +1997,7 @@ objs/$(CONFIG)/src/core/iomgr/endpoint_pair_posix.o: objs/$(CONFIG)/src/core/iomgr/fd_posix.o: objs/$(CONFIG)/src/core/iomgr/iomgr.o: objs/$(CONFIG)/src/core/iomgr/iomgr_posix.o: +objs/$(CONFIG)/src/core/iomgr/iomgr_windows.o: objs/$(CONFIG)/src/core/iomgr/pollset_kick.o: objs/$(CONFIG)/src/core/iomgr/pollset_multipoller_with_poll_posix.o: objs/$(CONFIG)/src/core/iomgr/pollset_posix.o: @@ -1989,9 +2007,12 @@ objs/$(CONFIG)/src/core/iomgr/sockaddr_utils.o: objs/$(CONFIG)/src/core/iomgr/socket_utils_common_posix.o: objs/$(CONFIG)/src/core/iomgr/socket_utils_linux.o: objs/$(CONFIG)/src/core/iomgr/socket_utils_posix.o: +objs/$(CONFIG)/src/core/iomgr/socket_windows.o: objs/$(CONFIG)/src/core/iomgr/tcp_client_posix.o: +objs/$(CONFIG)/src/core/iomgr/tcp_client_windows.o: objs/$(CONFIG)/src/core/iomgr/tcp_posix.o: objs/$(CONFIG)/src/core/iomgr/tcp_server_posix.o: +objs/$(CONFIG)/src/core/iomgr/tcp_windows.o: objs/$(CONFIG)/src/core/iomgr/time_averaged_stats.o: objs/$(CONFIG)/src/core/iomgr/wakeup_fd_eventfd.o: objs/$(CONFIG)/src/core/iomgr/wakeup_fd_nospecial.o: diff --git a/build.json b/build.json index 6e20d617e3a..28019c2bf27 100644 --- a/build.json +++ b/build.json @@ -45,6 +45,7 @@ "src/core/iomgr/iomgr.h", "src/core/iomgr/iomgr_internal.h", "src/core/iomgr/iomgr_posix.h", + "src/core/iomgr/iomgr_windows.h", "src/core/iomgr/pollset.h", "src/core/iomgr/pollset_kick.h", "src/core/iomgr/pollset_kick_posix.h", @@ -57,9 +58,11 @@ "src/core/iomgr/sockaddr_utils.h", "src/core/iomgr/sockaddr_win32.h", "src/core/iomgr/socket_utils_posix.h", + "src/core/iomgr/socket_windows.h", "src/core/iomgr/tcp_client.h", "src/core/iomgr/tcp_posix.h", "src/core/iomgr/tcp_server.h", + "src/core/iomgr/tcp_windows.h", "src/core/iomgr/time_averaged_stats.h", "src/core/iomgr/wakeup_fd_pipe.h", "src/core/iomgr/wakeup_fd_posix.h", @@ -131,6 +134,7 @@ "src/core/iomgr/fd_posix.c", "src/core/iomgr/iomgr.c", "src/core/iomgr/iomgr_posix.c", + "src/core/iomgr/iomgr_windows.c", "src/core/iomgr/pollset_kick.c", "src/core/iomgr/pollset_multipoller_with_poll_posix.c", "src/core/iomgr/pollset_posix.c", @@ -140,9 +144,12 @@ "src/core/iomgr/socket_utils_common_posix.c", "src/core/iomgr/socket_utils_linux.c", "src/core/iomgr/socket_utils_posix.c", + "src/core/iomgr/socket_windows.c", "src/core/iomgr/tcp_client_posix.c", + "src/core/iomgr/tcp_client_windows.c", "src/core/iomgr/tcp_posix.c", "src/core/iomgr/tcp_server_posix.c", + "src/core/iomgr/tcp_windows.c", "src/core/iomgr/time_averaged_stats.c", "src/core/iomgr/wakeup_fd_eventfd.c", "src/core/iomgr/wakeup_fd_nospecial.c", @@ -212,6 +219,7 @@ "include/grpc/support/histogram.h", "include/grpc/support/host_port.h", "include/grpc/support/log.h", + "include/grpc/support/log_win32.h", "include/grpc/support/port_platform.h", "include/grpc/support/slice.h", "include/grpc/support/slice_buffer.h", diff --git a/include/grpc/support/log_win32.h b/include/grpc/support/log_win32.h new file mode 100644 index 00000000000..7abd5df3941 --- /dev/null +++ b/include/grpc/support/log_win32.h @@ -0,0 +1,53 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef __GRPC_SUPPORT_LOG_WIN32_H__ +#define __GRPC_SUPPORT_LOG_WIN32_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Returns a string allocated with gpr_malloc that contains a UTF-8 + * formatted error message, corresponding to the error messageid. + * Use in cunjunction with GetLastError() et al. + */ +char *gpr_format_message(DWORD messageid); + +#ifdef __cplusplus +} +#endif + +#endif /* __GRPC_SUPPORT_LOG_H__ */ diff --git a/include/grpc/support/port_platform.h b/include/grpc/support/port_platform.h index 2bf53483157..672dd736fb4 100644 --- a/include/grpc/support/port_platform.h +++ b/include/grpc/support/port_platform.h @@ -46,10 +46,12 @@ #define GPR_WIN32 1 #define GPR_ARCH_64 1 #define GPR_GETPID_IN_PROCESS_H 1 +#define GPR_WINSOCK_SOCKET 1 #elif defined(_WIN32) || defined(WIN32) #define GPR_ARCH_32 1 #define GPR_WIN32 1 #define GPR_GETPID_IN_PROCESS_H 1 +#define GPR_WINSOCK_SOCKET 1 #elif defined(ANDROID) || defined(__ANDROID__) #define GPR_ANDROID 1 #define GPR_ARCH_32 1 diff --git a/include/grpc/support/time_win32.h b/include/grpc/support/time_win32.h index e62ad64b8f5..1f7b6844500 100644 --- a/include/grpc/support/time_win32.h +++ b/include/grpc/support/time_win32.h @@ -35,7 +35,7 @@ #define __GRPC_SUPPORT_TIME_WIN32_H__ /* Win32 variant of gpr_time_platform.h */ -#include +#include #include typedef struct gpr_timespec { diff --git a/src/core/iomgr/iomgr_posix.c b/src/core/iomgr/iomgr_posix.c index 9297f08e99a..22ca74314cb 100644 --- a/src/core/iomgr/iomgr_posix.c +++ b/src/core/iomgr/iomgr_posix.c @@ -31,6 +31,10 @@ * */ +#include + +#ifdef GPR_POSIX_SOCKET + #include "src/core/iomgr/iomgr_posix.h" #include "src/core/iomgr/fd_posix.h" @@ -43,3 +47,5 @@ void grpc_iomgr_platform_shutdown(void) { grpc_pollset_global_shutdown(); grpc_fd_global_shutdown(); } + +#endif /* GRPC_IOMGRP_POSIX */ diff --git a/src/core/iomgr/iomgr_windows.c b/src/core/iomgr/iomgr_windows.c new file mode 100644 index 00000000000..9cdf224ad77 --- /dev/null +++ b/src/core/iomgr/iomgr_windows.c @@ -0,0 +1,67 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include + +#ifdef GPR_WINSOCK_SOCKET + +#include "src/core/iomgr/sockaddr_win32.h" + +#include + +#include "src/core/iomgr/socket_windows.h" +#include "src/core/iomgr/iomgr.h" +#include "src/core/iomgr/iomgr_windows.h" + +static void winsock_init(void) { + WSADATA wsaData; + int status = WSAStartup(MAKEWORD(2, 0), &wsaData); + GPR_ASSERT(status == 0); +} + +static void winsock_shutdown(void) { + int status = WSACleanup(); + GPR_ASSERT(status == 0); +} + +void grpc_iomgr_platform_init(void) { + winsock_init(); + grpc_pollset_global_init(); +} + +void grpc_iomgr_platform_shutdown(void) { + grpc_pollset_global_shutdown(); + winsock_shutdown(); +} + +#endif /* GRPC_IOMGRP_POSIX */ diff --git a/src/core/iomgr/iomgr_windows.h b/src/core/iomgr/iomgr_windows.h new file mode 100644 index 00000000000..2d9449c1f62 --- /dev/null +++ b/src/core/iomgr/iomgr_windows.h @@ -0,0 +1,42 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef __GRPC_INTERNAL_IOMGR_IOMGR_WINDOWS_H_ +#define __GRPC_INTERNAL_IOMGR_IOMGR_WINDOWS_H_ + +#include "src/core/iomgr/socket_windows.h" + +void grpc_pollset_global_init(void); +void grpc_pollset_global_shutdown(void); + +#endif /* __GRPC_INTERNAL_IOMGR_IOMGR_WINDOWS_H_ */ diff --git a/src/core/iomgr/pollset_windows.c b/src/core/iomgr/pollset_windows.c index 3fb39918b35..fdebede4828 100644 --- a/src/core/iomgr/pollset_windows.c +++ b/src/core/iomgr/pollset_windows.c @@ -33,6 +33,187 @@ #include -#ifdef GPR_WIN32 +#ifdef GPR_WINSOCK_SOCKET -#endif /* GPR_WIN32 */ +#include + +#include +#include + +#include "src/core/iomgr/alarm_internal.h" +#include "src/core/iomgr/socket_windows.h" +#include "src/core/iomgr/iomgr_internal.h" +#include "src/core/iomgr/pollset_windows.h" + +static grpc_pollset g_global_pollset; +static ULONG g_pollset_kick_token; +static OVERLAPPED g_pollset_custom_overlap; + +static gpr_event g_shutdown_global_poller; +static gpr_event g_global_poller_done; + +void grpc_pollset_init(grpc_pollset *pollset) { + pollset->iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, + (ULONG_PTR)NULL, 0); + GPR_ASSERT(pollset->iocp); +} + +void grpc_pollset_destroy(grpc_pollset *pollset) { + BOOL status; + status = CloseHandle(pollset->iocp); + GPR_ASSERT(status); +} + +static int pollset_poll(grpc_pollset *pollset, + gpr_timespec deadline, gpr_timespec now) { + BOOL success; + DWORD bytes = 0; + DWORD flags = 0; + ULONG_PTR completion_key; + LPOVERLAPPED overlapped; + gpr_timespec wait_time = gpr_time_sub(deadline, now); + grpc_winsocket *socket; + grpc_winsocket_callback_info *info; + void(*f)(void *, int) = NULL; + void *opaque = NULL; + success = GetQueuedCompletionStatus(pollset->iocp, &bytes, + &completion_key, &overlapped, + gpr_time_to_millis(wait_time)); + + if (!success && !overlapped) { + /* The deadline got attained. */ + return 0; + } + GPR_ASSERT(completion_key && overlapped); + if (overlapped == &g_pollset_custom_overlap) { + if (completion_key == (ULONG_PTR) &g_pollset_kick_token) { + /* We were awoken from a kick. */ + gpr_log(GPR_DEBUG, "pollset_poll - got a kick"); + return 1; + } + gpr_log(GPR_ERROR, "Unknown custom completion key."); + abort(); + } + + socket = (grpc_winsocket*) completion_key; + if (overlapped == &socket->write_info.overlapped) { + gpr_log(GPR_DEBUG, "pollset_poll - got write packet"); + info = &socket->write_info; + } else if (overlapped == &socket->read_info.overlapped) { + gpr_log(GPR_DEBUG, "pollset_poll - got read packet"); + info = &socket->read_info; + } else { + gpr_log(GPR_ERROR, "Unknown IOCP operation"); + abort(); + } + success = WSAGetOverlappedResult(socket->socket, &info->overlapped, &bytes, + FALSE, &flags); + gpr_log(GPR_DEBUG, "bytes: %u, flags: %u - op %s", bytes, flags, + success ? "succeeded" : "failed"); + info->bytes_transfered = bytes; + info->wsa_error = success ? 0 : WSAGetLastError(); + GPR_ASSERT(overlapped == &info->overlapped); + gpr_mu_lock(&socket->state_mu); + GPR_ASSERT(!info->has_pending_iocp); + if (info->cb) { + f = info->cb; + opaque = info->opaque; + info->cb = NULL; + } else { + info->has_pending_iocp = 1; + } + gpr_mu_unlock(&socket->state_mu); + if (f) f(opaque, 1); + + return 1; +} + +int grpc_pollset_work(grpc_pollset *pollset, gpr_timespec deadline) { + gpr_timespec now; + now = gpr_now(); + if (gpr_time_cmp(now, deadline) > 0) { + return 0; + } + if (grpc_maybe_call_delayed_callbacks(NULL, 1)) { + return 1; + } + if (grpc_alarm_check(NULL, now, &deadline)) { + return 1; + } + return pollset_poll(pollset, deadline, now); +} + +void grpc_pollset_kick(grpc_pollset *pollset) { + BOOL status; + status = PostQueuedCompletionStatus(pollset->iocp, 0, + (ULONG_PTR) &g_pollset_kick_token, + &g_pollset_custom_overlap); + GPR_ASSERT(status); +} + +static void global_poller(void *p) { + while (!gpr_event_get(&g_shutdown_global_poller)) { + grpc_pollset_work(&g_global_pollset, gpr_inf_future); + } + + gpr_event_set(&g_global_poller_done, (void *) 1); +} + +void grpc_pollset_global_init(void) { + gpr_thd_id id; + + grpc_pollset_init(&g_global_pollset); + gpr_event_init(&g_global_poller_done); + gpr_event_init(&g_shutdown_global_poller); + gpr_thd_new(&id, global_poller, NULL, NULL); +} + +void grpc_pollset_global_shutdown(void) { + gpr_event_set(&g_shutdown_global_poller, (void *) 1); + grpc_pollset_kick(&g_global_pollset); + gpr_event_wait(&g_global_poller_done, gpr_inf_future); + grpc_pollset_destroy(&g_global_pollset); +} + +void grpc_pollset_add_handle(grpc_pollset *pollset, grpc_winsocket *socket) { + HANDLE ret = CreateIoCompletionPort((HANDLE) socket->socket, pollset->iocp, + (gpr_uintptr) socket, 0); + GPR_ASSERT(ret == pollset->iocp); +} + +static void handle_notify_on_iocp(grpc_winsocket *socket, + void(*cb)(void *, int), void *opaque, + grpc_winsocket_callback_info *info) { + int run_now = 0; + GPR_ASSERT(!info->cb); + gpr_mu_lock(&socket->state_mu); + if (info->has_pending_iocp) { + run_now = 1; + info->has_pending_iocp = 0; + gpr_log(GPR_DEBUG, "handle_notify_on_iocp - runs now"); + } else { + info->cb = cb; + info->opaque = opaque; + gpr_log(GPR_DEBUG, "handle_notify_on_iocp - queued"); + } + gpr_mu_unlock(&socket->state_mu); + if (run_now) cb(opaque, 1); +} + +void grpc_handle_notify_on_write(grpc_winsocket *socket, + void(*cb)(void *, int), void *opaque) { + gpr_log(GPR_DEBUG, "grpc_handle_notify_on_write"); + handle_notify_on_iocp(socket, cb, opaque, &socket->write_info); +} + +void grpc_handle_notify_on_read(grpc_winsocket *socket, + void(*cb)(void *, int), void *opaque) { + gpr_log(GPR_DEBUG, "grpc_handle_notify_on_read"); + handle_notify_on_iocp(socket, cb, opaque, &socket->read_info); +} + +grpc_pollset *grpc_global_pollset(void) { + return &g_global_pollset; +} + +#endif /* GPR_WINSOCK_SOCKET */ diff --git a/src/core/iomgr/pollset_windows.h b/src/core/iomgr/pollset_windows.h index 9214b04b170..036f3910a01 100644 --- a/src/core/iomgr/pollset_windows.h +++ b/src/core/iomgr/pollset_windows.h @@ -34,9 +34,11 @@ #ifndef __GRPC_INTERNAL_IOMGR_POLLSET_WINDOWS_H_ #define __GRPC_INTERNAL_IOMGR_POLLSET_WINDOWS_H_ +#include #include #include "src/core/iomgr/pollset_kick.h" +#include "src/core/iomgr/socket_windows.h" /* forward declare only in this file to avoid leaking impl details via pollset.h; real users of grpc_fd should always include 'fd_posix.h' and not @@ -44,11 +46,20 @@ struct grpc_fd; typedef struct grpc_pollset { - gpr_mu mu; - gpr_cv cv; + HANDLE iocp; } grpc_pollset; -#define GRPC_POLLSET_MU(pollset) (&(pollset)->mu) -#define GRPC_POLLSET_CV(pollset) (&(pollset)->cv) +#define GRPC_POLLSET_MU(pollset) (NULL) +#define GRPC_POLLSET_CV(pollset) (NULL) + +void grpc_pollset_add_handle(grpc_pollset *, grpc_winsocket *); + +grpc_pollset *grpc_global_pollset(void); + +void grpc_handle_notify_on_write(grpc_winsocket *, void(*cb)(void *, int success), + void *opaque); + +void grpc_handle_notify_on_read(grpc_winsocket *, void(*cb)(void *, int success), + void *opaque); #endif /* __GRPC_INTERNAL_IOMGR_POLLSET_WINDOWS_H_ */ diff --git a/src/core/iomgr/sockaddr_win32.h b/src/core/iomgr/sockaddr_win32.h index cdea33fec07..350f5fa33e1 100644 --- a/src/core/iomgr/sockaddr_win32.h +++ b/src/core/iomgr/sockaddr_win32.h @@ -35,5 +35,7 @@ #define __GRPC_INTERNAL_IOMGR_SOCKADDR_WIN32_H_ #include +#include +#include #endif // __GRPC_INTERNAL_IOMGR_SOCKADDR_WIN32_H_ diff --git a/src/core/iomgr/socket_windows.c b/src/core/iomgr/socket_windows.c new file mode 100644 index 00000000000..7d1d59c3187 --- /dev/null +++ b/src/core/iomgr/socket_windows.c @@ -0,0 +1,76 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include + +#ifdef GPR_WINSOCK_SOCKET + +#include "src/core/iomgr/iomgr.h" +#include "src/core/iomgr/iomgr_internal.h" +#include "src/core/iomgr/socket_windows.h" +#include "src/core/iomgr/pollset.h" +#include "src/core/iomgr/pollset_windows.h" + +grpc_winsocket *grpc_winsocket_create(SOCKET socket) { + grpc_winsocket *r = gpr_malloc(sizeof(grpc_winsocket)); + gpr_log(GPR_DEBUG, "grpc_winsocket_create"); + memset(r, 0, sizeof(grpc_winsocket)); + r->socket = socket; + gpr_mu_init(&r->state_mu); + grpc_iomgr_ref(); + grpc_pollset_add_handle(grpc_global_pollset(), r); + return r; +} + +void shutdown_op(grpc_winsocket_callback_info *info) { + if (!info->cb) return; + info->cb(info->opaque, 0); +} + +void grpc_winsocket_shutdown(grpc_winsocket *socket) { + gpr_log(GPR_DEBUG, "grpc_winsocket_shutdown"); + shutdown_op(&socket->read_info); + shutdown_op(&socket->write_info); +} + +void grpc_winsocket_orphan(grpc_winsocket *socket) { + gpr_log(GPR_DEBUG, "grpc_winsocket_orphan"); + grpc_iomgr_unref(); + closesocket(socket->socket); + gpr_mu_destroy(&socket->state_mu); + gpr_free(socket); +} + +#endif diff --git a/src/core/iomgr/socket_windows.h b/src/core/iomgr/socket_windows.h new file mode 100644 index 00000000000..ca85ea54cbf --- /dev/null +++ b/src/core/iomgr/socket_windows.h @@ -0,0 +1,73 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef __GRPC_INTERNAL_IOMGR_HANDLE_WINDOWS_H__ +#define __GRPC_INTERNAL_IOMGR_HANDLE_WINDOWS_H__ + +#include + +#include +#include + +typedef struct grpc_winsocket_callback_info { + /* I hate Microsoft so much. This is supposed to be a WSAOVERLAPPED, + * but in order to get that definition, we need to include ws2tcpip.h, + * which needs to be included from the top, otherwise it'll clash with + * a previous inclusion of windows.h that in turns includes winsock.h. + * If anyone knows a way to do it properly, feel free to send a patch. + */ + OVERLAPPED overlapped; + void(*cb)(void *opaque, int success); + void *opaque; + int has_pending_iocp; + DWORD bytes_transfered; + int wsa_error; +} grpc_winsocket_callback_info; + +typedef struct grpc_winsocket { + SOCKET socket; + + grpc_winsocket_callback_info write_info; + grpc_winsocket_callback_info read_info; + + gpr_mu state_mu; +} grpc_winsocket; + +/* Create a wrapped windows handle. +This takes ownership of closing it. */ +grpc_winsocket *grpc_winsocket_create(SOCKET socket); + +void grpc_winsocket_shutdown(grpc_winsocket *socket); +void grpc_winsocket_orphan(grpc_winsocket *socket); + +#endif /* __GRPC_INTERNAL_IOMGR_FD_POSIX_H_ */ diff --git a/src/core/iomgr/tcp_client_windows.c b/src/core/iomgr/tcp_client_windows.c new file mode 100644 index 00000000000..465fc75295d --- /dev/null +++ b/src/core/iomgr/tcp_client_windows.c @@ -0,0 +1,219 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include + +#ifdef GPR_WINSOCK_SOCKET + +#include "src/core/iomgr/sockaddr_win32.h" + +#include +#include +#include +#include +#include + +#include "src/core/iomgr/tcp_client.h" +#include "src/core/iomgr/tcp_windows.h" +#include "src/core/iomgr/socket_windows.h" +#include "src/core/iomgr/alarm.h" +#include "src/core/iomgr/sockaddr.h" +#include "src/core/iomgr/sockaddr_utils.h" + +typedef struct { + void(*cb)(void *arg, grpc_endpoint *tcp); + void *cb_arg; + gpr_mu mu; + grpc_winsocket *socket; + gpr_timespec deadline; + grpc_alarm alarm; + int refs; +} async_connect; + +static void async_connect_cleanup(async_connect *ac) { + int done = (--ac->refs == 0); + gpr_mu_unlock(&ac->mu); + if (done) { + gpr_mu_destroy(&ac->mu); + gpr_free(ac); + } +} + +static void on_alarm(void *acp, int success) { + async_connect *ac = acp; + gpr_mu_lock(&ac->mu); + if (ac->socket != NULL && success) { + grpc_winsocket_shutdown(ac->socket); + } + async_connect_cleanup(ac); +} + +static void on_connect(void *acp, int success) { + async_connect *ac = acp; + SOCKET sock = ac->socket->socket; + grpc_endpoint *ep = NULL; + grpc_winsocket_callback_info *info = &ac->socket->write_info; + void(*cb)(void *arg, grpc_endpoint *tcp) = ac->cb; + void *cb_arg = ac->cb_arg; + + grpc_alarm_cancel(&ac->alarm); + + if (success) { + DWORD transfered_bytes = 0; + DWORD flags; + BOOL wsa_success = WSAGetOverlappedResult(sock, &info->overlapped, + &transfered_bytes, FALSE, + &flags); + GPR_ASSERT(transfered_bytes == 0); + if (!wsa_success) { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "on_connect error: %s", utf8_message); + gpr_free(utf8_message); + goto finish; + } else { + gpr_log(GPR_DEBUG, "on_connect: connection established"); + ep = grpc_tcp_create(ac->socket); + goto finish; + } + } else { + __debugbreak(); + abort(); + gpr_log(GPR_ERROR, "on_writable failed during connect"); + goto finish; + } + + abort(); + +finish: + gpr_mu_lock(&ac->mu); + if (!ep) { + grpc_winsocket_orphan(ac->socket); + } + async_connect_cleanup(ac); + cb(cb_arg, ep); +} + +void grpc_tcp_client_connect(void(*cb)(void *arg, grpc_endpoint *tcp), + void *arg, const struct sockaddr *addr, + int addr_len, gpr_timespec deadline) { + SOCKET sock = INVALID_SOCKET; + BOOL success; + int status; + struct sockaddr_in6 addr6_v4mapped; + struct sockaddr_in6 local_address; + async_connect *ac; + grpc_winsocket *socket = NULL; + LPFN_CONNECTEX ConnectEx; + GUID guid = WSAID_CONNECTEX; + DWORD ioctl_num_bytes; + const char *message = NULL; + char *utf8_message; + grpc_winsocket_callback_info *info; + + /* Use dualstack sockets where available. */ + if (grpc_sockaddr_to_v4mapped(addr, &addr6_v4mapped)) { + addr = (const struct sockaddr *)&addr6_v4mapped; + addr_len = sizeof(addr6_v4mapped); + } + + sock = WSASocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP, NULL, 0, + WSA_FLAG_OVERLAPPED); + if (sock == INVALID_SOCKET) { + message = "Unable to create socket: %s"; + goto failure; + } + + if (!grpc_tcp_prepare_socket(sock)) { + message = "Unable to set socket options: %s"; + goto failure; + } + + status = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, + &guid, sizeof(guid), &ConnectEx, sizeof(ConnectEx), + &ioctl_num_bytes, NULL, NULL); + + if (status != 0) { + message = "Unable to retreive ConnectEx pointer: %s"; + goto failure; + } + + memset(&local_address, 0, sizeof(local_address)); + memcpy(&local_address.sin6_addr, &in6addr_any, sizeof(in6addr_any)); + local_address.sin6_family = AF_INET6; + + status = bind(sock, (struct sockaddr *) &local_address, + sizeof(local_address)); + if (status != 0) { + message = "Unable to bind socket: %s"; + goto failure; + } + + socket = grpc_winsocket_create(sock); + info = &socket->write_info; + success = ConnectEx(sock, addr, addr_len, NULL, 0, NULL, &info->overlapped); + + if (success) { + gpr_log(GPR_DEBUG, "connected immediately - but we still go to sleep"); + } else { + int error = WSAGetLastError(); + if (error != ERROR_IO_PENDING) { + message = "ConnectEx failed: %s"; + goto failure; + } + } + + gpr_log(GPR_DEBUG, "grpc_tcp_client_connect: connection pending"); + ac = gpr_malloc(sizeof(async_connect)); + ac->cb = cb; + ac->cb_arg = arg; + ac->socket = socket; + gpr_mu_init(&ac->mu); + ac->refs = 2; + + grpc_alarm_init(&ac->alarm, deadline, on_alarm, ac, gpr_now()); + grpc_handle_notify_on_write(socket, on_connect, ac); + return; + +failure: + utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, message, utf8_message); + gpr_free(utf8_message); + if (socket) { + grpc_winsocket_orphan(socket); + } else if (sock != INVALID_SOCKET) { + closesocket(sock); + } + cb(arg, NULL); +} + +#endif /* GPR_WINSOCK_SOCKET */ diff --git a/src/core/iomgr/tcp_windows.c b/src/core/iomgr/tcp_windows.c new file mode 100644 index 00000000000..0eb6663dd48 --- /dev/null +++ b/src/core/iomgr/tcp_windows.c @@ -0,0 +1,357 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include + +#ifdef GPR_WINSOCK_SOCKET + +#include "src/core/iomgr/sockaddr_win32.h" + +#include +#include +#include +#include +#include + +#include "src/core/iomgr/tcp_client.h" +#include "src/core/iomgr/socket_windows.h" +#include "src/core/iomgr/alarm.h" +#include "src/core/iomgr/sockaddr.h" +#include "src/core/iomgr/sockaddr_utils.h" + +static int set_non_block(SOCKET sock) { + int status; + unsigned long param = 1; + DWORD ret; + status = WSAIoctl(sock, FIONBIO, ¶m, sizeof(param), NULL, 0, &ret, + NULL, NULL); + return status == 0; +} + +int grpc_tcp_prepare_socket(SOCKET sock) { + if (!set_non_block(sock)) + return 0; + return 1; +} + +typedef struct grpc_tcp { + grpc_endpoint base; + grpc_winsocket *socket; + gpr_refcount refcount; + + grpc_endpoint_read_cb read_cb; + void *read_user_data; + gpr_slice read_slice; + int outstanding_read; + + grpc_endpoint_write_cb write_cb; + void *write_user_data; + gpr_slice_buffer write_slices; + int outstanding_write; + +} grpc_tcp; + +static void tcp_ref(grpc_tcp *tcp) { + gpr_log(GPR_DEBUG, "tcp_ref"); + gpr_ref(&tcp->refcount); +} + +static void tcp_unref(grpc_tcp *tcp) { + gpr_log(GPR_DEBUG, "tcp_unref"); + if (gpr_unref(&tcp->refcount)) { + gpr_log(GPR_DEBUG, "tcp_unref: destroying"); + gpr_slice_buffer_destroy(&tcp->write_slices); + grpc_winsocket_orphan(tcp->socket); + gpr_free(tcp); + } +} + +static void on_read(void *tcpp, int success) { + grpc_tcp *tcp = (grpc_tcp *) tcpp; + grpc_winsocket *socket = tcp->socket; + gpr_slice sub; + gpr_slice *slice = NULL; + size_t nslices = 0; + grpc_endpoint_cb_status status; + grpc_endpoint_read_cb cb = tcp->read_cb; + grpc_winsocket_callback_info *info = &socket->read_info; + void *opaque = tcp->read_user_data; + + GPR_ASSERT(tcp->outstanding_read); + + if (!success) { + __debugbreak(); + abort(); + } + + gpr_log(GPR_DEBUG, "on_read"); + tcp->outstanding_read = 0; + + if (socket->read_info.wsa_error != 0) { + char *utf8_message = gpr_format_message(info->wsa_error); + __debugbreak(); + gpr_log(GPR_ERROR, "ReadFile overlapped error: %s", utf8_message); + gpr_free(utf8_message); + status = GRPC_ENDPOINT_CB_ERROR; + } else { + if (info->bytes_transfered != 0) { + sub = gpr_slice_sub(tcp->read_slice, 0, info->bytes_transfered); + gpr_log(GPR_DEBUG, "on_read: calling callback"); + status = GRPC_ENDPOINT_CB_OK; + slice = ⊂ + nslices = 1; + } else { + gpr_log(GPR_DEBUG, "on_read: closed socket"); + gpr_slice_unref(tcp->read_slice); + status = GRPC_ENDPOINT_CB_EOF; + } + } + tcp_unref(tcp); + cb(opaque, slice, nslices, status); +} + +static void win_notify_on_read(grpc_endpoint *ep, + grpc_endpoint_read_cb cb, void *arg) { + grpc_tcp *tcp = (grpc_tcp *) ep; + grpc_winsocket *handle = tcp->socket; + grpc_winsocket_callback_info *info = &handle->read_info; + int status; + DWORD bytes_read = 0; + DWORD flags = 0; + int error; + WSABUF buffer; + + GPR_ASSERT(!tcp->outstanding_read); + tcp_ref(tcp); + tcp->outstanding_read = 1; + tcp->read_cb = cb; + tcp->read_user_data = arg; + + tcp->read_slice = gpr_slice_malloc(8192); + + buffer.len = GPR_SLICE_LENGTH(tcp->read_slice); + buffer.buf = GPR_SLICE_START_PTR(tcp->read_slice); + + gpr_log(GPR_DEBUG, "win_notify_on_read: calling WSARecv without overlap"); + + status = WSARecv(tcp->socket->socket, &buffer, 1, &bytes_read, &flags, + NULL, NULL); + info->wsa_error = status == 0 ? 0 : WSAGetLastError(); + + if (info->wsa_error != WSAEWOULDBLOCK) { + gpr_log(GPR_DEBUG, "got response immediately, calling on_read"); + info->bytes_transfered = bytes_read; + /* This might heavily recurse. */ + on_read(tcp, 1); + return; + } + + gpr_log(GPR_DEBUG, "got WSAEWOULDBLOCK - calling WSARecv with overlap"); + + memset(&tcp->socket->read_info.overlapped, 0, sizeof(OVERLAPPED)); + status = WSARecv(tcp->socket->socket, &buffer, 1, &bytes_read, &flags, + &info->overlapped, NULL); + + if (status == 0) { + gpr_log(GPR_DEBUG, "got response immediately, but we're goint to sleep"); + grpc_handle_notify_on_read(tcp->socket, on_read, tcp); + return; + } + + error = WSAGetLastError(); + + if (error != WSA_IO_PENDING) { + char *utf8_message = gpr_format_message(WSAGetLastError()); + __debugbreak(); + gpr_log(GPR_ERROR, "WSARecv error: %s", utf8_message); + gpr_free(utf8_message); + /* would the IO completion port be called anyway... ? Let's assume not. */ + tcp->outstanding_read = 0; + tcp_unref(tcp); + cb(arg, NULL, 0, GRPC_ENDPOINT_CB_ERROR); + return; + } + + gpr_log(GPR_DEBUG, "waiting on the IO completion port now"); + grpc_handle_notify_on_read(tcp->socket, on_read, tcp); +} + +static void on_write(void *tcpp, int success) { + grpc_tcp *tcp = (grpc_tcp *) tcpp; + grpc_winsocket *handle = tcp->socket; + grpc_winsocket_callback_info *info = &handle->write_info; + grpc_endpoint_cb_status status = GRPC_ENDPOINT_CB_OK; + grpc_endpoint_write_cb cb = tcp->write_cb; + void *opaque = tcp->write_user_data; + + GPR_ASSERT(tcp->outstanding_write); + + gpr_log(GPR_DEBUG, "on_write"); + + if (!success) { + __debugbreak(); + abort(); + } + + if (info->wsa_error != 0) { + char *utf8_message = gpr_format_message(info->wsa_error); + gpr_log(GPR_ERROR, "WSASend overlapped error: %s", utf8_message); + gpr_free(utf8_message); + status = GRPC_ENDPOINT_CB_ERROR; + } else { + GPR_ASSERT(info->bytes_transfered == tcp->write_slices.length); + } + + gpr_slice_buffer_reset_and_unref(&tcp->write_slices); + tcp->outstanding_write = 0; + + tcp_unref(tcp); + cb(opaque, status); +} + +static grpc_endpoint_write_status win_write(grpc_endpoint *ep, + gpr_slice *slices, size_t nslices, + grpc_endpoint_write_cb cb, + void *arg) { + grpc_tcp *tcp = (grpc_tcp *) ep; + grpc_winsocket *socket = tcp->socket; + grpc_winsocket_callback_info *info = &socket->write_info; + unsigned i; + DWORD bytes_sent; + int status; + WSABUF local_buffers[16]; + WSABUF *allocated = NULL; + WSABUF *buffers = local_buffers; + + GPR_ASSERT(nslices != 0); + GPR_ASSERT(GPR_SLICE_LENGTH(slices[0]) != 0); + GPR_ASSERT(!tcp->outstanding_write); + tcp_ref(tcp); + + gpr_log(GPR_DEBUG, "win_write"); + + tcp->outstanding_write = 1; + tcp->write_cb = cb; + tcp->write_user_data = arg; + gpr_slice_buffer_addn(&tcp->write_slices, slices, nslices); + + if (tcp->write_slices.count > GPR_ARRAY_SIZE(local_buffers)) { + buffers = (WSABUF *) gpr_malloc(sizeof(WSABUF) * tcp->write_slices.count); + allocated = buffers; + } + + for (i = 0; i < tcp->write_slices.count; i++) { + buffers[i].len = GPR_SLICE_LENGTH(tcp->write_slices.slices[i]); + buffers[i].buf = GPR_SLICE_START_PTR(tcp->write_slices.slices[i]); + } + + gpr_log(GPR_DEBUG, "win_write: calling WSASend without overlap"); + status = WSASend(socket->socket, buffers, tcp->write_slices.count, + &bytes_sent, 0, NULL, NULL); + info->wsa_error = status == 0 ? 0 : WSAGetLastError(); + + if (info->wsa_error != WSAEWOULDBLOCK) { + grpc_endpoint_write_status ret = GRPC_ENDPOINT_WRITE_ERROR; + gpr_log(GPR_DEBUG, "got response immediately, cleaning up and leaving"); + if (status == 0) { + ret = GRPC_ENDPOINT_WRITE_DONE; + GPR_ASSERT(bytes_sent == tcp->write_slices.length); + } + if (allocated) gpr_free(allocated); + gpr_slice_buffer_reset_and_unref(&tcp->write_slices); + tcp->outstanding_write = 0; + tcp_unref(tcp); + return ret; + } + + gpr_log(GPR_DEBUG, "got WSAEWOULDBLOCK - calling WSASend with overlap"); + + memset(&socket->write_info, 0, sizeof(OVERLAPPED)); + status = WSASend(socket->socket, buffers, tcp->write_slices.count, + &bytes_sent, 0, &socket->write_info.overlapped, NULL); + if (allocated) gpr_free(allocated); + + if (status != 0) { + int error = WSAGetLastError(); + if (error != WSA_IO_PENDING) { + char *utf8_message = gpr_format_message(WSAGetLastError()); + __debugbreak(); + gpr_log(GPR_ERROR, "WSASend error: %s", utf8_message); + gpr_free(utf8_message); + /* would the IO completion port be called anyway ? Let's assume not. */ + tcp->outstanding_write = 0; + tcp_unref(tcp); + return GRPC_ENDPOINT_WRITE_ERROR; + } + gpr_log(GPR_DEBUG, "win_write: got pending op"); + } else { + gpr_log(GPR_DEBUG, "wrote data immediately - but we're going to sleep"); + } + + grpc_handle_notify_on_write(socket, on_write, tcp); + return GRPC_ENDPOINT_WRITE_PENDING; +} + +static void win_add_to_pollset(grpc_endpoint *ep, grpc_pollset *pollset) { + grpc_tcp *tcp = (grpc_tcp *) ep; + gpr_log(GPR_DEBUG, "win_add_to_pollset"); + grpc_pollset_add_handle(pollset, tcp->socket); +} + +static void win_shutdown(grpc_endpoint *ep) { + grpc_tcp *tcp = (grpc_tcp *) ep; + gpr_log(GPR_DEBUG, "win_shutdown"); + grpc_winsocket_shutdown(tcp->socket); +} + +static void win_destroy(grpc_endpoint *ep) { + grpc_tcp *tcp = (grpc_tcp *) ep; + gpr_log(GPR_DEBUG, "win_destroy"); + tcp_unref(tcp); +} + +static grpc_endpoint_vtable vtable = { + win_notify_on_read, win_write, win_add_to_pollset, win_shutdown, win_destroy +}; + +grpc_endpoint *grpc_tcp_create(grpc_winsocket *socket) { + grpc_tcp *tcp = (grpc_tcp *) gpr_malloc(sizeof(grpc_tcp)); + memset(tcp, 0, sizeof(grpc_tcp)); + tcp->base.vtable = &vtable; + tcp->socket = socket; + gpr_slice_buffer_init(&tcp->write_slices); + gpr_ref_init(&tcp->refcount, 1); + return &tcp->base; +} + +#endif /* GPR_WINSOCK_SOCKET */ diff --git a/src/core/iomgr/tcp_windows.h b/src/core/iomgr/tcp_windows.h new file mode 100644 index 00000000000..cbe60801b49 --- /dev/null +++ b/src/core/iomgr/tcp_windows.h @@ -0,0 +1,57 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef __GRPC_INTERNAL_IOMGR_TCP_WINDOWS_H__ +#define __GRPC_INTERNAL_IOMGR_TCP_WINDOWS_H__ +/* + Low level TCP "bottom half" implementation, for use by transports built on + top of a TCP connection. + + Note that this file does not (yet) include APIs for creating the socket in + the first place. + + All calls passing slice transfer ownership of a slice refcount unless + otherwise specified. +*/ + +#include "src/core/iomgr/endpoint.h" +#include "src/core/iomgr/socket_windows.h" + +/* Create a tcp endpoint given a winsock handle. + * Takes ownership of the handle. + */ +grpc_endpoint *grpc_tcp_create(grpc_winsocket *socket); + +int grpc_tcp_prepare_socket(SOCKET sock); + +#endif /* __GRPC_INTERNAL_IOMGR_TCP_WINDOWS_H__ */ diff --git a/src/core/support/log_win32.c b/src/core/support/log_win32.c index dc8c1d0785a..e1cf6fb10a4 100644 --- a/src/core/support/log_win32.c +++ b/src/core/support/log_win32.c @@ -35,6 +35,7 @@ #ifdef GPR_WIN32 +#include #include #include #include @@ -74,8 +75,42 @@ void gpr_log(const char *file, int line, gpr_log_severity severity, /* Simple starter implementation */ void gpr_default_log(gpr_log_func_args *args) { - fprintf(stderr, "%s %s:%d: %s\n", gpr_log_severity_string(args->severity), + fprintf(stderr, "%s.%u %s:%d: %s\n", + gpr_log_severity_string(args->severity), GetCurrentThreadId(), args->file, args->line, args->message); } +/* Arguable, this could become a public function. But hardly + * anything beside the Windows implementation should use + * it, so, never mind...*/ +#if defined UNICODE || defined _UNICODE +static char *tchar_to_char(LPWSTR input) { + char *ret; + int needed = WideCharToMultiByte(CP_UTF8, 0, input, -1, NULL, 0, NULL, NULL); + if (needed == 0) return NULL; + ret = gpr_malloc(needed + 1); + WideCharToMultiByte(CP_UTF8, 0, input, -1, ret, needed, NULL, NULL); + ret[needed] = 0; + return ret; +} +#else +static char *tchar_to_char(LPSTR input) { + return gpr_strdup(input); +} #endif + +char *gpr_format_message(DWORD messageid) { + LPTSTR tmessage; + char *message; + DWORD status = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, messageid, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)(&tmessage), 0, NULL); + message = tchar_to_char(tmessage); + LocalFree(tmessage); + return message; +} + +#endif /* GPR_WIN32 */ diff --git a/test/core/end2end/cq_verifier.c b/test/core/end2end/cq_verifier.c index 287f83eebca..cf3455ca6b6 100644 --- a/test/core/end2end/cq_verifier.c +++ b/test/core/end2end/cq_verifier.c @@ -31,13 +31,6 @@ * */ -/* Disable sprintf warnings on Windows (it's fine to do that for test code). - Also, cases where sprintf is called are crash sites anyway. - TODO(jtattermusch): b/18636890 */ -#ifdef _MSC_VER -#define _CRT_SECURE_NO_WARNINGS -#endif - #include "test/core/end2end/cq_verifier.h" #include diff --git a/vsprojects/vs2013/global.props b/vsprojects/vs2013/global.props index 6a9050e3d2b..27efc13b247 100644 --- a/vsprojects/vs2013/global.props +++ b/vsprojects/vs2013/global.props @@ -1,12 +1,14 @@ - - - - - - - - $(SolutionDir)\..\..;$(SolutionDir)\..\..\include;$(SolutionDir)\..\..\third_party\zlib;$(SolutionDir)\..\third_party;$(SolutionDir)\..\..\third_party\openssl\inc32 - - - + + + + + + + + $(SolutionDir)\..\..;$(SolutionDir)\..\..\include;$(SolutionDir)\..\..\third_party\zlib;$(SolutionDir)\..\third_party;$(SolutionDir)\..\..\third_party\openssl\inc32 + _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;%(PreprocessorDefinitions) + EnableAllWarnings + + + \ No newline at end of file diff --git a/vsprojects/vs2013/gpr.vcxproj b/vsprojects/vs2013/gpr.vcxproj index f71b586aff4..0a11b617c5d 100644 --- a/vsprojects/vs2013/gpr.vcxproj +++ b/vsprojects/vs2013/gpr.vcxproj @@ -83,6 +83,7 @@ + diff --git a/vsprojects/vs2013/gpr.vcxproj.filters b/vsprojects/vs2013/gpr.vcxproj.filters index 013ed4b3c99..2a8eb1d9a4f 100644 --- a/vsprojects/vs2013/gpr.vcxproj.filters +++ b/vsprojects/vs2013/gpr.vcxproj.filters @@ -111,6 +111,9 @@ include\grpc\support + + include\grpc\support + include\grpc\support diff --git a/vsprojects/vs2013/grpc.vcxproj b/vsprojects/vs2013/grpc.vcxproj index 21a1f06f6d6..10c8472b7e3 100644 --- a/vsprojects/vs2013/grpc.vcxproj +++ b/vsprojects/vs2013/grpc.vcxproj @@ -118,6 +118,7 @@ + @@ -130,9 +131,11 @@ + + @@ -256,6 +259,8 @@ + + @@ -274,12 +279,18 @@ + + + + + + diff --git a/vsprojects/vs2013/grpc.vcxproj.filters b/vsprojects/vs2013/grpc.vcxproj.filters index 3af681a4666..53f3a1161ac 100644 --- a/vsprojects/vs2013/grpc.vcxproj.filters +++ b/vsprojects/vs2013/grpc.vcxproj.filters @@ -118,6 +118,9 @@ src\core\iomgr + + src\core\iomgr + src\core\iomgr @@ -145,15 +148,24 @@ src\core\iomgr + + src\core\iomgr + src\core\iomgr + + src\core\iomgr + src\core\iomgr src\core\iomgr + + src\core\iomgr + src\core\iomgr @@ -437,6 +449,9 @@ src\core\iomgr + + src\core\iomgr + src\core\iomgr @@ -473,6 +488,9 @@ src\core\iomgr + + src\core\iomgr + src\core\iomgr @@ -482,6 +500,9 @@ src\core\iomgr + + src\core\iomgr + src\core\iomgr diff --git a/vsprojects/vs2013/grpc_unsecure.vcxproj b/vsprojects/vs2013/grpc_unsecure.vcxproj index 21a1f06f6d6..10c8472b7e3 100644 --- a/vsprojects/vs2013/grpc_unsecure.vcxproj +++ b/vsprojects/vs2013/grpc_unsecure.vcxproj @@ -118,6 +118,7 @@ + @@ -130,9 +131,11 @@ + + @@ -256,6 +259,8 @@ + + @@ -274,12 +279,18 @@ + + + + + + diff --git a/vsprojects/vs2013/grpc_unsecure.vcxproj.filters b/vsprojects/vs2013/grpc_unsecure.vcxproj.filters index 4dadb611921..7b37c6d2472 100644 --- a/vsprojects/vs2013/grpc_unsecure.vcxproj.filters +++ b/vsprojects/vs2013/grpc_unsecure.vcxproj.filters @@ -79,6 +79,9 @@ src\core\iomgr + + src\core\iomgr + src\core\iomgr @@ -106,15 +109,24 @@ src\core\iomgr + + src\core\iomgr + src\core\iomgr + + src\core\iomgr + src\core\iomgr src\core\iomgr + + src\core\iomgr + src\core\iomgr @@ -362,6 +374,9 @@ src\core\iomgr + + src\core\iomgr + src\core\iomgr @@ -398,6 +413,9 @@ src\core\iomgr + + src\core\iomgr + src\core\iomgr @@ -407,6 +425,9 @@ src\core\iomgr + + src\core\iomgr + src\core\iomgr From ee0c96c7fc4bdcf908969eedb96b248e33db1cbb Mon Sep 17 00:00:00 2001 From: "Nicolas \"Pixel\" Noble" Date: Wed, 4 Feb 2015 23:35:41 +0100 Subject: [PATCH 02/26] Second draft of the win32 implementation. -) Client code is now threadsafe. -) The echo_client code runs and succeeds. --- include/grpc/support/log_win32.h | 2 +- src/core/iomgr/iomgr_posix.c | 2 +- src/core/iomgr/iomgr_windows.c | 2 +- src/core/iomgr/pollset_windows.c | 30 ++++++++++++++++++++++++++---- src/core/iomgr/pollset_windows.h | 6 ++++-- src/core/iomgr/sockaddr_win32.h | 2 +- src/core/iomgr/socket_windows.c | 4 ++-- src/core/iomgr/socket_windows.h | 4 +++- src/core/iomgr/tcp_windows.c | 26 ++++++++++++++++++++------ src/core/support/log_win32.c | 17 +++++++++++++++-- 10 files changed, 74 insertions(+), 21 deletions(-) diff --git a/include/grpc/support/log_win32.h b/include/grpc/support/log_win32.h index 7abd5df3941..0350056d26e 100644 --- a/include/grpc/support/log_win32.h +++ b/include/grpc/support/log_win32.h @@ -42,7 +42,7 @@ extern "C" { /* Returns a string allocated with gpr_malloc that contains a UTF-8 * formatted error message, corresponding to the error messageid. - * Use in cunjunction with GetLastError() et al. + * Use in conjunction with GetLastError() et al. */ char *gpr_format_message(DWORD messageid); diff --git a/src/core/iomgr/iomgr_posix.c b/src/core/iomgr/iomgr_posix.c index 22ca74314cb..bbf8cfc4190 100644 --- a/src/core/iomgr/iomgr_posix.c +++ b/src/core/iomgr/iomgr_posix.c @@ -48,4 +48,4 @@ void grpc_iomgr_platform_shutdown(void) { grpc_fd_global_shutdown(); } -#endif /* GRPC_IOMGRP_POSIX */ +#endif /* GRPC_POSIX_SOCKET */ diff --git a/src/core/iomgr/iomgr_windows.c b/src/core/iomgr/iomgr_windows.c index 9cdf224ad77..5c8382e1c01 100644 --- a/src/core/iomgr/iomgr_windows.c +++ b/src/core/iomgr/iomgr_windows.c @@ -64,4 +64,4 @@ void grpc_iomgr_platform_shutdown(void) { winsock_shutdown(); } -#endif /* GRPC_IOMGRP_POSIX */ +#endif /* GRPC_WINSOCK_SOCKET */ diff --git a/src/core/iomgr/pollset_windows.c b/src/core/iomgr/pollset_windows.c index fdebede4828..134e6f45e21 100644 --- a/src/core/iomgr/pollset_windows.c +++ b/src/core/iomgr/pollset_windows.c @@ -38,6 +38,8 @@ #include #include +#include +#include #include #include "src/core/iomgr/alarm_internal.h" @@ -53,6 +55,8 @@ static gpr_event g_shutdown_global_poller; static gpr_event g_global_poller_done; void grpc_pollset_init(grpc_pollset *pollset) { + gpr_mu_init(&pollset->mu); + gpr_cv_init(&pollset->cv); pollset->iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, (ULONG_PTR)NULL, 0); GPR_ASSERT(pollset->iocp); @@ -60,6 +64,8 @@ void grpc_pollset_init(grpc_pollset *pollset) { void grpc_pollset_destroy(grpc_pollset *pollset) { BOOL status; + gpr_mu_destroy(&pollset->mu); + gpr_cv_destroy(&pollset->cv); status = CloseHandle(pollset->iocp); GPR_ASSERT(status); } @@ -76,10 +82,11 @@ static int pollset_poll(grpc_pollset *pollset, grpc_winsocket_callback_info *info; void(*f)(void *, int) = NULL; void *opaque = NULL; + gpr_mu_unlock(&pollset->mu); success = GetQueuedCompletionStatus(pollset->iocp, &bytes, &completion_key, &overlapped, gpr_time_to_millis(wait_time)); - + gpr_mu_lock(&pollset->mu); if (!success && !overlapped) { /* The deadline got attained. */ return 0; @@ -95,6 +102,8 @@ static int pollset_poll(grpc_pollset *pollset, abort(); } + GPR_ASSERT(pollset == &g_global_pollset); + socket = (grpc_winsocket*) completion_key; if (overlapped == &socket->write_info.overlapped) { gpr_log(GPR_DEBUG, "pollset_poll - got write packet"); @@ -153,7 +162,9 @@ void grpc_pollset_kick(grpc_pollset *pollset) { static void global_poller(void *p) { while (!gpr_event_get(&g_shutdown_global_poller)) { + gpr_mu_lock(&g_global_pollset.mu); grpc_pollset_work(&g_global_pollset, gpr_inf_future); + gpr_mu_unlock(&g_global_pollset.mu); } gpr_event_set(&g_global_poller_done, (void *) 1); @@ -176,9 +187,20 @@ void grpc_pollset_global_shutdown(void) { } void grpc_pollset_add_handle(grpc_pollset *pollset, grpc_winsocket *socket) { - HANDLE ret = CreateIoCompletionPort((HANDLE) socket->socket, pollset->iocp, - (gpr_uintptr) socket, 0); - GPR_ASSERT(ret == pollset->iocp); + HANDLE ret; + if (socket->added_to_iocp) return; + ret = CreateIoCompletionPort((HANDLE)socket->socket, + g_global_pollset.iocp, + (gpr_uintptr) socket, 0); + if (!ret) { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "Unable to add socket to iocp: %s", utf8_message); + gpr_free(utf8_message); + __debugbreak(); + abort(); + } + socket->added_to_iocp = 1; + GPR_ASSERT(ret == g_global_pollset.iocp); } static void handle_notify_on_iocp(grpc_winsocket *socket, diff --git a/src/core/iomgr/pollset_windows.h b/src/core/iomgr/pollset_windows.h index 036f3910a01..919af5d7b73 100644 --- a/src/core/iomgr/pollset_windows.h +++ b/src/core/iomgr/pollset_windows.h @@ -46,11 +46,13 @@ struct grpc_fd; typedef struct grpc_pollset { + gpr_mu mu; + gpr_cv cv; HANDLE iocp; } grpc_pollset; -#define GRPC_POLLSET_MU(pollset) (NULL) -#define GRPC_POLLSET_CV(pollset) (NULL) +#define GRPC_POLLSET_MU(pollset) (&(pollset)->mu) +#define GRPC_POLLSET_CV(pollset) (&(pollset)->cv) void grpc_pollset_add_handle(grpc_pollset *, grpc_winsocket *); diff --git a/src/core/iomgr/sockaddr_win32.h b/src/core/iomgr/sockaddr_win32.h index 350f5fa33e1..08be0e54f8e 100644 --- a/src/core/iomgr/sockaddr_win32.h +++ b/src/core/iomgr/sockaddr_win32.h @@ -38,4 +38,4 @@ #include #include -#endif // __GRPC_INTERNAL_IOMGR_SOCKADDR_WIN32_H_ +#endif /* __GRPC_INTERNAL_IOMGR_SOCKADDR_WIN32_H_ */ diff --git a/src/core/iomgr/socket_windows.c b/src/core/iomgr/socket_windows.c index 7d1d59c3187..805e96a0d15 100644 --- a/src/core/iomgr/socket_windows.c +++ b/src/core/iomgr/socket_windows.c @@ -56,7 +56,7 @@ grpc_winsocket *grpc_winsocket_create(SOCKET socket) { void shutdown_op(grpc_winsocket_callback_info *info) { if (!info->cb) return; - info->cb(info->opaque, 0); + grpc_iomgr_add_delayed_callback(info->cb, info->opaque, 0); } void grpc_winsocket_shutdown(grpc_winsocket *socket) { @@ -73,4 +73,4 @@ void grpc_winsocket_orphan(grpc_winsocket *socket) { gpr_free(socket); } -#endif +#endif /* GPR_WINSOCK_SOCKET */ diff --git a/src/core/iomgr/socket_windows.h b/src/core/iomgr/socket_windows.h index ca85ea54cbf..19822ac085c 100644 --- a/src/core/iomgr/socket_windows.h +++ b/src/core/iomgr/socket_windows.h @@ -57,6 +57,8 @@ typedef struct grpc_winsocket_callback_info { typedef struct grpc_winsocket { SOCKET socket; + int added_to_iocp; + grpc_winsocket_callback_info write_info; grpc_winsocket_callback_info read_info; @@ -70,4 +72,4 @@ grpc_winsocket *grpc_winsocket_create(SOCKET socket); void grpc_winsocket_shutdown(grpc_winsocket *socket); void grpc_winsocket_orphan(grpc_winsocket *socket); -#endif /* __GRPC_INTERNAL_IOMGR_FD_POSIX_H_ */ +#endif /* __GRPC_INTERNAL_IOMGR_HANDLE_WINDOWS_H__ */ diff --git a/src/core/iomgr/tcp_windows.c b/src/core/iomgr/tcp_windows.c index 0eb6663dd48..967177fdb1c 100644 --- a/src/core/iomgr/tcp_windows.c +++ b/src/core/iomgr/tcp_windows.c @@ -58,9 +58,18 @@ static int set_non_block(SOCKET sock) { return status == 0; } +static int set_dualstack(SOCKET sock) { + int status; + unsigned long param = 0; + status = setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, ¶m, sizeof(param)); + return status == 0; +} + int grpc_tcp_prepare_socket(SOCKET sock) { if (!set_non_block(sock)) return 0; + if (!set_dualstack(sock)) + return 0; return 1; } @@ -110,8 +119,9 @@ static void on_read(void *tcpp, int success) { GPR_ASSERT(tcp->outstanding_read); if (!success) { - __debugbreak(); - abort(); + tcp_unref(tcp); + cb(opaque, NULL, 0, GRPC_ENDPOINT_CB_SHUTDOWN); + return; } gpr_log(GPR_DEBUG, "on_read"); @@ -163,7 +173,6 @@ static void win_notify_on_read(grpc_endpoint *ep, buffer.buf = GPR_SLICE_START_PTR(tcp->read_slice); gpr_log(GPR_DEBUG, "win_notify_on_read: calling WSARecv without overlap"); - status = WSARecv(tcp->socket->socket, &buffer, 1, &bytes_read, &flags, NULL, NULL); info->wsa_error = status == 0 ? 0 : WSAGetLastError(); @@ -183,7 +192,7 @@ static void win_notify_on_read(grpc_endpoint *ep, &info->overlapped, NULL); if (status == 0) { - gpr_log(GPR_DEBUG, "got response immediately, but we're goint to sleep"); + gpr_log(GPR_DEBUG, "got response immediately, but we're going to sleep"); grpc_handle_notify_on_read(tcp->socket, on_read, tcp); return; } @@ -219,8 +228,9 @@ static void on_write(void *tcpp, int success) { gpr_log(GPR_DEBUG, "on_write"); if (!success) { - __debugbreak(); - abort(); + tcp_unref(tcp); + cb(opaque, NULL, 0, GRPC_ENDPOINT_CB_SHUTDOWN); + return; } if (info->wsa_error != 0) { @@ -286,6 +296,10 @@ static grpc_endpoint_write_status win_write(grpc_endpoint *ep, if (status == 0) { ret = GRPC_ENDPOINT_WRITE_DONE; GPR_ASSERT(bytes_sent == tcp->write_slices.length); + } else { + char *utf8_message = gpr_format_message(info->wsa_error); + gpr_log(GPR_ERROR, "WSASend error: %s", utf8_message); + gpr_free(utf8_message); } if (allocated) gpr_free(allocated); gpr_slice_buffer_reset_and_unref(&tcp->write_slices); diff --git a/src/core/support/log_win32.c b/src/core/support/log_win32.c index e1cf6fb10a4..4c0a866048f 100644 --- a/src/core/support/log_win32.c +++ b/src/core/support/log_win32.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -75,8 +76,20 @@ void gpr_log(const char *file, int line, gpr_log_severity severity, /* Simple starter implementation */ void gpr_default_log(gpr_log_func_args *args) { - fprintf(stderr, "%s.%u %s:%d: %s\n", - gpr_log_severity_string(args->severity), GetCurrentThreadId(), + char time_buffer[64]; + gpr_timespec now = gpr_now(); + struct tm tm; + + if (localtime_s(&tm, &now.tv_sec)) { + strcpy(time_buffer, "error:localtime"); + } else if (0 == + strftime(time_buffer, sizeof(time_buffer), "%m%d %H:%M:%S", &tm)) { + strcpy(time_buffer, "error:strftime"); + } + + fprintf(stderr, "%s%s.%09u %5u %s:%d: %s\n", + gpr_log_severity_string(args->severity), time_buffer, + (int)(now.tv_nsec), GetCurrentThreadId(), args->file, args->line, args->message); } From ba410fabdf476ba61fef22a1f3205bc6d8f96a4b Mon Sep 17 00:00:00 2001 From: "Nicolas \"Pixel\" Noble" Date: Thu, 5 Feb 2015 00:54:15 +0100 Subject: [PATCH 03/26] Fixing bad copy/paste. --- src/core/iomgr/tcp_windows.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/iomgr/tcp_windows.c b/src/core/iomgr/tcp_windows.c index 967177fdb1c..734628dd8ee 100644 --- a/src/core/iomgr/tcp_windows.c +++ b/src/core/iomgr/tcp_windows.c @@ -229,7 +229,7 @@ static void on_write(void *tcpp, int success) { if (!success) { tcp_unref(tcp); - cb(opaque, NULL, 0, GRPC_ENDPOINT_CB_SHUTDOWN); + cb(opaque, GRPC_ENDPOINT_CB_SHUTDOWN); return; } From 0f3ec822380081670edd0e9e1d7e40c1122bda21 Mon Sep 17 00:00:00 2001 From: "Nicolas \"Pixel\" Noble" Date: Thu, 5 Feb 2015 19:40:38 +0100 Subject: [PATCH 04/26] Adding Windows tcp server code. --- Makefile | 5 + build.json | 1 + src/core/iomgr/sockaddr_utils.c | 19 +- src/core/iomgr/sockaddr_utils.h | 6 + src/core/iomgr/tcp_client_windows.c | 8 +- src/core/iomgr/tcp_server_windows.c | 372 ++++++++++++++++++ src/core/iomgr/tcp_windows.c | 5 +- vsprojects/vs2013/grpc.vcxproj | 2 + vsprojects/vs2013/grpc.vcxproj.filters | 3 + vsprojects/vs2013/grpc_unsecure.vcxproj | 2 + .../vs2013/grpc_unsecure.vcxproj.filters | 3 + 11 files changed, 412 insertions(+), 14 deletions(-) create mode 100644 src/core/iomgr/tcp_server_windows.c diff --git a/Makefile b/Makefile index 2dd70a5a333..7a4ca9303eb 100644 --- a/Makefile +++ b/Makefile @@ -1437,6 +1437,7 @@ LIBGRPC_SRC = \ src/core/iomgr/tcp_client_windows.c \ src/core/iomgr/tcp_posix.c \ src/core/iomgr/tcp_server_posix.c \ + src/core/iomgr/tcp_server_windows.c \ src/core/iomgr/tcp_windows.c \ src/core/iomgr/time_averaged_stats.c \ src/core/iomgr/wakeup_fd_eventfd.c \ @@ -1567,6 +1568,7 @@ src/core/iomgr/tcp_client_posix.c: $(OPENSSL_DEP) src/core/iomgr/tcp_client_windows.c: $(OPENSSL_DEP) src/core/iomgr/tcp_posix.c: $(OPENSSL_DEP) src/core/iomgr/tcp_server_posix.c: $(OPENSSL_DEP) +src/core/iomgr/tcp_server_windows.c: $(OPENSSL_DEP) src/core/iomgr/tcp_windows.c: $(OPENSSL_DEP) src/core/iomgr/time_averaged_stats.c: $(OPENSSL_DEP) src/core/iomgr/wakeup_fd_eventfd.c: $(OPENSSL_DEP) @@ -1719,6 +1721,7 @@ objs/$(CONFIG)/src/core/iomgr/tcp_client_posix.o: objs/$(CONFIG)/src/core/iomgr/tcp_client_windows.o: objs/$(CONFIG)/src/core/iomgr/tcp_posix.o: objs/$(CONFIG)/src/core/iomgr/tcp_server_posix.o: +objs/$(CONFIG)/src/core/iomgr/tcp_server_windows.o: objs/$(CONFIG)/src/core/iomgr/tcp_windows.o: objs/$(CONFIG)/src/core/iomgr/time_averaged_stats.o: objs/$(CONFIG)/src/core/iomgr/wakeup_fd_eventfd.o: @@ -1890,6 +1893,7 @@ LIBGRPC_UNSECURE_SRC = \ src/core/iomgr/tcp_client_windows.c \ src/core/iomgr/tcp_posix.c \ src/core/iomgr/tcp_server_posix.c \ + src/core/iomgr/tcp_server_windows.c \ src/core/iomgr/tcp_windows.c \ src/core/iomgr/time_averaged_stats.c \ src/core/iomgr/wakeup_fd_eventfd.c \ @@ -2025,6 +2029,7 @@ objs/$(CONFIG)/src/core/iomgr/tcp_client_posix.o: objs/$(CONFIG)/src/core/iomgr/tcp_client_windows.o: objs/$(CONFIG)/src/core/iomgr/tcp_posix.o: objs/$(CONFIG)/src/core/iomgr/tcp_server_posix.o: +objs/$(CONFIG)/src/core/iomgr/tcp_server_windows.o: objs/$(CONFIG)/src/core/iomgr/tcp_windows.o: objs/$(CONFIG)/src/core/iomgr/time_averaged_stats.o: objs/$(CONFIG)/src/core/iomgr/wakeup_fd_eventfd.o: diff --git a/build.json b/build.json index 98377cddad5..4a23f245d2d 100644 --- a/build.json +++ b/build.json @@ -149,6 +149,7 @@ "src/core/iomgr/tcp_client_windows.c", "src/core/iomgr/tcp_posix.c", "src/core/iomgr/tcp_server_posix.c", + "src/core/iomgr/tcp_server_windows.c", "src/core/iomgr/tcp_windows.c", "src/core/iomgr/time_averaged_stats.c", "src/core/iomgr/wakeup_fd_eventfd.c", diff --git a/src/core/iomgr/sockaddr_utils.c b/src/core/iomgr/sockaddr_utils.c index 07bf7b3a35c..8dcfca74c68 100644 --- a/src/core/iomgr/sockaddr_utils.c +++ b/src/core/iomgr/sockaddr_utils.c @@ -111,13 +111,20 @@ int grpc_sockaddr_is_wildcard(const struct sockaddr *addr, int *port_out) { void grpc_sockaddr_make_wildcards(int port, struct sockaddr_in *wild4_out, struct sockaddr_in6 *wild6_out) { - memset(wild4_out, 0, sizeof(*wild4_out)); - wild4_out->sin_family = AF_INET; - wild4_out->sin_port = htons(port); + grpc_sockaddr_make_wildcard4(port, wild4_out); + grpc_sockaddr_make_wildcard6(port, wild6_out); +} + +void grpc_sockaddr_make_wildcard4(int port, struct sockaddr_in *wild_out) { + memset(wild_out, 0, sizeof(*wild_out)); + wild_out->sin_family = AF_INET; + wild_out->sin_port = htons(port); +} - memset(wild6_out, 0, sizeof(*wild6_out)); - wild6_out->sin6_family = AF_INET6; - wild6_out->sin6_port = htons(port); +void grpc_sockaddr_make_wildcard6(int port, struct sockaddr_in6 *wild_out) { + memset(wild_out, 0, sizeof(*wild_out)); + wild_out->sin6_family = AF_INET6; + wild_out->sin6_port = htons(port); } int grpc_sockaddr_to_string(char **out, const struct sockaddr *addr, diff --git a/src/core/iomgr/sockaddr_utils.h b/src/core/iomgr/sockaddr_utils.h index 3f5b770e865..b49cc50491c 100644 --- a/src/core/iomgr/sockaddr_utils.h +++ b/src/core/iomgr/sockaddr_utils.h @@ -57,6 +57,12 @@ int grpc_sockaddr_is_wildcard(const struct sockaddr *addr, int *port_out); void grpc_sockaddr_make_wildcards(int port, struct sockaddr_in *wild4_out, struct sockaddr_in6 *wild6_out); +/* Writes 0.0.0.0:port. */ +void grpc_sockaddr_make_wildcard4(int port, struct sockaddr_in *wild_out); + +/* Writes [::]:port. */ +void grpc_sockaddr_make_wildcard6(int port, struct sockaddr_in6 *wild_out); + /* Return the IP port number of a sockaddr */ int grpc_sockaddr_get_port(const struct sockaddr *addr); diff --git a/src/core/iomgr/tcp_client_windows.c b/src/core/iomgr/tcp_client_windows.c index 465fc75295d..37e6b125522 100644 --- a/src/core/iomgr/tcp_client_windows.c +++ b/src/core/iomgr/tcp_client_windows.c @@ -106,9 +106,7 @@ static void on_connect(void *acp, int success) { goto finish; } } else { - __debugbreak(); - abort(); - gpr_log(GPR_ERROR, "on_writable failed during connect"); + gpr_log(GPR_ERROR, "on_connect is shutting down"); goto finish; } @@ -167,9 +165,7 @@ void grpc_tcp_client_connect(void(*cb)(void *arg, grpc_endpoint *tcp), goto failure; } - memset(&local_address, 0, sizeof(local_address)); - memcpy(&local_address.sin6_addr, &in6addr_any, sizeof(in6addr_any)); - local_address.sin6_family = AF_INET6; + grpc_sockaddr_make_wildcard6(0, &local_address); status = bind(sock, (struct sockaddr *) &local_address, sizeof(local_address)); diff --git a/src/core/iomgr/tcp_server_windows.c b/src/core/iomgr/tcp_server_windows.c new file mode 100644 index 00000000000..21901958d1c --- /dev/null +++ b/src/core/iomgr/tcp_server_windows.c @@ -0,0 +1,372 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include + +#ifdef GPR_WINSOCK_SOCKET + +#define _GNU_SOURCE +#include "src/core/iomgr/sockaddr_utils.h" + +#include "src/core/iomgr/pollset_windows.h" +#include "src/core/iomgr/socket_windows.h" +#include "src/core/iomgr/tcp_server.h" +#include "src/core/iomgr/tcp_windows.h" +#include +#include +#include +#include +#include + +#define INIT_PORT_CAP 2 +#define MIN_SAFE_ACCEPT_QUEUE_SIZE 100 + +static gpr_once s_init_max_accept_queue_size; +static int s_max_accept_queue_size; + +/* one listening port */ +typedef struct server_port { + gpr_uint8 addresses[sizeof(struct sockaddr_in6) * 2 + 32]; + SOCKET new_socket; + grpc_winsocket *socket; + grpc_tcp_server *server; + LPFN_ACCEPTEX AcceptEx; +} server_port; + +/* the overall server */ +struct grpc_tcp_server { + grpc_tcp_server_cb cb; + void *cb_arg; + + gpr_mu mu; + gpr_cv cv; + + /* active port count: how many ports are actually still listening */ + int active_ports; + + /* all listening ports */ + server_port *ports; + size_t nports; + size_t port_capacity; +}; + +grpc_tcp_server *grpc_tcp_server_create(void) { + grpc_tcp_server *s = gpr_malloc(sizeof(grpc_tcp_server)); + gpr_mu_init(&s->mu); + gpr_cv_init(&s->cv); + s->active_ports = 0; + s->cb = NULL; + s->cb_arg = NULL; + s->ports = gpr_malloc(sizeof(server_port) * INIT_PORT_CAP); + s->nports = 0; + s->port_capacity = INIT_PORT_CAP; + return s; +} + +void grpc_tcp_server_destroy(grpc_tcp_server *s) { + size_t i; + gpr_mu_lock(&s->mu); + /* shutdown all fd's */ + for (i = 0; i < s->nports; i++) { + grpc_winsocket_shutdown(s->ports[i].socket); + } + /* wait while that happens */ + while (s->active_ports) { + gpr_cv_wait(&s->cv, &s->mu, gpr_inf_future); + } + gpr_mu_unlock(&s->mu); + + /* delete ALL the things */ + for (i = 0; i < s->nports; i++) { + server_port *sp = &s->ports[i]; + grpc_winsocket_orphan(sp->socket); + } + gpr_free(s->ports); + gpr_free(s); +} + +/* Prepare a recently-created socket for listening. */ +static int prepare_socket(SOCKET sock, + const struct sockaddr *addr, int addr_len) { + struct sockaddr_storage sockname_temp; + socklen_t sockname_len; + + if (sock == INVALID_SOCKET) goto error; + + if (!grpc_tcp_prepare_socket(sock)) { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "Unable to prepare socket: %s", utf8_message); + gpr_free(utf8_message); + goto error; + } + + if (bind(sock, addr, addr_len) == SOCKET_ERROR) { + char *addr_str; + char *utf8_message = gpr_format_message(WSAGetLastError()); + grpc_sockaddr_to_string(&addr_str, addr, 0); + gpr_log(GPR_ERROR, "bind addr=%s: %s", addr_str, utf8_message); + gpr_free(utf8_message); + gpr_free(addr_str); + goto error; + } + + if (listen(sock, SOMAXCONN) == SOCKET_ERROR) { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "listen: %s", utf8_message); + gpr_free(utf8_message); + goto error; + } + + sockname_len = sizeof(sockname_temp); + if (getsockname(sock, (struct sockaddr *) &sockname_temp, &sockname_len) + == SOCKET_ERROR) { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "getsockname: %s", utf8_message); + gpr_free(utf8_message); + goto error; + } + + return grpc_sockaddr_get_port((struct sockaddr *) &sockname_temp); + +error: + if (sock != INVALID_SOCKET) closesocket(sock); + return -1; +} + +static void on_accept(void *arg, int success); + +static void start_accept(server_port *port) { + SOCKET sock = INVALID_SOCKET; + char *message; + char *utf8_message; + BOOL success; + DWORD addrlen = sizeof(struct sockaddr_in6) + 16; + DWORD bytes_received = 0; + + sock = WSASocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP, NULL, 0, + WSA_FLAG_OVERLAPPED); + + if (sock == INVALID_SOCKET) { + message = "Unable to create socket: %s"; + goto failure; + } + + if (!grpc_tcp_prepare_socket(sock)) { + message = "Unable to prepare socket: %s"; + goto failure; + } + + success = port->AcceptEx(port->socket->socket, sock, port->addresses, 0, + addrlen, addrlen, &bytes_received, + &port->socket->read_info.overlapped); + + if (success) { + gpr_log(GPR_DEBUG, "accepted immediately - but we still go to sleep"); + } else { + int error = WSAGetLastError(); + if (error != ERROR_IO_PENDING) { + message = "AcceptEx failed: %s"; + goto failure; + } + } + + port->new_socket = sock; + grpc_handle_notify_on_read(port->socket, on_accept, port); + return; + +failure: + utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, message, utf8_message); + gpr_free(utf8_message); + if (sock != INVALID_SOCKET) closesocket(sock); +} + +/* event manager callback when reads are ready */ +static void on_accept(void *arg, int success) { + server_port *sp = arg; + SOCKET sock = sp->new_socket; + grpc_winsocket_callback_info *info = &sp->socket->read_info; + grpc_endpoint *ep = NULL; + + if (success) { + DWORD transfered_bytes = 0; + DWORD flags; + BOOL wsa_success = WSAGetOverlappedResult(sock, &info->overlapped, + &transfered_bytes, FALSE, + &flags); + if (!wsa_success) { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "on_accept error: %s", utf8_message); + gpr_free(utf8_message); + closesocket(sock); + } else { + gpr_log(GPR_DEBUG, "on_accept: accepted connection"); + ep = grpc_tcp_create(grpc_winsocket_create(sock)); + } + } else { + gpr_log(GPR_DEBUG, "on_accept: shutting down"); + closesocket(sock); + gpr_mu_lock(&sp->server->mu); + if (0 == --sp->server->active_ports) { + gpr_cv_broadcast(&sp->server->cv); + } + gpr_mu_unlock(&sp->server->mu); + } + + if (ep) sp->server->cb(sp->server->cb_arg, ep); + start_accept(sp); +} + +static int add_socket_to_server(grpc_tcp_server *s, SOCKET sock, + const struct sockaddr *addr, int addr_len) { + server_port *sp; + int port; + int status; + GUID guid = WSAID_ACCEPTEX; + DWORD ioctl_num_bytes; + LPFN_ACCEPTEX AcceptEx; + + if (sock == INVALID_SOCKET) return -1; + + status = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, + &guid, sizeof(guid), &AcceptEx, sizeof(AcceptEx), + &ioctl_num_bytes, NULL, NULL); + + if (status != 0) { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "on_connect error: %s", utf8_message); + gpr_free(utf8_message); + closesocket(sock); + return -1; + } + + port = prepare_socket(sock, addr, addr_len); + if (port >= 0) { + gpr_mu_lock(&s->mu); + GPR_ASSERT(!s->cb && "must add ports before starting server"); + /* append it to the list under a lock */ + if (s->nports == s->port_capacity) { + s->port_capacity *= 2; + s->ports = gpr_realloc(s->ports, sizeof(server_port) * s->port_capacity); + } + sp = &s->ports[s->nports++]; + sp->server = s; + sp->socket = grpc_winsocket_create(sock); + sp->AcceptEx = AcceptEx; + GPR_ASSERT(sp->socket); + gpr_mu_unlock(&s->mu); + } + + return port; +} + +int grpc_tcp_server_add_port(grpc_tcp_server *s, const void *addr, + int addr_len) { + int allocated_port = -1; + unsigned i; + SOCKET sock; + struct sockaddr_in6 addr6_v4mapped; + struct sockaddr_in6 wildcard; + struct sockaddr *allocated_addr = NULL; + struct sockaddr_storage sockname_temp; + socklen_t sockname_len; + int port; + + /* Check if this is a wildcard port, and if so, try to keep the port the same + as some previously created listener. */ + if (grpc_sockaddr_get_port(addr) == 0) { + for (i = 0; i < s->nports; i++) { + sockname_len = sizeof(sockname_temp); + if (0 == getsockname(s->ports[i].socket->socket, + (struct sockaddr *) &sockname_temp, + &sockname_len)) { + port = grpc_sockaddr_get_port((struct sockaddr *) &sockname_temp); + if (port > 0) { + allocated_addr = malloc(addr_len); + memcpy(allocated_addr, addr, addr_len); + grpc_sockaddr_set_port(allocated_addr, port); + addr = allocated_addr; + break; + } + } + } + } + + if (grpc_sockaddr_to_v4mapped(addr, &addr6_v4mapped)) { + addr = (const struct sockaddr *)&addr6_v4mapped; + addr_len = sizeof(addr6_v4mapped); + } + + /* Treat :: or 0.0.0.0 as a family-agnostic wildcard. */ + if (grpc_sockaddr_is_wildcard(addr, &port)) { + grpc_sockaddr_make_wildcard6(port, &wildcard); + + addr = (struct sockaddr *) &wildcard; + addr_len = sizeof(wildcard); + } + + sock = WSASocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP, NULL, 0, + WSA_FLAG_OVERLAPPED); + if (sock == INVALID_SOCKET) { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "unable to create socket: %s", utf8_message); + gpr_free(utf8_message); + } + + allocated_port = add_socket_to_server(s, sock, addr, addr_len); + gpr_free(allocated_addr); + + return allocated_port; +} + +SOCKET grpc_tcp_server_get_socket(grpc_tcp_server *s, unsigned index) { + return (index < s->nports) ? s->ports[index].socket->socket : INVALID_SOCKET; +} + +void grpc_tcp_server_start(grpc_tcp_server *s, grpc_pollset *pollset, + grpc_tcp_server_cb cb, void *cb_arg) { + size_t i; + GPR_ASSERT(cb); + gpr_mu_lock(&s->mu); + GPR_ASSERT(!s->cb); + GPR_ASSERT(s->active_ports == 0); + s->cb = cb; + s->cb_arg = cb_arg; + for (i = 0; i < s->nports; i++) { + start_accept(s->ports + i); + s->active_ports++; + } + gpr_mu_unlock(&s->mu); +} + +#endif /* GPR_WINSOCK_SOCKET */ diff --git a/src/core/iomgr/tcp_windows.c b/src/core/iomgr/tcp_windows.c index 734628dd8ee..bd0b2dd869b 100644 --- a/src/core/iomgr/tcp_windows.c +++ b/src/core/iomgr/tcp_windows.c @@ -61,7 +61,8 @@ static int set_non_block(SOCKET sock) { static int set_dualstack(SOCKET sock) { int status; unsigned long param = 0; - status = setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, ¶m, sizeof(param)); + status = setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + (const char *) ¶m, sizeof(param)); return status == 0; } @@ -120,7 +121,7 @@ static void on_read(void *tcpp, int success) { if (!success) { tcp_unref(tcp); - cb(opaque, NULL, 0, GRPC_ENDPOINT_CB_SHUTDOWN); + cb(opaque, GRPC_ENDPOINT_CB_SHUTDOWN); return; } diff --git a/vsprojects/vs2013/grpc.vcxproj b/vsprojects/vs2013/grpc.vcxproj index 10c8472b7e3..6d791789e47 100644 --- a/vsprojects/vs2013/grpc.vcxproj +++ b/vsprojects/vs2013/grpc.vcxproj @@ -289,6 +289,8 @@ + + diff --git a/vsprojects/vs2013/grpc.vcxproj.filters b/vsprojects/vs2013/grpc.vcxproj.filters index 53f3a1161ac..55f0108a4ba 100644 --- a/vsprojects/vs2013/grpc.vcxproj.filters +++ b/vsprojects/vs2013/grpc.vcxproj.filters @@ -163,6 +163,9 @@ src\core\iomgr + + src\core\iomgr + src\core\iomgr diff --git a/vsprojects/vs2013/grpc_unsecure.vcxproj b/vsprojects/vs2013/grpc_unsecure.vcxproj index 10c8472b7e3..6d791789e47 100644 --- a/vsprojects/vs2013/grpc_unsecure.vcxproj +++ b/vsprojects/vs2013/grpc_unsecure.vcxproj @@ -289,6 +289,8 @@ + + diff --git a/vsprojects/vs2013/grpc_unsecure.vcxproj.filters b/vsprojects/vs2013/grpc_unsecure.vcxproj.filters index 7b37c6d2472..23daa46e3d3 100644 --- a/vsprojects/vs2013/grpc_unsecure.vcxproj.filters +++ b/vsprojects/vs2013/grpc_unsecure.vcxproj.filters @@ -124,6 +124,9 @@ src\core\iomgr + + src\core\iomgr + src\core\iomgr From 970781bba9982e66c70e012fce1ba08fb8b88da8 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Sun, 8 Feb 2015 14:38:31 -0800 Subject: [PATCH 05/26] Add missing unlock --- src/core/surface/call.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/surface/call.c b/src/core/surface/call.c index 0af524cead7..6a21c439b2d 100644 --- a/src/core/surface/call.c +++ b/src/core/surface/call.c @@ -1116,7 +1116,10 @@ grpc_call_error grpc_call_server_accept_old(grpc_call *call, ls = get_legacy_state(call); err = bind_cq(call, cq); - if (err != GRPC_CALL_OK) return err; + if (err != GRPC_CALL_OK) { + unlock(call); + return err; + } ls->finished_tag = finished_tag; From 7d41321306eecdd7fa1dbbcf663cc94fd75db975 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Mon, 9 Feb 2015 08:00:02 -0800 Subject: [PATCH 06/26] Reduce contention on lock Change the fd watcher from being O(active_pollers) to O(1), reducing time spent under the fd->watcher_mu lock, and ultimately scaling us much better. --- src/core/iomgr/fd_posix.c | 50 +++++++------------ src/core/iomgr/fd_posix.h | 22 +++++--- .../pollset_multipoller_with_poll_posix.c | 26 +++++----- src/core/iomgr/pollset_posix.c | 9 ++-- 4 files changed, 52 insertions(+), 55 deletions(-) diff --git a/src/core/iomgr/fd_posix.c b/src/core/iomgr/fd_posix.c index b67c6cde709..737ee016aab 100644 --- a/src/core/iomgr/fd_posix.c +++ b/src/core/iomgr/fd_posix.c @@ -68,7 +68,6 @@ static grpc_fd *fd_freelist = NULL; static gpr_mu fd_freelist_mu; static void freelist_fd(grpc_fd *fd) { - gpr_free(fd->watchers); gpr_mu_lock(&fd_freelist_mu); fd->freelist_next = fd_freelist; fd_freelist = fd; @@ -93,9 +92,7 @@ static grpc_fd *alloc_fd(int fd) { gpr_atm_rel_store(&r->writest.state, NOT_READY); gpr_atm_rel_store(&r->shutdown, 0); r->fd = fd; - r->watchers = NULL; - r->watcher_count = 0; - r->watcher_capacity = 0; + r->watcher_root.next = r->watcher_root.prev = &r->watcher_root; r->freelist_next = NULL; return r; } @@ -118,9 +115,7 @@ static void unref_by(grpc_fd *fd, int n) { } } -void grpc_fd_global_init(void) { - gpr_mu_init(&fd_freelist_mu); -} +void grpc_fd_global_init(void) { gpr_mu_init(&fd_freelist_mu); } void grpc_fd_global_shutdown(void) { while (fd_freelist != NULL) { @@ -145,11 +140,11 @@ int grpc_fd_is_orphaned(grpc_fd *fd) { } static void wake_watchers(grpc_fd *fd) { - size_t i, n; + grpc_fd_watcher *watcher; gpr_mu_lock(&fd->watcher_mu); - n = fd->watcher_count; - for (i = 0; i < n; i++) { - grpc_pollset_force_kick(fd->watchers[i]); + for (watcher = fd->watcher_root.next; watcher != &fd->watcher_root; + watcher = watcher->next) { + grpc_pollset_force_kick(watcher->pollset); } gpr_mu_unlock(&fd->watcher_mu); } @@ -293,36 +288,27 @@ void grpc_fd_notify_on_write(grpc_fd *fd, grpc_iomgr_cb_func write_cb, } gpr_uint32 grpc_fd_begin_poll(grpc_fd *fd, grpc_pollset *pollset, - gpr_uint32 read_mask, gpr_uint32 write_mask) { + gpr_uint32 read_mask, gpr_uint32 write_mask, + grpc_fd_watcher *watcher) { /* keep track of pollers that have requested our events, in case they change */ gpr_mu_lock(&fd->watcher_mu); - if (fd->watcher_capacity == fd->watcher_count) { - fd->watcher_capacity = - GPR_MAX(fd->watcher_capacity + 8, fd->watcher_capacity * 3 / 2); - fd->watchers = gpr_realloc(fd->watchers, - fd->watcher_capacity * sizeof(grpc_pollset *)); - } - fd->watchers[fd->watcher_count++] = pollset; + watcher->next = &fd->watcher_root; + watcher->prev = watcher->next->prev; + watcher->next->prev = watcher->prev->next = watcher; + watcher->pollset = pollset; + watcher->fd = fd; gpr_mu_unlock(&fd->watcher_mu); return (gpr_atm_acq_load(&fd->readst.state) != READY ? read_mask : 0) | (gpr_atm_acq_load(&fd->writest.state) != READY ? write_mask : 0); } -void grpc_fd_end_poll(grpc_fd *fd, grpc_pollset *pollset) { - size_t r, w, n; - - gpr_mu_lock(&fd->watcher_mu); - n = fd->watcher_count; - for (r = 0, w = 0; r < n; r++) { - if (fd->watchers[r] == pollset) { - fd->watcher_count--; - continue; - } - fd->watchers[w++] = fd->watchers[r]; - } - gpr_mu_unlock(&fd->watcher_mu); +void grpc_fd_end_poll(grpc_fd_watcher *watcher) { + gpr_mu_lock(&watcher->fd->watcher_mu); + watcher->next->prev = watcher->prev; + watcher->prev->next = watcher->next; + gpr_mu_unlock(&watcher->fd->watcher_mu); } void grpc_fd_become_readable(grpc_fd *fd, int allow_synchronous_callback) { diff --git a/src/core/iomgr/fd_posix.h b/src/core/iomgr/fd_posix.h index f42ae195790..9a675087e59 100644 --- a/src/core/iomgr/fd_posix.h +++ b/src/core/iomgr/fd_posix.h @@ -47,7 +47,16 @@ typedef struct { gpr_atm state; } grpc_fd_state; -typedef struct grpc_fd { +typedef struct grpc_fd grpc_fd; + +typedef struct grpc_fd_watcher { + struct grpc_fd_watcher *next; + struct grpc_fd_watcher *prev; + grpc_pollset *pollset; + grpc_fd *fd; +} grpc_fd_watcher; + +struct grpc_fd { int fd; /* refst format: bit0: 1=active/0=orphaned @@ -60,9 +69,7 @@ typedef struct grpc_fd { gpr_atm shutdown; gpr_mu watcher_mu; - grpc_pollset **watchers; - size_t watcher_count; - size_t watcher_capacity; + grpc_fd_watcher watcher_root; grpc_fd_state readst; grpc_fd_state writest; @@ -70,7 +77,7 @@ typedef struct grpc_fd { grpc_iomgr_cb_func on_done; void *on_done_user_data; struct grpc_fd *freelist_next; -} grpc_fd; +}; /* Create a wrapped file descriptor. Requires fd is a non-blocking file descriptor. @@ -95,9 +102,10 @@ void grpc_fd_orphan(grpc_fd *fd, grpc_iomgr_cb_func on_done, void *user_data); Polling strategies that do not need to alter their behavior depending on the fd's current interest (such as epoll) do not need to call this function. */ gpr_uint32 grpc_fd_begin_poll(grpc_fd *fd, grpc_pollset *pollset, - gpr_uint32 read_mask, gpr_uint32 write_mask); + gpr_uint32 read_mask, gpr_uint32 write_mask, + grpc_fd_watcher *rec); /* Complete polling previously started with grpc_fd_begin_poll */ -void grpc_fd_end_poll(grpc_fd *fd, grpc_pollset *pollset); +void grpc_fd_end_poll(grpc_fd_watcher *rec); /* Return 1 if this fd is orphaned, 0 otherwise */ int grpc_fd_is_orphaned(grpc_fd *fd); diff --git a/src/core/iomgr/pollset_multipoller_with_poll_posix.c b/src/core/iomgr/pollset_multipoller_with_poll_posix.c index e88296979df..3244ae08db5 100644 --- a/src/core/iomgr/pollset_multipoller_with_poll_posix.c +++ b/src/core/iomgr/pollset_multipoller_with_poll_posix.c @@ -53,11 +53,11 @@ typedef struct { size_t fd_count; size_t fd_capacity; grpc_fd **fds; - /* fds being polled by the current poller: parallel arrays of pollfd and the - * grpc_fd* that the pollfd was constructed from */ + /* fds being polled by the current poller: parallel arrays of pollfd, and + a grpc_fd_watcher */ size_t pfd_count; size_t pfd_capacity; - grpc_fd **selfds; + grpc_fd_watcher *watchers; struct pollfd *pfds; /* fds that have been removed from the pollset explicitly */ size_t del_count; @@ -98,7 +98,7 @@ static void end_polling(grpc_pollset *pollset) { pollset_hdr *h; h = pollset->data.ptr; for (i = 1; i < h->pfd_count; i++) { - grpc_fd_end_poll(h->selfds[i], pollset); + grpc_fd_end_poll(&h->watchers[i]); } } @@ -125,9 +125,9 @@ static int multipoll_with_poll_pollset_maybe_work( if (h->pfd_capacity < h->fd_count + 1) { h->pfd_capacity = GPR_MAX(h->pfd_capacity * 3 / 2, h->fd_count + 1); gpr_free(h->pfds); - gpr_free(h->selfds); + gpr_free(h->watchers); h->pfds = gpr_malloc(sizeof(struct pollfd) * h->pfd_capacity); - h->selfds = gpr_malloc(sizeof(grpc_fd *) * h->pfd_capacity); + h->watchers = gpr_malloc(sizeof(grpc_fd_watcher) * h->pfd_capacity); } nf = 0; np = 1; @@ -147,7 +147,7 @@ static int multipoll_with_poll_pollset_maybe_work( grpc_fd_unref(h->fds[i]); } else { h->fds[nf++] = h->fds[i]; - h->selfds[np] = h->fds[i]; + h->watchers[np].fd = h->fds[i]; h->pfds[np].fd = h->fds[i]->fd; h->pfds[np].revents = 0; np++; @@ -167,8 +167,8 @@ static int multipoll_with_poll_pollset_maybe_work( gpr_mu_unlock(&pollset->mu); for (i = 1; i < np; i++) { - h->pfds[i].events = - grpc_fd_begin_poll(h->selfds[i], pollset, POLLIN, POLLOUT); + h->pfds[i].events = grpc_fd_begin_poll(h->watchers[i].fd, pollset, POLLIN, + POLLOUT, &h->watchers[i]); } r = poll(h->pfds, h->pfd_count, timeout); @@ -184,10 +184,10 @@ static int multipoll_with_poll_pollset_maybe_work( } for (i = 1; i < np; i++) { if (h->pfds[i].revents & POLLIN) { - grpc_fd_become_readable(h->selfds[i], allow_synchronous_callback); + grpc_fd_become_readable(h->watchers[i].fd, allow_synchronous_callback); } if (h->pfds[i].revents & POLLOUT) { - grpc_fd_become_writable(h->selfds[i], allow_synchronous_callback); + grpc_fd_become_writable(h->watchers[i].fd, allow_synchronous_callback); } } } @@ -211,7 +211,7 @@ static void multipoll_with_poll_pollset_destroy(grpc_pollset *pollset) { grpc_fd_unref(h->dels[i]); } gpr_free(h->pfds); - gpr_free(h->selfds); + gpr_free(h->watchers); gpr_free(h->fds); gpr_free(h->dels); gpr_free(h); @@ -234,7 +234,7 @@ void grpc_platform_become_multipoller(grpc_pollset *pollset, grpc_fd **fds, h->pfd_count = 0; h->pfd_capacity = 0; h->pfds = NULL; - h->selfds = NULL; + h->watchers = NULL; h->del_count = 0; h->del_capacity = 0; h->dels = NULL; diff --git a/src/core/iomgr/pollset_posix.c b/src/core/iomgr/pollset_posix.c index b1c2c64a18c..b0404b870b5 100644 --- a/src/core/iomgr/pollset_posix.c +++ b/src/core/iomgr/pollset_posix.c @@ -80,7 +80,9 @@ void grpc_pollset_kick(grpc_pollset *p) { } } -void grpc_pollset_force_kick(grpc_pollset *p) { grpc_pollset_kick_kick(&p->kick_state); } +void grpc_pollset_force_kick(grpc_pollset *p) { + grpc_pollset_kick_kick(&p->kick_state); +} /* global state management */ @@ -217,6 +219,7 @@ static int unary_poll_pollset_maybe_work(grpc_pollset *pollset, int allow_synchronous_callback) { struct pollfd pfd[2]; grpc_fd *fd; + grpc_fd_watcher fd_watcher; int timeout; int r; @@ -249,7 +252,7 @@ static int unary_poll_pollset_maybe_work(grpc_pollset *pollset, pollset->counter = 1; gpr_mu_unlock(&pollset->mu); - pfd[1].events = grpc_fd_begin_poll(fd, pollset, POLLIN, POLLOUT); + pfd[1].events = grpc_fd_begin_poll(fd, pollset, POLLIN, POLLOUT, &fd_watcher); r = poll(pfd, GPR_ARRAY_SIZE(pfd), timeout); if (r < 0) { @@ -271,7 +274,7 @@ static int unary_poll_pollset_maybe_work(grpc_pollset *pollset, } grpc_pollset_kick_post_poll(&pollset->kick_state); - grpc_fd_end_poll(fd, pollset); + grpc_fd_end_poll(&fd_watcher); gpr_mu_lock(&pollset->mu); pollset->counter = 0; From 45e67a37ae63b14d22eab7ee4bea9b912baa7010 Mon Sep 17 00:00:00 2001 From: Nicolas Noble Date: Mon, 9 Feb 2015 16:20:49 -0800 Subject: [PATCH 07/26] Addressing comments. --- Makefile | 5 + build.json | 3 +- src/core/iomgr/iocp_windows.c | 200 ++++++++++++++++++ .../iomgr/{iomgr_windows.h => iocp_windows.h} | 20 +- src/core/iomgr/iomgr_windows.c | 6 +- src/core/iomgr/pollset_windows.c | 174 +-------------- src/core/iomgr/pollset_windows.h | 11 - src/core/iomgr/socket_windows.c | 3 +- src/core/iomgr/tcp_client_windows.c | 2 +- src/core/iomgr/tcp_server_windows.c | 2 +- src/core/iomgr/tcp_windows.c | 15 +- vsprojects/vs2013/grpc.vcxproj | 4 +- vsprojects/vs2013/grpc.vcxproj.filters | 9 +- vsprojects/vs2013/grpc_unsecure.vcxproj | 4 +- .../vs2013/grpc_unsecure.vcxproj.filters | 9 +- 15 files changed, 257 insertions(+), 210 deletions(-) create mode 100644 src/core/iomgr/iocp_windows.c rename src/core/iomgr/{iomgr_windows.h => iocp_windows.h} (74%) diff --git a/Makefile b/Makefile index 7a4ca9303eb..8ca21b7e97a 100644 --- a/Makefile +++ b/Makefile @@ -1420,6 +1420,7 @@ LIBGRPC_SRC = \ src/core/iomgr/endpoint.c \ src/core/iomgr/endpoint_pair_posix.c \ src/core/iomgr/fd_posix.c \ + src/core/iomgr/iocp_windows.c \ src/core/iomgr/iomgr.c \ src/core/iomgr/iomgr_posix.c \ src/core/iomgr/iomgr_windows.c \ @@ -1551,6 +1552,7 @@ src/core/iomgr/alarm_heap.c: $(OPENSSL_DEP) src/core/iomgr/endpoint.c: $(OPENSSL_DEP) src/core/iomgr/endpoint_pair_posix.c: $(OPENSSL_DEP) src/core/iomgr/fd_posix.c: $(OPENSSL_DEP) +src/core/iomgr/iocp_windows.c: $(OPENSSL_DEP) src/core/iomgr/iomgr.c: $(OPENSSL_DEP) src/core/iomgr/iomgr_posix.c: $(OPENSSL_DEP) src/core/iomgr/iomgr_windows.c: $(OPENSSL_DEP) @@ -1704,6 +1706,7 @@ objs/$(CONFIG)/src/core/iomgr/alarm_heap.o: objs/$(CONFIG)/src/core/iomgr/endpoint.o: objs/$(CONFIG)/src/core/iomgr/endpoint_pair_posix.o: objs/$(CONFIG)/src/core/iomgr/fd_posix.o: +objs/$(CONFIG)/src/core/iomgr/iocp_windows.o: objs/$(CONFIG)/src/core/iomgr/iomgr.o: objs/$(CONFIG)/src/core/iomgr/iomgr_posix.o: objs/$(CONFIG)/src/core/iomgr/iomgr_windows.o: @@ -1876,6 +1879,7 @@ LIBGRPC_UNSECURE_SRC = \ src/core/iomgr/endpoint.c \ src/core/iomgr/endpoint_pair_posix.c \ src/core/iomgr/fd_posix.c \ + src/core/iomgr/iocp_windows.c \ src/core/iomgr/iomgr.c \ src/core/iomgr/iomgr_posix.c \ src/core/iomgr/iomgr_windows.c \ @@ -2012,6 +2016,7 @@ objs/$(CONFIG)/src/core/iomgr/alarm_heap.o: objs/$(CONFIG)/src/core/iomgr/endpoint.o: objs/$(CONFIG)/src/core/iomgr/endpoint_pair_posix.o: objs/$(CONFIG)/src/core/iomgr/fd_posix.o: +objs/$(CONFIG)/src/core/iomgr/iocp_windows.o: objs/$(CONFIG)/src/core/iomgr/iomgr.o: objs/$(CONFIG)/src/core/iomgr/iomgr_posix.o: objs/$(CONFIG)/src/core/iomgr/iomgr_windows.o: diff --git a/build.json b/build.json index 4a23f245d2d..d2ce51022b6 100644 --- a/build.json +++ b/build.json @@ -42,10 +42,10 @@ "src/core/iomgr/endpoint.h", "src/core/iomgr/endpoint_pair.h", "src/core/iomgr/fd_posix.h", + "src/core/iomgr/iocp_windows.h", "src/core/iomgr/iomgr.h", "src/core/iomgr/iomgr_internal.h", "src/core/iomgr/iomgr_posix.h", - "src/core/iomgr/iomgr_windows.h", "src/core/iomgr/pollset.h", "src/core/iomgr/pollset_kick.h", "src/core/iomgr/pollset_kick_posix.h", @@ -132,6 +132,7 @@ "src/core/iomgr/endpoint.c", "src/core/iomgr/endpoint_pair_posix.c", "src/core/iomgr/fd_posix.c", + "src/core/iomgr/iocp_windows.c", "src/core/iomgr/iomgr.c", "src/core/iomgr/iomgr_posix.c", "src/core/iomgr/iomgr_windows.c", diff --git a/src/core/iomgr/iocp_windows.c b/src/core/iomgr/iocp_windows.c new file mode 100644 index 00000000000..729b11b78dc --- /dev/null +++ b/src/core/iomgr/iocp_windows.c @@ -0,0 +1,200 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include + +#ifdef GPR_WINSOCK_SOCKET + +#include + +#include +#include +#include +#include + +#include "src/core/iomgr/alarm_internal.h" +#include "src/core/iomgr/iocp_windows.h" +#include "src/core/iomgr/iomgr_internal.h" +#include "src/core/iomgr/socket_windows.h" + +static ULONG g_iocp_kick_token; +static OVERLAPPED g_iocp_custom_overlap; + +static gpr_event g_shutdown_iocp; +static gpr_event g_iocp_done; + +static HANDLE g_iocp; + +static int do_iocp_work() { + BOOL success; + DWORD bytes = 0; + DWORD flags = 0; + ULONG_PTR completion_key; + LPOVERLAPPED overlapped; + gpr_timespec wait_time = gpr_inf_future; + grpc_winsocket *socket; + grpc_winsocket_callback_info *info; + void(*f)(void *, int) = NULL; + void *opaque = NULL; + success = GetQueuedCompletionStatus(g_iocp, &bytes, + &completion_key, &overlapped, + gpr_time_to_millis(wait_time)); + if (!success && !overlapped) { + /* The deadline got attained. */ + return 0; + } + GPR_ASSERT(completion_key && overlapped); + if (overlapped == &g_iocp_custom_overlap) { + if (completion_key == (ULONG_PTR) &g_iocp_kick_token) { + /* We were awoken from a kick. */ + gpr_log(GPR_DEBUG, "do_iocp_work - got a kick"); + return 1; + } + gpr_log(GPR_ERROR, "Unknown custom completion key."); + abort(); + } + + socket = (grpc_winsocket*) completion_key; + if (overlapped == &socket->write_info.overlapped) { + gpr_log(GPR_DEBUG, "do_iocp_work - got write packet"); + info = &socket->write_info; + } else if (overlapped == &socket->read_info.overlapped) { + gpr_log(GPR_DEBUG, "do_iocp_work - got read packet"); + info = &socket->read_info; + } else { + gpr_log(GPR_ERROR, "Unknown IOCP operation"); + abort(); + } + success = WSAGetOverlappedResult(socket->socket, &info->overlapped, &bytes, + FALSE, &flags); + gpr_log(GPR_DEBUG, "bytes: %u, flags: %u - op %s", bytes, flags, + success ? "succeeded" : "failed"); + info->bytes_transfered = bytes; + info->wsa_error = success ? 0 : WSAGetLastError(); + GPR_ASSERT(overlapped == &info->overlapped); + gpr_mu_lock(&socket->state_mu); + GPR_ASSERT(!info->has_pending_iocp); + if (info->cb) { + f = info->cb; + opaque = info->opaque; + info->cb = NULL; + } else { + info->has_pending_iocp = 1; + } + gpr_mu_unlock(&socket->state_mu); + if (f) f(opaque, 1); + + return 1; +} + +static void iocp_loop(void *p) { + while (!gpr_event_get(&g_shutdown_iocp)) { + grpc_maybe_call_delayed_callbacks(NULL, 1); + do_iocp_work(); + } + + gpr_event_set(&g_iocp_done, (void *)1); +} + +void grpc_iocp_init(void) { + gpr_thd_id id; + + g_iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, + (ULONG_PTR)NULL, 0); + GPR_ASSERT(g_iocp); + + gpr_event_init(&g_iocp_done); + gpr_event_init(&g_shutdown_iocp); + gpr_thd_new(&id, iocp_loop, NULL, NULL); +} + +void grpc_iocp_shutdown(void) { + BOOL success; + gpr_event_set(&g_shutdown_iocp, (void *)1); + success = PostQueuedCompletionStatus(g_iocp, 0, + (ULONG_PTR) &g_iocp_kick_token, + &g_iocp_custom_overlap); + GPR_ASSERT(success); + gpr_event_wait(&g_iocp_done, gpr_inf_future); + success = CloseHandle(g_iocp); + GPR_ASSERT(success); +} + +void grpc_iocp_add_socket(grpc_winsocket *socket) { + HANDLE ret; + if (socket->added_to_iocp) return; + ret = CreateIoCompletionPort((HANDLE)socket->socket, + g_iocp, (gpr_uintptr) socket, 0); + if (!ret) { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "Unable to add socket to iocp: %s", utf8_message); + gpr_free(utf8_message); + __debugbreak(); + abort(); + } + socket->added_to_iocp = 1; + GPR_ASSERT(ret == g_iocp); +} + +static void socket_notify_on_iocp(grpc_winsocket *socket, + void(*cb)(void *, int), void *opaque, + grpc_winsocket_callback_info *info) { + int run_now = 0; + GPR_ASSERT(!info->cb); + gpr_mu_lock(&socket->state_mu); + if (info->has_pending_iocp) { + run_now = 1; + info->has_pending_iocp = 0; + gpr_log(GPR_DEBUG, "socket_notify_on_iocp - runs now"); + } else { + info->cb = cb; + info->opaque = opaque; + gpr_log(GPR_DEBUG, "socket_notify_on_iocp - queued"); + } + gpr_mu_unlock(&socket->state_mu); + if (run_now) cb(opaque, 1); +} + +void grpc_socket_notify_on_write(grpc_winsocket *socket, + void(*cb)(void *, int), void *opaque) { + gpr_log(GPR_DEBUG, "grpc_socket_notify_on_write"); + socket_notify_on_iocp(socket, cb, opaque, &socket->write_info); +} + +void grpc_socket_notify_on_read(grpc_winsocket *socket, + void(*cb)(void *, int), void *opaque) { + gpr_log(GPR_DEBUG, "grpc_socket_notify_on_read"); + socket_notify_on_iocp(socket, cb, opaque, &socket->read_info); +} + +#endif /* GPR_WINSOCK_SOCKET */ diff --git a/src/core/iomgr/iomgr_windows.h b/src/core/iomgr/iocp_windows.h similarity index 74% rename from src/core/iomgr/iomgr_windows.h rename to src/core/iomgr/iocp_windows.h index 2d9449c1f62..bf5b90978ef 100644 --- a/src/core/iomgr/iomgr_windows.h +++ b/src/core/iomgr/iocp_windows.h @@ -31,12 +31,22 @@ * */ -#ifndef __GRPC_INTERNAL_IOMGR_IOMGR_WINDOWS_H_ -#define __GRPC_INTERNAL_IOMGR_IOMGR_WINDOWS_H_ +#ifndef __GRPC_INTERNAL_IOMGR_IOCP_WINDOWS_H_ +#define __GRPC_INTERNAL_IOMGR_IOCP_WINDOWS_H_ + +#include +#include #include "src/core/iomgr/socket_windows.h" -void grpc_pollset_global_init(void); -void grpc_pollset_global_shutdown(void); +void grpc_iocp_init(void); +void grpc_iocp_shutdown(void); +void grpc_iocp_add_socket(grpc_winsocket *); + +void grpc_socket_notify_on_write(grpc_winsocket *, void(*cb)(void *, int success), + void *opaque); + +void grpc_socket_notify_on_read(grpc_winsocket *, void(*cb)(void *, int success), + void *opaque); -#endif /* __GRPC_INTERNAL_IOMGR_IOMGR_WINDOWS_H_ */ +#endif /* __GRPC_INTERNAL_IOMGR_IOCP_WINDOWS_H_ */ diff --git a/src/core/iomgr/iomgr_windows.c b/src/core/iomgr/iomgr_windows.c index 5c8382e1c01..a3a255eaed2 100644 --- a/src/core/iomgr/iomgr_windows.c +++ b/src/core/iomgr/iomgr_windows.c @@ -40,8 +40,8 @@ #include #include "src/core/iomgr/socket_windows.h" +#include "src/core/iomgr/iocp_windows.h" #include "src/core/iomgr/iomgr.h" -#include "src/core/iomgr/iomgr_windows.h" static void winsock_init(void) { WSADATA wsaData; @@ -56,11 +56,11 @@ static void winsock_shutdown(void) { void grpc_iomgr_platform_init(void) { winsock_init(); - grpc_pollset_global_init(); + grpc_iocp_init(); } void grpc_iomgr_platform_shutdown(void) { - grpc_pollset_global_shutdown(); + grpc_iocp_shutdown(); winsock_shutdown(); } diff --git a/src/core/iomgr/pollset_windows.c b/src/core/iomgr/pollset_windows.c index 134e6f45e21..b81d23e57c2 100644 --- a/src/core/iomgr/pollset_windows.c +++ b/src/core/iomgr/pollset_windows.c @@ -35,106 +35,20 @@ #ifdef GPR_WINSOCK_SOCKET -#include - -#include -#include -#include #include #include "src/core/iomgr/alarm_internal.h" -#include "src/core/iomgr/socket_windows.h" #include "src/core/iomgr/iomgr_internal.h" #include "src/core/iomgr/pollset_windows.h" -static grpc_pollset g_global_pollset; -static ULONG g_pollset_kick_token; -static OVERLAPPED g_pollset_custom_overlap; - -static gpr_event g_shutdown_global_poller; -static gpr_event g_global_poller_done; - void grpc_pollset_init(grpc_pollset *pollset) { gpr_mu_init(&pollset->mu); gpr_cv_init(&pollset->cv); - pollset->iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, - (ULONG_PTR)NULL, 0); - GPR_ASSERT(pollset->iocp); } void grpc_pollset_destroy(grpc_pollset *pollset) { - BOOL status; gpr_mu_destroy(&pollset->mu); gpr_cv_destroy(&pollset->cv); - status = CloseHandle(pollset->iocp); - GPR_ASSERT(status); -} - -static int pollset_poll(grpc_pollset *pollset, - gpr_timespec deadline, gpr_timespec now) { - BOOL success; - DWORD bytes = 0; - DWORD flags = 0; - ULONG_PTR completion_key; - LPOVERLAPPED overlapped; - gpr_timespec wait_time = gpr_time_sub(deadline, now); - grpc_winsocket *socket; - grpc_winsocket_callback_info *info; - void(*f)(void *, int) = NULL; - void *opaque = NULL; - gpr_mu_unlock(&pollset->mu); - success = GetQueuedCompletionStatus(pollset->iocp, &bytes, - &completion_key, &overlapped, - gpr_time_to_millis(wait_time)); - gpr_mu_lock(&pollset->mu); - if (!success && !overlapped) { - /* The deadline got attained. */ - return 0; - } - GPR_ASSERT(completion_key && overlapped); - if (overlapped == &g_pollset_custom_overlap) { - if (completion_key == (ULONG_PTR) &g_pollset_kick_token) { - /* We were awoken from a kick. */ - gpr_log(GPR_DEBUG, "pollset_poll - got a kick"); - return 1; - } - gpr_log(GPR_ERROR, "Unknown custom completion key."); - abort(); - } - - GPR_ASSERT(pollset == &g_global_pollset); - - socket = (grpc_winsocket*) completion_key; - if (overlapped == &socket->write_info.overlapped) { - gpr_log(GPR_DEBUG, "pollset_poll - got write packet"); - info = &socket->write_info; - } else if (overlapped == &socket->read_info.overlapped) { - gpr_log(GPR_DEBUG, "pollset_poll - got read packet"); - info = &socket->read_info; - } else { - gpr_log(GPR_ERROR, "Unknown IOCP operation"); - abort(); - } - success = WSAGetOverlappedResult(socket->socket, &info->overlapped, &bytes, - FALSE, &flags); - gpr_log(GPR_DEBUG, "bytes: %u, flags: %u - op %s", bytes, flags, - success ? "succeeded" : "failed"); - info->bytes_transfered = bytes; - info->wsa_error = success ? 0 : WSAGetLastError(); - GPR_ASSERT(overlapped == &info->overlapped); - gpr_mu_lock(&socket->state_mu); - GPR_ASSERT(!info->has_pending_iocp); - if (info->cb) { - f = info->cb; - opaque = info->opaque; - info->cb = NULL; - } else { - info->has_pending_iocp = 1; - } - gpr_mu_unlock(&socket->state_mu); - if (f) f(opaque, 1); - - return 1; } int grpc_pollset_work(grpc_pollset *pollset, gpr_timespec deadline) { @@ -149,93 +63,9 @@ int grpc_pollset_work(grpc_pollset *pollset, gpr_timespec deadline) { if (grpc_alarm_check(NULL, now, &deadline)) { return 1; } - return pollset_poll(pollset, deadline, now); + return 0; } -void grpc_pollset_kick(grpc_pollset *pollset) { - BOOL status; - status = PostQueuedCompletionStatus(pollset->iocp, 0, - (ULONG_PTR) &g_pollset_kick_token, - &g_pollset_custom_overlap); - GPR_ASSERT(status); -} - -static void global_poller(void *p) { - while (!gpr_event_get(&g_shutdown_global_poller)) { - gpr_mu_lock(&g_global_pollset.mu); - grpc_pollset_work(&g_global_pollset, gpr_inf_future); - gpr_mu_unlock(&g_global_pollset.mu); - } - - gpr_event_set(&g_global_poller_done, (void *) 1); -} - -void grpc_pollset_global_init(void) { - gpr_thd_id id; - - grpc_pollset_init(&g_global_pollset); - gpr_event_init(&g_global_poller_done); - gpr_event_init(&g_shutdown_global_poller); - gpr_thd_new(&id, global_poller, NULL, NULL); -} - -void grpc_pollset_global_shutdown(void) { - gpr_event_set(&g_shutdown_global_poller, (void *) 1); - grpc_pollset_kick(&g_global_pollset); - gpr_event_wait(&g_global_poller_done, gpr_inf_future); - grpc_pollset_destroy(&g_global_pollset); -} - -void grpc_pollset_add_handle(grpc_pollset *pollset, grpc_winsocket *socket) { - HANDLE ret; - if (socket->added_to_iocp) return; - ret = CreateIoCompletionPort((HANDLE)socket->socket, - g_global_pollset.iocp, - (gpr_uintptr) socket, 0); - if (!ret) { - char *utf8_message = gpr_format_message(WSAGetLastError()); - gpr_log(GPR_ERROR, "Unable to add socket to iocp: %s", utf8_message); - gpr_free(utf8_message); - __debugbreak(); - abort(); - } - socket->added_to_iocp = 1; - GPR_ASSERT(ret == g_global_pollset.iocp); -} - -static void handle_notify_on_iocp(grpc_winsocket *socket, - void(*cb)(void *, int), void *opaque, - grpc_winsocket_callback_info *info) { - int run_now = 0; - GPR_ASSERT(!info->cb); - gpr_mu_lock(&socket->state_mu); - if (info->has_pending_iocp) { - run_now = 1; - info->has_pending_iocp = 0; - gpr_log(GPR_DEBUG, "handle_notify_on_iocp - runs now"); - } else { - info->cb = cb; - info->opaque = opaque; - gpr_log(GPR_DEBUG, "handle_notify_on_iocp - queued"); - } - gpr_mu_unlock(&socket->state_mu); - if (run_now) cb(opaque, 1); -} - -void grpc_handle_notify_on_write(grpc_winsocket *socket, - void(*cb)(void *, int), void *opaque) { - gpr_log(GPR_DEBUG, "grpc_handle_notify_on_write"); - handle_notify_on_iocp(socket, cb, opaque, &socket->write_info); -} - -void grpc_handle_notify_on_read(grpc_winsocket *socket, - void(*cb)(void *, int), void *opaque) { - gpr_log(GPR_DEBUG, "grpc_handle_notify_on_read"); - handle_notify_on_iocp(socket, cb, opaque, &socket->read_info); -} - -grpc_pollset *grpc_global_pollset(void) { - return &g_global_pollset; -} +void grpc_pollset_kick(grpc_pollset *p) { } #endif /* GPR_WINSOCK_SOCKET */ diff --git a/src/core/iomgr/pollset_windows.h b/src/core/iomgr/pollset_windows.h index 919af5d7b73..1a5e31f627b 100644 --- a/src/core/iomgr/pollset_windows.h +++ b/src/core/iomgr/pollset_windows.h @@ -48,20 +48,9 @@ struct grpc_fd; typedef struct grpc_pollset { gpr_mu mu; gpr_cv cv; - HANDLE iocp; } grpc_pollset; #define GRPC_POLLSET_MU(pollset) (&(pollset)->mu) #define GRPC_POLLSET_CV(pollset) (&(pollset)->cv) -void grpc_pollset_add_handle(grpc_pollset *, grpc_winsocket *); - -grpc_pollset *grpc_global_pollset(void); - -void grpc_handle_notify_on_write(grpc_winsocket *, void(*cb)(void *, int success), - void *opaque); - -void grpc_handle_notify_on_read(grpc_winsocket *, void(*cb)(void *, int success), - void *opaque); - #endif /* __GRPC_INTERNAL_IOMGR_POLLSET_WINDOWS_H_ */ diff --git a/src/core/iomgr/socket_windows.c b/src/core/iomgr/socket_windows.c index 805e96a0d15..3639798dbcd 100644 --- a/src/core/iomgr/socket_windows.c +++ b/src/core/iomgr/socket_windows.c @@ -37,6 +37,7 @@ #ifdef GPR_WINSOCK_SOCKET +#include "src/core/iomgr/iocp_windows.h" #include "src/core/iomgr/iomgr.h" #include "src/core/iomgr/iomgr_internal.h" #include "src/core/iomgr/socket_windows.h" @@ -50,7 +51,7 @@ grpc_winsocket *grpc_winsocket_create(SOCKET socket) { r->socket = socket; gpr_mu_init(&r->state_mu); grpc_iomgr_ref(); - grpc_pollset_add_handle(grpc_global_pollset(), r); + grpc_iocp_add_socket(r); return r; } diff --git a/src/core/iomgr/tcp_client_windows.c b/src/core/iomgr/tcp_client_windows.c index 37e6b125522..2ed5f39b390 100644 --- a/src/core/iomgr/tcp_client_windows.c +++ b/src/core/iomgr/tcp_client_windows.c @@ -197,7 +197,7 @@ void grpc_tcp_client_connect(void(*cb)(void *arg, grpc_endpoint *tcp), ac->refs = 2; grpc_alarm_init(&ac->alarm, deadline, on_alarm, ac, gpr_now()); - grpc_handle_notify_on_write(socket, on_connect, ac); + grpc_socket_notify_on_write(socket, on_connect, ac); return; failure: diff --git a/src/core/iomgr/tcp_server_windows.c b/src/core/iomgr/tcp_server_windows.c index 21901958d1c..97f8fe41ea9 100644 --- a/src/core/iomgr/tcp_server_windows.c +++ b/src/core/iomgr/tcp_server_windows.c @@ -201,7 +201,7 @@ static void start_accept(server_port *port) { } port->new_socket = sock; - grpc_handle_notify_on_read(port->socket, on_accept, port); + grpc_socket_notify_on_read(port->socket, on_accept, port); return; failure: diff --git a/src/core/iomgr/tcp_windows.c b/src/core/iomgr/tcp_windows.c index bd0b2dd869b..94d84f92b59 100644 --- a/src/core/iomgr/tcp_windows.c +++ b/src/core/iomgr/tcp_windows.c @@ -43,11 +43,12 @@ #include #include -#include "src/core/iomgr/tcp_client.h" -#include "src/core/iomgr/socket_windows.h" #include "src/core/iomgr/alarm.h" +#include "src/core/iomgr/iocp_windows.h" #include "src/core/iomgr/sockaddr.h" #include "src/core/iomgr/sockaddr_utils.h" +#include "src/core/iomgr/socket_windows.h" +#include "src/core/iomgr/tcp_client.h" static int set_non_block(SOCKET sock) { int status; @@ -121,7 +122,7 @@ static void on_read(void *tcpp, int success) { if (!success) { tcp_unref(tcp); - cb(opaque, GRPC_ENDPOINT_CB_SHUTDOWN); + cb(opaque, NULL, 0, GRPC_ENDPOINT_CB_SHUTDOWN); return; } @@ -194,7 +195,7 @@ static void win_notify_on_read(grpc_endpoint *ep, if (status == 0) { gpr_log(GPR_DEBUG, "got response immediately, but we're going to sleep"); - grpc_handle_notify_on_read(tcp->socket, on_read, tcp); + grpc_socket_notify_on_read(tcp->socket, on_read, tcp); return; } @@ -213,7 +214,7 @@ static void win_notify_on_read(grpc_endpoint *ep, } gpr_log(GPR_DEBUG, "waiting on the IO completion port now"); - grpc_handle_notify_on_read(tcp->socket, on_read, tcp); + grpc_socket_notify_on_read(tcp->socket, on_read, tcp); } static void on_write(void *tcpp, int success) { @@ -333,14 +334,14 @@ static grpc_endpoint_write_status win_write(grpc_endpoint *ep, gpr_log(GPR_DEBUG, "wrote data immediately - but we're going to sleep"); } - grpc_handle_notify_on_write(socket, on_write, tcp); + grpc_socket_notify_on_write(socket, on_write, tcp); return GRPC_ENDPOINT_WRITE_PENDING; } static void win_add_to_pollset(grpc_endpoint *ep, grpc_pollset *pollset) { grpc_tcp *tcp = (grpc_tcp *) ep; gpr_log(GPR_DEBUG, "win_add_to_pollset"); - grpc_pollset_add_handle(pollset, tcp->socket); + grpc_iocp_add_socket(tcp->socket); } static void win_shutdown(grpc_endpoint *ep) { diff --git a/vsprojects/vs2013/grpc.vcxproj b/vsprojects/vs2013/grpc.vcxproj index 6d791789e47..9db23091e4f 100644 --- a/vsprojects/vs2013/grpc.vcxproj +++ b/vsprojects/vs2013/grpc.vcxproj @@ -115,10 +115,10 @@ + - @@ -255,6 +255,8 @@ + + diff --git a/vsprojects/vs2013/grpc.vcxproj.filters b/vsprojects/vs2013/grpc.vcxproj.filters index 55f0108a4ba..5059e572bdb 100644 --- a/vsprojects/vs2013/grpc.vcxproj.filters +++ b/vsprojects/vs2013/grpc.vcxproj.filters @@ -112,6 +112,9 @@ src\core\iomgr + + src\core\iomgr + src\core\iomgr @@ -443,6 +446,9 @@ src\core\iomgr + + src\core\iomgr + src\core\iomgr @@ -452,9 +458,6 @@ src\core\iomgr - - src\core\iomgr - src\core\iomgr diff --git a/vsprojects/vs2013/grpc_unsecure.vcxproj b/vsprojects/vs2013/grpc_unsecure.vcxproj index 6d791789e47..9db23091e4f 100644 --- a/vsprojects/vs2013/grpc_unsecure.vcxproj +++ b/vsprojects/vs2013/grpc_unsecure.vcxproj @@ -115,10 +115,10 @@ + - @@ -255,6 +255,8 @@ + + diff --git a/vsprojects/vs2013/grpc_unsecure.vcxproj.filters b/vsprojects/vs2013/grpc_unsecure.vcxproj.filters index 23daa46e3d3..29afb528b3d 100644 --- a/vsprojects/vs2013/grpc_unsecure.vcxproj.filters +++ b/vsprojects/vs2013/grpc_unsecure.vcxproj.filters @@ -73,6 +73,9 @@ src\core\iomgr + + src\core\iomgr + src\core\iomgr @@ -368,6 +371,9 @@ src\core\iomgr + + src\core\iomgr + src\core\iomgr @@ -377,9 +383,6 @@ src\core\iomgr - - src\core\iomgr - src\core\iomgr From d72ba6a4ffe695a38600719cee96d39c1a307623 Mon Sep 17 00:00:00 2001 From: Nicolas Noble Date: Mon, 9 Feb 2015 16:30:35 -0800 Subject: [PATCH 08/26] Merge cleanup. --- src/core/iomgr/tcp_server_windows.c | 10 ++++++---- src/core/support/log_win32.c | 30 +++++++---------------------- 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/src/core/iomgr/tcp_server_windows.c b/src/core/iomgr/tcp_server_windows.c index 97f8fe41ea9..e6161eb1e86 100644 --- a/src/core/iomgr/tcp_server_windows.c +++ b/src/core/iomgr/tcp_server_windows.c @@ -38,16 +38,18 @@ #define _GNU_SOURCE #include "src/core/iomgr/sockaddr_utils.h" -#include "src/core/iomgr/pollset_windows.h" -#include "src/core/iomgr/socket_windows.h" -#include "src/core/iomgr/tcp_server.h" -#include "src/core/iomgr/tcp_windows.h" #include #include #include #include #include +#include "src/core/iomgr/iocp_windows.h" +#include "src/core/iomgr/pollset_windows.h" +#include "src/core/iomgr/socket_windows.h" +#include "src/core/iomgr/tcp_server.h" +#include "src/core/iomgr/tcp_windows.h" + #define INIT_PORT_CAP 2 #define MIN_SAFE_ACCEPT_QUEUE_SIZE 100 diff --git a/src/core/support/log_win32.c b/src/core/support/log_win32.c index 4c0a866048f..840f24f68aa 100644 --- a/src/core/support/log_win32.c +++ b/src/core/support/log_win32.c @@ -35,12 +35,15 @@ #ifdef GPR_WIN32 +#include +#include + +#include #include #include -#include #include -#include -#include + +#include "src/core/support/string_win32.h" void gpr_log(const char *file, int line, gpr_log_severity severity, const char *format, ...) { @@ -93,25 +96,6 @@ void gpr_default_log(gpr_log_func_args *args) { args->file, args->line, args->message); } -/* Arguable, this could become a public function. But hardly - * anything beside the Windows implementation should use - * it, so, never mind...*/ -#if defined UNICODE || defined _UNICODE -static char *tchar_to_char(LPWSTR input) { - char *ret; - int needed = WideCharToMultiByte(CP_UTF8, 0, input, -1, NULL, 0, NULL, NULL); - if (needed == 0) return NULL; - ret = gpr_malloc(needed + 1); - WideCharToMultiByte(CP_UTF8, 0, input, -1, ret, needed, NULL, NULL); - ret[needed] = 0; - return ret; -} -#else -static char *tchar_to_char(LPSTR input) { - return gpr_strdup(input); -} -#endif - char *gpr_format_message(DWORD messageid) { LPTSTR tmessage; char *message; @@ -121,7 +105,7 @@ char *gpr_format_message(DWORD messageid) { NULL, messageid, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)(&tmessage), 0, NULL); - message = tchar_to_char(tmessage); + message = gpr_tchar_to_char(tmessage); LocalFree(tmessage); return message; } From 3d8e34d4096efeee1bf4b43dad9214a1176c250a Mon Sep 17 00:00:00 2001 From: "Nicolas \"Pixel\" Noble" Date: Tue, 10 Feb 2015 11:05:48 +0100 Subject: [PATCH 09/26] Rewording comment :-) --- src/core/iomgr/socket_windows.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core/iomgr/socket_windows.h b/src/core/iomgr/socket_windows.h index 19822ac085c..990b520c6da 100644 --- a/src/core/iomgr/socket_windows.h +++ b/src/core/iomgr/socket_windows.h @@ -40,11 +40,11 @@ #include typedef struct grpc_winsocket_callback_info { - /* I hate Microsoft so much. This is supposed to be a WSAOVERLAPPED, - * but in order to get that definition, we need to include ws2tcpip.h, - * which needs to be included from the top, otherwise it'll clash with - * a previous inclusion of windows.h that in turns includes winsock.h. - * If anyone knows a way to do it properly, feel free to send a patch. + /* This is supposed to be a WSAOVERLAPPED, but in order to get that + * definition, we need to include ws2tcpip.h, which needs to be included + * from the top, otherwise it'll clash with a previous inclusion of + * windows.h that in turns includes winsock.h. If anyone knows a way + * to do it properly, feel free to send a patch. */ OVERLAPPED overlapped; void(*cb)(void *opaque, int success); From 860f6d0820636bacc84414d921c9f80daff9211a Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Tue, 10 Feb 2015 08:29:51 -0800 Subject: [PATCH 10/26] started using nuget packages for dependencies --- src/csharp/.gitignore | 2 ++ src/csharp/GrpcApi/.gitignore | 1 + src/csharp/GrpcApi/GrpcApi.csproj | 28 ++++++++++-------- src/csharp/GrpcApi/packages.config | 11 +++++++ src/csharp/GrpcApiTests/.gitignore | 1 + src/csharp/GrpcApiTests/GrpcApiTests.csproj | 19 ++++++++---- src/csharp/GrpcApiTests/packages.config | 5 ++++ src/csharp/GrpcCore/.gitignore | 1 + src/csharp/GrpcCoreTests/.gitignore | 1 + src/csharp/GrpcCoreTests/GrpcCoreTests.csproj | 14 ++++++--- src/csharp/GrpcCoreTests/packages.config | 4 +++ src/csharp/InteropClient/.gitignore | 1 + src/csharp/InteropClient/InteropClient.csproj | 16 ++++++---- src/csharp/InteropClient/packages.config | 5 ++++ src/csharp/MathClient/.gitignore | 1 + src/csharp/lib/Google.ProtocolBuffers.dll | Bin 380416 -> 0 bytes 16 files changed, 81 insertions(+), 29 deletions(-) create mode 100644 src/csharp/GrpcApi/packages.config create mode 100644 src/csharp/GrpcApiTests/packages.config create mode 100644 src/csharp/GrpcCoreTests/packages.config create mode 100644 src/csharp/InteropClient/packages.config delete mode 100755 src/csharp/lib/Google.ProtocolBuffers.dll diff --git a/src/csharp/.gitignore b/src/csharp/.gitignore index dbf38f34b73..d35ff63f6ef 100644 --- a/src/csharp/.gitignore +++ b/src/csharp/.gitignore @@ -1,2 +1,4 @@ *.userprefs test-results +packages +Grpc.v12.suo diff --git a/src/csharp/GrpcApi/.gitignore b/src/csharp/GrpcApi/.gitignore index 2cc8cca52d0..4795a95b94e 100644 --- a/src/csharp/GrpcApi/.gitignore +++ b/src/csharp/GrpcApi/.gitignore @@ -1,2 +1,3 @@ test-results bin +obj diff --git a/src/csharp/GrpcApi/GrpcApi.csproj b/src/csharp/GrpcApi/GrpcApi.csproj index f0f11de2167..5a4ae67bd55 100644 --- a/src/csharp/GrpcApi/GrpcApi.csproj +++ b/src/csharp/GrpcApi/GrpcApi.csproj @@ -1,4 +1,4 @@ - + Debug @@ -30,19 +30,23 @@ false + + False + ..\packages\Google.ProtocolBuffers.2.4.1.521\lib\net40\Google.ProtocolBuffers.dll + - - False + + ..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll - - - False + + ..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll - - False + + + ..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll - - ..\lib\Google.ProtocolBuffers.dll + + ..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll @@ -63,12 +67,10 @@ + - - - \ No newline at end of file diff --git a/src/csharp/GrpcApi/packages.config b/src/csharp/GrpcApi/packages.config new file mode 100644 index 00000000000..a6a949b3b3a --- /dev/null +++ b/src/csharp/GrpcApi/packages.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/csharp/GrpcApiTests/.gitignore b/src/csharp/GrpcApiTests/.gitignore index 2cc8cca52d0..4795a95b94e 100644 --- a/src/csharp/GrpcApiTests/.gitignore +++ b/src/csharp/GrpcApiTests/.gitignore @@ -1,2 +1,3 @@ test-results bin +obj diff --git a/src/csharp/GrpcApiTests/GrpcApiTests.csproj b/src/csharp/GrpcApiTests/GrpcApiTests.csproj index d0aac2b7533..cb955cff41b 100644 --- a/src/csharp/GrpcApiTests/GrpcApiTests.csproj +++ b/src/csharp/GrpcApiTests/GrpcApiTests.csproj @@ -1,4 +1,4 @@ - + Debug @@ -30,13 +30,14 @@ false - - - False + + False + ..\packages\Google.ProtocolBuffers.2.4.1.521\lib\net40\Google.ProtocolBuffers.dll - - ..\lib\Google.ProtocolBuffers.dll + + ..\packages\NUnit.2.6.4\lib\nunit.framework.dll + @@ -53,4 +54,10 @@ GrpcCore + + + + + + \ No newline at end of file diff --git a/src/csharp/GrpcApiTests/packages.config b/src/csharp/GrpcApiTests/packages.config new file mode 100644 index 00000000000..51c17bcd5e7 --- /dev/null +++ b/src/csharp/GrpcApiTests/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/csharp/GrpcCore/.gitignore b/src/csharp/GrpcCore/.gitignore index ba077a4031a..8d4a6c08a83 100644 --- a/src/csharp/GrpcCore/.gitignore +++ b/src/csharp/GrpcCore/.gitignore @@ -1 +1,2 @@ bin +obj \ No newline at end of file diff --git a/src/csharp/GrpcCoreTests/.gitignore b/src/csharp/GrpcCoreTests/.gitignore index 2cc8cca52d0..775a9440a2c 100644 --- a/src/csharp/GrpcCoreTests/.gitignore +++ b/src/csharp/GrpcCoreTests/.gitignore @@ -1,2 +1,3 @@ test-results bin +obj \ No newline at end of file diff --git a/src/csharp/GrpcCoreTests/GrpcCoreTests.csproj b/src/csharp/GrpcCoreTests/GrpcCoreTests.csproj index 111f0883db0..ca52cd8f40d 100644 --- a/src/csharp/GrpcCoreTests/GrpcCoreTests.csproj +++ b/src/csharp/GrpcCoreTests/GrpcCoreTests.csproj @@ -1,4 +1,4 @@ - + Debug @@ -30,10 +30,10 @@ false - - - False + + ..\packages\NUnit.2.6.4\lib\nunit.framework.dll + @@ -49,4 +49,10 @@ GrpcCore + + + + + + \ No newline at end of file diff --git a/src/csharp/GrpcCoreTests/packages.config b/src/csharp/GrpcCoreTests/packages.config new file mode 100644 index 00000000000..c714ef3a23e --- /dev/null +++ b/src/csharp/GrpcCoreTests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/csharp/InteropClient/.gitignore b/src/csharp/InteropClient/.gitignore index ba077a4031a..8d4a6c08a83 100644 --- a/src/csharp/InteropClient/.gitignore +++ b/src/csharp/InteropClient/.gitignore @@ -1 +1,2 @@ bin +obj \ No newline at end of file diff --git a/src/csharp/InteropClient/InteropClient.csproj b/src/csharp/InteropClient/InteropClient.csproj index b8e099d7852..a450f3a2feb 100644 --- a/src/csharp/InteropClient/InteropClient.csproj +++ b/src/csharp/InteropClient/InteropClient.csproj @@ -1,4 +1,4 @@ - + Debug @@ -33,13 +33,14 @@ x86 - - - False + + False + ..\packages\Google.ProtocolBuffers.2.4.1.521\lib\net40\Google.ProtocolBuffers.dll - - ..\lib\Google.ProtocolBuffers.dll + + ..\packages\NUnit.2.6.4\lib\nunit.framework.dll + @@ -56,4 +57,7 @@ GrpcApi + + + \ No newline at end of file diff --git a/src/csharp/InteropClient/packages.config b/src/csharp/InteropClient/packages.config new file mode 100644 index 00000000000..51c17bcd5e7 --- /dev/null +++ b/src/csharp/InteropClient/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/csharp/MathClient/.gitignore b/src/csharp/MathClient/.gitignore index ba077a4031a..1746e3269ed 100644 --- a/src/csharp/MathClient/.gitignore +++ b/src/csharp/MathClient/.gitignore @@ -1 +1,2 @@ bin +obj diff --git a/src/csharp/lib/Google.ProtocolBuffers.dll b/src/csharp/lib/Google.ProtocolBuffers.dll deleted file mode 100755 index ce2f466b243da0e875764856cd99c61ca9d98628..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 380416 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�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 Date: Tue, 10 Feb 2015 09:50:57 -0800 Subject: [PATCH 11/26] update readme file --- src/csharp/README.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/csharp/README.md b/src/csharp/README.md index 0df6925b395..a16f1e719e1 100755 --- a/src/csharp/README.md +++ b/src/csharp/README.md @@ -15,8 +15,15 @@ EXPERIMENTAL ONLY completely rewritten. -INSTALLATION AND USAGE ----------------------- +INSTALLATION AND USAGE: WINDOWS +------------------------------- + +- Open Grpc.sln using Visual Studio 2013. NuGet dependencies will be restored + upon build. + + +INSTALLATION AND USAGE: LINUX & MONO +------------------------------------ - Compile and install the gRPC C Core library ``` @@ -31,6 +38,18 @@ sudo apt-get install monodevelop monodevelop-nunit sudo apt-get install nunit nunit-console ``` +- NuGet is used to manage project's dependencies. Prior opening Grpc.sln, + download dependencies using NuGet restore command: + +``` +# Import needed certicates into Mono certificate store: +mozroots --import --sync + +# Download NuGet.exe http://nuget.codeplex.com/releases/ +# Restore the nuget packages with Grpc C# dependencies +mono ~/Downloads/NuGet.exe restore Grpc.sln +``` + - Use MonoDevelop to open the solution Grpc.sln (you can also run unit tests from there). From 3dd9df93590829f4394cf6174cfdf16d132c7521 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Tue, 10 Feb 2015 10:16:41 -0800 Subject: [PATCH 12/26] Updated INSTALL to clarify phrasing around OpenSSL. --- INSTALL | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/INSTALL b/INSTALL index 7a3d02f186e..9dfc2282559 100644 --- a/INSTALL +++ b/INSTALL @@ -100,16 +100,16 @@ Then, you can build and install protobuf 3.0.0: A word on OpenSSL ----------------- -Secure HTTP2 requires to have the TLS extension ALPN (see rfc 7301 and +Secure HTTP2 requires the TLS extension ALPN (see rfc 7301 and http://http2.github.io/http2-spec/ section 3.3). Our HTTP2 implementation relies on OpenSSL's implementation. OpenSSL 1.0.2 is the first released version of OpenSSL that has ALPN support, and this explains our dependency on it. Note that the Makefile supports compiling only the unsecure elements of grpc, and if you do not have OpenSSL and do not want it, you can still proceed -with installing only the elements you require. However, it is recommended -to encrypt your network traffic, therefore we urge you to not use the unsecure -version of grpc if possible. +with installing only the elements you require. However, we strongly recoomend +the use of encryption for all network traffic, and discourage the use of grpc +without TLS. Compiling From 5438d4b214ed37e681a3acb377c7b019565ec207 Mon Sep 17 00:00:00 2001 From: Vijay Pai Date: Tue, 10 Feb 2015 10:36:20 -0800 Subject: [PATCH 13/26] Change link options for qps client and qps server --- build.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.json b/build.json index 68110e47020..c9a83f56c17 100644 --- a/build.json +++ b/build.json @@ -1616,6 +1616,7 @@ { "name": "qps_client", "build": "test", + "run": false, "language": "c++", "src": [ "test/cpp/qps/qpstest.proto", @@ -1633,6 +1634,7 @@ { "name": "qps_server", "build": "test", + "run": false, "language": "c++", "src": [ "test/cpp/qps/qpstest.proto", From c3b02d9a67da11bdeb4eeb5a7986882740dea6d1 Mon Sep 17 00:00:00 2001 From: Vijay Pai Date: Tue, 10 Feb 2015 10:39:03 -0800 Subject: [PATCH 14/26] Allow varying number of server threads via command line flag --- test/cpp/qps/server.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/cpp/qps/server.cc b/test/cpp/qps/server.cc index c35d9ebdd87..11edfe58245 100644 --- a/test/cpp/qps/server.cc +++ b/test/cpp/qps/server.cc @@ -44,6 +44,7 @@ #include #include #include +#include "src/cpp/server/thread_pool.h" #include "test/core/util/grpc_profiler.h" #include "test/cpp/qps/qpstest.pb.h" @@ -52,10 +53,12 @@ DEFINE_bool(enable_ssl, false, "Whether to use ssl/tls."); DEFINE_int32(port, 0, "Server port."); +DEFINE_int32(server_threads, 4, "Number of server threads."); using grpc::Server; using grpc::ServerBuilder; using grpc::ServerContext; +using grpc::ThreadPool; using grpc::testing::Payload; using grpc::testing::PayloadType; using grpc::testing::ServerStats; @@ -126,6 +129,10 @@ static void RunServer() { ServerBuilder builder; builder.AddPort(server_address); builder.RegisterService(service.service()); + + ThreadPool *pool = new ThreadPool(FLAGS_server_threads); + builder.SetThreadPool(pool); + std::unique_ptr server(builder.BuildAndStart()); gpr_log(GPR_INFO, "Server listening on %s\n", server_address); @@ -137,6 +144,7 @@ static void RunServer() { grpc_profiler_stop(); + delete pool; gpr_free(server_address); } From 4ca479c3a1ccb11e025ca68be91f8bd9eabc5e85 Mon Sep 17 00:00:00 2001 From: Vijay Pai Date: Tue, 10 Feb 2015 10:55:53 -0800 Subject: [PATCH 15/26] Change pointer format --- test/cpp/qps/server.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/cpp/qps/server.cc b/test/cpp/qps/server.cc index 11edfe58245..3a432b6fbbb 100644 --- a/test/cpp/qps/server.cc +++ b/test/cpp/qps/server.cc @@ -130,8 +130,8 @@ static void RunServer() { builder.AddPort(server_address); builder.RegisterService(service.service()); - ThreadPool *pool = new ThreadPool(FLAGS_server_threads); - builder.SetThreadPool(pool); + std::unique_ptr pool(new ThreadPool(FLAGS_server_threads)); + builder.SetThreadPool(pool.get()); std::unique_ptr server(builder.BuildAndStart()); gpr_log(GPR_INFO, "Server listening on %s\n", server_address); @@ -144,7 +144,6 @@ static void RunServer() { grpc_profiler_stop(); - delete pool; gpr_free(server_address); } From 2d63da7e534115ebb898d48a4eef4934f134dece Mon Sep 17 00:00:00 2001 From: Vijay Pai Date: Tue, 10 Feb 2015 10:57:37 -0800 Subject: [PATCH 16/26] Updated as a result of changed options in build.json --- Makefile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Makefile b/Makefile index 1547b496167..c411c07e425 100644 --- a/Makefile +++ b/Makefile @@ -1433,10 +1433,6 @@ test_cxx: buildtests_cxx $(Q) ./bins/$(CONFIG)/credentials_test || ( echo test credentials_test failed ; exit 1 ) $(E) "[RUN] Testing end2end_test" $(Q) ./bins/$(CONFIG)/end2end_test || ( echo test end2end_test failed ; exit 1 ) - $(E) "[RUN] Testing qps_client" - $(Q) ./bins/$(CONFIG)/qps_client || ( echo test qps_client failed ; exit 1 ) - $(E) "[RUN] Testing qps_server" - $(Q) ./bins/$(CONFIG)/qps_server || ( echo test qps_server failed ; exit 1 ) $(E) "[RUN] Testing status_test" $(Q) ./bins/$(CONFIG)/status_test || ( echo test status_test failed ; exit 1 ) $(E) "[RUN] Testing sync_client_async_server_test" From 6749e73561f672b5d08c7afdc9c9151d4bc8e4d6 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Tue, 10 Feb 2015 10:59:52 -0800 Subject: [PATCH 17/26] Fix spelling: recommend --- INSTALL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL b/INSTALL index 9dfc2282559..b7c1d46884e 100644 --- a/INSTALL +++ b/INSTALL @@ -107,7 +107,7 @@ of OpenSSL that has ALPN support, and this explains our dependency on it. Note that the Makefile supports compiling only the unsecure elements of grpc, and if you do not have OpenSSL and do not want it, you can still proceed -with installing only the elements you require. However, we strongly recoomend +with installing only the elements you require. However, we strongly recommend the use of encryption for all network traffic, and discourage the use of grpc without TLS. From 75be91a149e98940bb0de6f793863025ae889abf Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Mon, 9 Feb 2015 23:24:24 -0800 Subject: [PATCH 18/26] Allow Python to be built without sudo --- tools/run_tests/build_python.sh | 8 ++++++-- tools/run_tests/run_python.sh | 28 +++++++++++++++------------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/tools/run_tests/build_python.sh b/tools/run_tests/build_python.sh index 4abb412c95a..b45b9d6106e 100755 --- a/tools/run_tests/build_python.sh +++ b/tools/run_tests/build_python.sh @@ -5,7 +5,11 @@ set -ex # change to grpc repo root cd $(dirname $0)/../.. +make -j6 + root=`pwd` virtualenv python2.7_virtual_environment -python2.7_virtual_environment/bin/pip install enum34==1.0.4 futures==2.2.0 protobuf==2.6.1 -python2.7_virtual_environment/bin/pip install src/python +ln -sf $root/include/grpc python2.7_virtual_environment/include/grpc +source python2.7_virtual_environment/bin/activate +pip install enum34==1.0.4 futures==2.2.0 protobuf==2.6.1 +CFLAGS=-I$root/include LDFLAGS=-L$root/libs/opt pip install src/python diff --git a/tools/run_tests/run_python.sh b/tools/run_tests/run_python.sh index 6e9405afb6f..7d3ee73a0e4 100755 --- a/tools/run_tests/run_python.sh +++ b/tools/run_tests/run_python.sh @@ -6,19 +6,21 @@ set -ex cd $(dirname $0)/../.. root=`pwd` +export LD_LIBRARY_PATH=$root/libs/opt +source python2.7_virtual_environment/bin/activate # TODO(issue 215): Properly itemize these in run_tests.py so that they can be parallelized. -python2.7_virtual_environment/bin/python2.7 -B -m _adapter._blocking_invocation_inline_service_test -python2.7_virtual_environment/bin/python2.7 -B -m _adapter._c_test -python2.7_virtual_environment/bin/python2.7 -B -m _adapter._event_invocation_synchronous_event_service_test -python2.7_virtual_environment/bin/python2.7 -B -m _adapter._future_invocation_asynchronous_event_service_test -python2.7_virtual_environment/bin/python2.7 -B -m _adapter._links_test -python2.7_virtual_environment/bin/python2.7 -B -m _adapter._lonely_rear_link_test -python2.7_virtual_environment/bin/python2.7 -B -m _adapter._low_test -python2.7_virtual_environment/bin/python2.7 -B -m _framework.base.packets.implementations_test -python2.7_virtual_environment/bin/python2.7 -B -m _framework.face.blocking_invocation_inline_service_test -python2.7_virtual_environment/bin/python2.7 -B -m _framework.face.event_invocation_synchronous_event_service_test -python2.7_virtual_environment/bin/python2.7 -B -m _framework.face.future_invocation_asynchronous_event_service_test -python2.7_virtual_environment/bin/python2.7 -B -m _framework.foundation._later_test -python2.7_virtual_environment/bin/python2.7 -B -m _framework.foundation._logging_pool_test +python2.7 -B -m _adapter._blocking_invocation_inline_service_test +python2.7 -B -m _adapter._c_test +python2.7 -B -m _adapter._event_invocation_synchronous_event_service_test +python2.7 -B -m _adapter._future_invocation_asynchronous_event_service_test +python2.7 -B -m _adapter._links_test +python2.7 -B -m _adapter._lonely_rear_link_test +python2.7 -B -m _adapter._low_test +python2.7 -B -m _framework.base.packets.implementations_test +python2.7 -B -m _framework.face.blocking_invocation_inline_service_test +python2.7 -B -m _framework.face.event_invocation_synchronous_event_service_test +python2.7 -B -m _framework.face.future_invocation_asynchronous_event_service_test +python2.7 -B -m _framework.foundation._later_test +python2.7 -B -m _framework.foundation._logging_pool_test # TODO(nathaniel): Get tests working under 3.4 (requires 3.X-friendly protobuf) # python3.4 -B -m unittest discover -s src/python -p '*.py' From 49c877e57935b01d4ca51e3d7ebcd1b48b410898 Mon Sep 17 00:00:00 2001 From: Nathaniel Manista Date: Tue, 10 Feb 2015 17:39:53 +0000 Subject: [PATCH 19/26] Add a wrapping of grpc_server_credentials. --- src/python/setup.py | 1 + src/python/src/_adapter/_c.c | 4 + src/python/src/_adapter/_c_test.py | 26 +++ src/python/src/_adapter/_server_credentials.c | 157 ++++++++++++++++++ src/python/src/_adapter/_server_credentials.h | 48 ++++++ 5 files changed, 236 insertions(+) create mode 100644 src/python/src/_adapter/_server_credentials.c create mode 100644 src/python/src/_adapter/_server_credentials.h diff --git a/src/python/setup.py b/src/python/setup.py index 58dc3b17dfa..5e566bad4fd 100644 --- a/src/python/setup.py +++ b/src/python/setup.py @@ -38,6 +38,7 @@ _EXTENSION_SOURCES = ( 'src/_adapter/_completion_queue.c', 'src/_adapter/_error.c', 'src/_adapter/_server.c', + 'src/_adapter/_server_credentials.c', ) _EXTENSION_INCLUDE_DIRECTORIES = ( diff --git a/src/python/src/_adapter/_c.c b/src/python/src/_adapter/_c.c index d1f7fbb0d54..6fb7fa29faf 100644 --- a/src/python/src/_adapter/_c.c +++ b/src/python/src/_adapter/_c.c @@ -38,6 +38,7 @@ #include "_adapter/_channel.h" #include "_adapter/_call.h" #include "_adapter/_server.h" +#include "_adapter/_server_credentials.h" static PyObject *init(PyObject *self, PyObject *args) { grpc_init(); @@ -74,4 +75,7 @@ PyMODINIT_FUNC init_c(void) { if (pygrpc_add_server(module) == -1) { return; } + if (pygrpc_add_server_credentials(module) == -1) { + return; + } } diff --git a/src/python/src/_adapter/_c_test.py b/src/python/src/_adapter/_c_test.py index bc0a622cc4f..19c91ffe018 100644 --- a/src/python/src/_adapter/_c_test.py +++ b/src/python/src/_adapter/_c_test.py @@ -136,6 +136,32 @@ class _CTest(unittest.TestCase): _c.shut_down() + def test_server_credentials(self): + root_certificates = b'Trust starts here. Really.' + first_private_key = b'This is a really bad private key, yo.' + first_certificate_chain = b'Trust me! Do I not look trustworty?' + second_private_key = b'This is another bad private key, yo.' + second_certificate_chain = b'Look into my eyes; you can totes trust me.' + + _c.init() + + server_credentials = _c.ServerCredentials( + None, ((first_private_key, first_certificate_chain),)) + del server_credentials + server_credentials = _c.ServerCredentials( + root_certificates, ((first_private_key, first_certificate_chain),)) + del server_credentials + server_credentials = _c.ServerCredentials( + root_certificates, + ((first_private_key, first_certificate_chain), + (second_private_key, second_certificate_chain),)) + del server_credentials + with self.assertRaises(TypeError): + _c.ServerCredentials( + root_certificates, first_private_key, second_certificate_chain) + + _c.shut_down() + if __name__ == '__main__': unittest.main() diff --git a/src/python/src/_adapter/_server_credentials.c b/src/python/src/_adapter/_server_credentials.c new file mode 100644 index 00000000000..390266ae897 --- /dev/null +++ b/src/python/src/_adapter/_server_credentials.c @@ -0,0 +1,157 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "_adapter/_server_credentials.h" + +#include +#include +#include + +static int pygrpc_server_credentials_init(ServerCredentials *self, + PyObject *args, PyObject *kwds) { + char *root_certificates; + PyObject *pair_sequence; + Py_ssize_t pair_count; + grpc_ssl_pem_key_cert_pair *pairs; + int error; + PyObject *iterator; + int i; + PyObject *pair; + + if (!(PyArg_ParseTuple(args, "zO", &root_certificates, &pair_sequence))) { + self->c_server_credentials = NULL; + return -1; + } + + pair_count = PySequence_Length(pair_sequence); + if (pair_count == -1) { + self->c_server_credentials = NULL; + return -1; + } + + iterator = PyObject_GetIter(pair_sequence); + if (iterator == NULL) { + self->c_server_credentials = NULL; + return -1; + } + pairs = gpr_malloc(pair_count * sizeof(grpc_ssl_pem_key_cert_pair)); + error = 0; + for (i = 0; i < pair_count; i++) { + pair = PyIter_Next(iterator); + if (pair == NULL) { + error = 1; + break; + } + if (!(PyArg_ParseTuple(pair, "ss", &pairs[i].private_key, + &pairs[i].cert_chain))) { + error = 1; + Py_DECREF(pair); + break; + } + Py_DECREF(pair); + } + Py_DECREF(iterator); + + if (error) { + self->c_server_credentials = NULL; + gpr_free(pairs); + return -1; + } else { + self->c_server_credentials = grpc_ssl_server_credentials_create( + root_certificates, pairs, pair_count); + gpr_free(pairs); + return 0; + } +} + +static void pygrpc_server_credentials_dealloc(ServerCredentials *self) { + if (self->c_server_credentials != NULL) { + grpc_server_credentials_release(self->c_server_credentials); + } + self->ob_type->tp_free((PyObject *)self); +} + +PyTypeObject pygrpc_ServerCredentialsType = { + PyObject_HEAD_INIT(NULL)0, /*ob_size*/ + "_grpc.ServerCredencials", /*tp_name*/ + sizeof(ServerCredentials), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)pygrpc_server_credentials_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + "Wrapping of grpc_server_credentials.", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)pygrpc_server_credentials_init, /* tp_init */ +}; + +int pygrpc_add_server_credentials(PyObject *module) { + pygrpc_ServerCredentialsType.tp_new = PyType_GenericNew; + if (PyType_Ready(&pygrpc_ServerCredentialsType) < 0) { + PyErr_SetString(PyExc_RuntimeError, + "Error defining pygrpc_ServerCredentialsType!"); + return -1; + } + if (PyModule_AddObject(module, "ServerCredentials", + (PyObject *)&pygrpc_ServerCredentialsType) == -1) { + PyErr_SetString(PyExc_ImportError, + "Couldn't add ServerCredentials type to module!"); + return -1; + } + return 0; +} diff --git a/src/python/src/_adapter/_server_credentials.h b/src/python/src/_adapter/_server_credentials.h new file mode 100644 index 00000000000..2e56efdcd9b --- /dev/null +++ b/src/python/src/_adapter/_server_credentials.h @@ -0,0 +1,48 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _ADAPTER__SERVER_CREDENTIALS_H_ +#define _ADAPTER__SERVER_CREDENTIALS_H_ + +#include +#include + +typedef struct { + PyObject_HEAD grpc_server_credentials *c_server_credentials; +} ServerCredentials; + +PyTypeObject pygrpc_ServerCredentialsType; + +int pygrpc_add_server_credentials(PyObject *module); + +#endif /* _ADAPTER__SERVER_CREDENTIALS_H_ */ From 7eb7d75c512020cc9ca43c12414281a2b7b45e78 Mon Sep 17 00:00:00 2001 From: Yang Gao Date: Tue, 10 Feb 2015 14:23:15 -0800 Subject: [PATCH 20/26] call implementation before the meeting --- include/grpc++/impl/call.h | 13 ++++++++--- src/cpp/common/call.cc | 46 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/include/grpc++/impl/call.h b/include/grpc++/impl/call.h index de789febe6e..91f3f584439 100644 --- a/include/grpc++/impl/call.h +++ b/include/grpc++/impl/call.h @@ -38,7 +38,7 @@ #include #include -#include +#include namespace google { namespace protobuf { @@ -59,7 +59,9 @@ class CallOpBuffer final : public CompletionQueueTag { void Reset(void *next_return_tag); - void AddSendInitialMetadata(std::vector > *metadata); + // Does not take ownership. + void AddSendInitialMetadata( + std::multimap *metadata); void AddSendMessage(const google::protobuf::Message &message); void AddRecvMessage(google::protobuf::Message *message); void AddClientSendClose(); @@ -74,7 +76,12 @@ class CallOpBuffer final : public CompletionQueueTag { void FinalizeResult(void *tag, bool *status) override; private: - void *return_tag_; + void *return_tag_ = nullptr; + std::multimap* metadata_ = nullptr; + const google::protobuf::Message* send_message_ = nullptr; + google::protobuf::Message* recv_message_ = nullptr; + bool client_send_close_ = false; + Status* status_ = nullptr; }; class CCallDeleter { diff --git a/src/cpp/common/call.cc b/src/cpp/common/call.cc index 0315ffb6bdd..37e374bad31 100644 --- a/src/cpp/common/call.cc +++ b/src/cpp/common/call.cc @@ -36,6 +36,52 @@ namespace grpc { +void CallOpBuffer::Reset(void* next_return_tag) { + return_tag_ = next_return_tag; + metadata_ = nullptr; + send_message_ = nullptr; + recv_message_ = nullptr; + client_send_close_ = false; + status_ = false; +} + +void CallOpBuffer::AddSendInitialMetadata( + std::multimap* metadata) { + metadata_ = metadata; +} + +void CallOpBuffer::AddSendMessage(const google::protobuf::Message& message) { + send_message_ = &message; +} + +void CallOpBuffer::AddRecvMessage(google::protobuf::Message *message) { + recv_message_ = message; +} + +void CallOpBuffer::AddClientSendClose() { + client_sent_close_ = true; +} + +void CallOpBuffer::AddClientRecvStatus(Status *status) { + status_ = status; +} + +void CallOpBuffer::FillOps(grpc_op *ops, size_t *nops) { + + +} + +void CallOpBuffer::FinalizeResult(void *tag, bool *status) { + +} + +void CCallDeleter::operator()(grpc_call* c) { + grpc_call_destroy(c); +} + +Call::Call(grpc_call* call, ChannelInterface* channel, CompletionQueue* cq) + : channel_(channel), cq_(cq), call_(call) {} + void Call::PerformOps(CallOpBuffer* buffer) { channel_->PerformOpsOnCall(buffer, this); } From 04cc8be23362025625c787ecce69514087e2fc2b Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Tue, 10 Feb 2015 16:11:22 -0800 Subject: [PATCH 21/26] First draft registered methods --- src/core/surface/server.c | 152 ++++++++++++++++++++++++++++---------- 1 file changed, 115 insertions(+), 37 deletions(-) diff --git a/src/core/surface/server.c b/src/core/surface/server.c index 972ac28cd26..81eaf4fc940 100644 --- a/src/core/surface/server.c +++ b/src/core/surface/server.c @@ -99,7 +99,7 @@ typedef struct { struct registered_method { char *method; char *host; - call_link pending; + call_data *pending; requested_call_array requested; registered_method *next; }; @@ -118,6 +118,9 @@ struct channel_data { /* linked list of all channels on a server */ channel_data *next; channel_data *prev; + channel_registered_method *registered_methods; + gpr_uint32 registered_method_slots; + gpr_uint32 registered_method_max_probes; }; struct grpc_server { @@ -167,7 +170,7 @@ struct call_data { legacy_data *legacy; - gpr_uint8 included[CALL_LIST_COUNT]; + call_data **root[CALL_LIST_COUNT]; call_link links[CALL_LIST_COUNT]; }; @@ -180,30 +183,30 @@ static void begin_call(grpc_server *server, call_data *calld, requested_call *rc); static void fail_call(grpc_server *server, requested_call *rc); -static int call_list_join(grpc_server *server, call_data *call, +static int call_list_join(call_data **root, call_data *call, call_list list) { - if (call->included[list]) return 0; - call->included[list] = 1; - if (!server->lists[list]) { - server->lists[list] = call; + GPR_ASSERT(!call->root[list]); + call->root[list] = root; + if (!*root) { + *root = call; call->links[list].next = call->links[list].prev = call; } else { - call->links[list].next = server->lists[list]; - call->links[list].prev = server->lists[list]->links[list].prev; + call->links[list].next = *root; + call->links[list].prev = (*root)->links[list].prev; call->links[list].next->links[list].prev = call->links[list].prev->links[list].next = call; } return 1; } -static call_data *call_list_remove_head(grpc_server *server, call_list list) { - call_data *out = server->lists[list]; +static call_data *call_list_remove_head(call_data **root, call_list list) { + call_data *out = *root; if (out) { - out->included[list] = 0; + out->root[list] = NULL; if (out->links[list].next == out) { - server->lists[list] = NULL; + *root = NULL; } else { - server->lists[list] = out->links[list].next; + *root = out->links[list].next; out->links[list].next->links[list].prev = out->links[list].prev; out->links[list].prev->links[list].next = out->links[list].next; } @@ -211,18 +214,18 @@ static call_data *call_list_remove_head(grpc_server *server, call_list list) { return out; } -static int call_list_remove(grpc_server *server, call_data *call, - call_list list) { - if (!call->included[list]) return 0; - call->included[list] = 0; - if (server->lists[list] == call) { - server->lists[list] = call->links[list].next; - if (server->lists[list] == call) { - server->lists[list] = NULL; +static int call_list_remove(call_data *call, call_list list) { + call_data **root = call->root[list]; + if (root == NULL) return 0; + call->root[list] = NULL; + if (*root == call) { + *root = call->links[list].next; + if (*root == call) { + *root = NULL; return 1; } } - GPR_ASSERT(server->lists[list] != call); + GPR_ASSERT(*root != call); call->links[list].next->links[list].prev = call->links[list].prev; call->links[list].prev->links[list].next = call->links[list].next; return 1; @@ -283,23 +286,53 @@ static void destroy_channel(channel_data *chand) { grpc_iomgr_add_callback(finish_destroy_channel, chand); } +static void finish_start_new_rpc_and_unlock(grpc_server *server, grpc_call_element *elem, call_data **pending_root, requested_call_array *array) { + requested_call rc; + call_data *calld = elem->call_data; + if (array->count == 0) { + calld->state = PENDING; + call_list_join(pending_root, calld, PENDING_START); + gpr_mu_unlock(&server->mu); + } else { + rc = server->requested_calls.calls[--server->requested_calls.count]; + calld->state = ACTIVATED; + gpr_mu_unlock(&server->mu); + begin_call(server, calld, &rc); + } +} + static void start_new_rpc(grpc_call_element *elem) { channel_data *chand = elem->channel_data; call_data *calld = elem->call_data; grpc_server *server = chand->server; + gpr_uint32 i; + gpr_uint32 hash; + channel_registered_method *rm; gpr_mu_lock(&server->mu); - if (server->requested_calls.count > 0) { - requested_call rc = - server->requested_calls.calls[--server->requested_calls.count]; - calld->state = ACTIVATED; - gpr_mu_unlock(&server->mu); - begin_call(server, calld, &rc); - } else { - calld->state = PENDING; - call_list_join(server, calld, PENDING_START); - gpr_mu_unlock(&server->mu); + if (chand->registered_methods && calld->path && calld->host) { + /* check for an exact match with host */ + hash = GRPC_MDSTR_KV_HASH(calld->host->hash, calld->path->hash); + for (i = 0; i < chand->registered_method_max_probes; i++) { + rm = &chand->registered_methods[(hash + i) % chand->registered_method_slots]; + if (!rm) break; + if (rm->host != calld->host) continue; + if (rm->method != calld->path) continue; + finish_start_new_rpc_and_unlock(server, elem, &rm->server_registered_method->pending, &rm->server_registered_method->requested); + return; + } + /* check for a wildcard method definition (no host set) */ + hash = GRPC_MDSTR_KV_HASH(0, calld->path->hash); + for (i = 0; i < chand->registered_method_max_probes; i++) { + rm = &chand->registered_methods[(hash + i) % chand->registered_method_slots]; + if (!rm) break; + if (rm->host != NULL) continue; + if (rm->method != calld->path) continue; + finish_start_new_rpc_and_unlock(server, elem, &rm->server_registered_method->pending, &rm->server_registered_method->requested); + return; + } } + finish_start_new_rpc_and_unlock(server, elem, &server->lists[PENDING_START], &server->requested_calls); } static void kill_zombie(void *elem, int success) { @@ -314,7 +347,7 @@ static void stream_closed(grpc_call_element *elem) { case ACTIVATED: break; case PENDING: - call_list_remove(chand->server, calld, PENDING_START); + call_list_remove(calld, PENDING_START); /* fallthrough intended */ case NOT_STARTED: calld->state = ZOMBIED; @@ -445,7 +478,7 @@ static void init_call_elem(grpc_call_element *elem, calld->call = grpc_call_from_top_element(elem); gpr_mu_lock(&chand->server->mu); - call_list_join(chand->server, calld, ALL_CALLS); + call_list_join(&chand->server->lists[ALL_CALLS], calld, ALL_CALLS); gpr_mu_unlock(&chand->server->mu); server_ref(chand->server); @@ -458,7 +491,7 @@ static void destroy_call_elem(grpc_call_element *elem) { gpr_mu_lock(&chand->server->mu); for (i = 0; i < CALL_LIST_COUNT; i++) { - call_list_remove(chand->server, elem->call_data, i); + call_list_remove(elem->call_data, i); } if (chand->server->shutdown && chand->server->have_shutdown_tag && chand->server->lists[ALL_CALLS] == NULL) { @@ -493,6 +526,7 @@ static void init_channel_elem(grpc_channel_element *elem, chand->path_key = grpc_mdstr_from_string(metadata_context, ":path"); chand->authority_key = grpc_mdstr_from_string(metadata_context, ":authority"); chand->next = chand->prev = chand; + chand->registered_methods = NULL; } static void destroy_channel_elem(grpc_channel_element *elem) { @@ -600,8 +634,18 @@ grpc_transport_setup_result grpc_server_setup_transport( grpc_channel_filter const **filters = gpr_malloc(sizeof(grpc_channel_filter *) * num_filters); size_t i; + size_t num_registered_methods; + size_t alloc; + registered_method *rm; + channel_registered_method *crm; grpc_channel *channel; channel_data *chand; + grpc_mdstr *host; + grpc_mdstr *method; + gpr_uint32 hash; + gpr_uint32 slots; + gpr_uint32 probes; + gpr_uint32 max_probes = 0; for (i = 0; i < s->channel_filter_count; i++) { filters[i] = s->channel_filters[i]; @@ -621,6 +665,32 @@ grpc_transport_setup_result grpc_server_setup_transport( server_ref(s); chand->channel = channel; + num_registered_methods = 0; + for (rm = s->registered_methods; rm; rm = rm->next) { + num_registered_methods++; + } + /* build a lookup table phrased in terms of mdstr's in this channels context + to quickly find registered methods */ + if (num_registered_methods > 0) { + slots = 2 * num_registered_methods; + alloc = sizeof(channel_registered_method) * slots; + chand->registered_methods = gpr_malloc(alloc); + memset(chand->registered_methods, 0, alloc); + for (rm = s->registered_methods; rm; rm = rm->next) { + host = rm->host ? grpc_mdstr_from_string(mdctx, rm->host) : NULL; + method = grpc_mdstr_from_string(mdctx, rm->host); + hash = GRPC_MDSTR_KV_HASH(host ? host->hash : 0, method->hash); + for (probes = 0; chand->registered_methods[(hash + probes) % slots].server_registered_method != NULL; probes++); + if (probes > max_probes) max_probes = probes; + crm = &chand->registered_methods[(hash + probes) % slots]; + crm->server_registered_method = rm; + crm->host = host; + crm->method = method; + } + chand->registered_method_slots = slots; + chand->registered_method_max_probes = max_probes; + } + gpr_mu_lock(&s->mu); chand->next = &s->root_channel_data; chand->prev = chand->next->prev; @@ -752,7 +822,15 @@ static grpc_call_error queue_call_request(grpc_server *server, fail_call(server, rc); return GRPC_CALL_OK; } - calld = call_list_remove_head(server, PENDING_START); + switch (rc->type) { + case LEGACY_CALL: + case BATCH_CALL: + calld = call_list_remove_head(&server->lists[PENDING_START], PENDING_START); + break; + case REGISTERED_CALL: + calld = call_list_remove_head(&rc->data.registered.registered_method->pending, PENDING_START); + break; + } if (calld) { GPR_ASSERT(calld->state == PENDING); calld->state = ACTIVATED; From 8e8fd89fafbab00bcb91c032692978320b8b1e6b Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Tue, 10 Feb 2015 17:02:08 -0800 Subject: [PATCH 22/26] Allow two completion queues on request call One for the new rpc notification, the other is bound to the new call. This will make async c++ soooo much easier. --- include/grpc/grpc | 1 + include/grpc/grpc.h | 7 +++- src/core/surface/server.c | 39 ++++++++++++------- test/core/end2end/tests/cancel_after_accept.c | 3 +- ...esponse_with_binary_metadata_and_payload.c | 3 +- ...quest_response_with_metadata_and_payload.c | 3 +- .../tests/request_response_with_payload.c | 3 +- ...ponse_with_trailing_metadata_and_payload.c | 3 +- .../tests/request_with_large_metadata.c | 3 +- .../core/end2end/tests/request_with_payload.c | 3 +- .../end2end/tests/simple_delayed_request.c | 3 +- test/core/end2end/tests/simple_request.c | 3 +- 12 files changed, 50 insertions(+), 24 deletions(-) create mode 120000 include/grpc/grpc diff --git a/include/grpc/grpc b/include/grpc/grpc new file mode 120000 index 00000000000..fc80ad1c867 --- /dev/null +++ b/include/grpc/grpc @@ -0,0 +1 @@ +/home/craig/grpc-ct/include/grpc \ No newline at end of file diff --git a/include/grpc/grpc.h b/include/grpc/grpc.h index 4ccb5a4dd59..7733f8bb2ae 100644 --- a/include/grpc/grpc.h +++ b/include/grpc/grpc.h @@ -553,7 +553,9 @@ grpc_call_error grpc_server_request_call_old(grpc_server *server, grpc_call_error grpc_server_request_call( grpc_server *server, grpc_call **call, grpc_call_details *details, grpc_metadata_array *request_metadata, - grpc_completion_queue *completion_queue, void *tag_new); + grpc_completion_queue *cq_when_rpc_available, + grpc_completion_queue *cq_bound_to_call, + void *tag_new); void *grpc_server_register_method(grpc_server *server, const char *method, const char *host); @@ -562,7 +564,8 @@ grpc_call_error grpc_server_request_registered_call( grpc_server *server, void *registered_method, grpc_call **call, gpr_timespec *deadline, grpc_metadata_array *request_metadata, grpc_byte_buffer **optional_payload, - grpc_completion_queue *completion_queue, void *tag_new); + grpc_completion_queue *cq_when_rpc_available, + grpc_completion_queue *cq_bound_to_call, void *tag_new); /* Create a server */ grpc_server *grpc_server_create(grpc_completion_queue *cq, diff --git a/src/core/surface/server.c b/src/core/surface/server.c index 81eaf4fc940..b28a52bcbdd 100644 --- a/src/core/surface/server.c +++ b/src/core/surface/server.c @@ -74,13 +74,15 @@ typedef struct { void *tag; union { struct { - grpc_completion_queue *cq; + grpc_completion_queue *cq_new; + grpc_completion_queue *cq_bind; grpc_call **call; grpc_call_details *details; grpc_metadata_array *initial_metadata; } batch; struct { - grpc_completion_queue *cq; + grpc_completion_queue *cq_new; + grpc_completion_queue *cq_bind; grpc_call **call; registered_method *registered_method; gpr_timespec *deadline; @@ -172,6 +174,8 @@ struct call_data { call_data **root[CALL_LIST_COUNT]; call_link links[CALL_LIST_COUNT]; + + grpc_completion_queue *cq_new; }; #define SERVER_FROM_CALL_ELEM(elem) \ @@ -847,12 +851,14 @@ static grpc_call_error queue_call_request(grpc_server *server, grpc_call_error grpc_server_request_call(grpc_server *server, grpc_call **call, grpc_call_details *details, grpc_metadata_array *initial_metadata, - grpc_completion_queue *cq, void *tag) { + grpc_completion_queue *cq_new, + grpc_completion_queue *cq_bind, void *tag) { requested_call rc; - grpc_cq_begin_op(cq, NULL, GRPC_OP_COMPLETE); + grpc_cq_begin_op(cq_new, NULL, GRPC_OP_COMPLETE); rc.type = BATCH_CALL; rc.tag = tag; - rc.data.batch.cq = cq; + rc.data.batch.cq_new = cq_new; + rc.data.batch.cq_bind = cq_bind; rc.data.batch.call = call; rc.data.batch.details = details; rc.data.batch.initial_metadata = initial_metadata; @@ -862,12 +868,14 @@ grpc_call_error grpc_server_request_call(grpc_server *server, grpc_call **call, grpc_call_error grpc_server_request_registered_call( grpc_server *server, void *registered_method, grpc_call **call, gpr_timespec *deadline, grpc_metadata_array *initial_metadata, - grpc_byte_buffer **optional_payload, grpc_completion_queue *cq, void *tag) { + grpc_byte_buffer **optional_payload, grpc_completion_queue *cq_new, grpc_completion_queue *cq_bind, + void *tag) { requested_call rc; - grpc_cq_begin_op(cq, NULL, GRPC_OP_COMPLETE); + grpc_cq_begin_op(cq_new, NULL, GRPC_OP_COMPLETE); rc.type = REGISTERED_CALL; rc.tag = tag; - rc.data.registered.cq = cq; + rc.data.registered.cq_new = cq_new; + rc.data.registered.cq_bind = cq_bind; rc.data.registered.call = call; rc.data.registered.registered_method = registered_method; rc.data.registered.deadline = deadline; @@ -926,16 +934,17 @@ static void begin_call(grpc_server *server, call_data *calld, &rc->data.batch.details->host_capacity, calld->host); cpstr(&rc->data.batch.details->method, &rc->data.batch.details->method_capacity, calld->path); - grpc_call_set_completion_queue(calld->call, rc->data.batch.cq); + grpc_call_set_completion_queue(calld->call, rc->data.batch.cq_bind); *rc->data.batch.call = calld->call; r->op = GRPC_IOREQ_RECV_INITIAL_METADATA; r->data.recv_metadata = rc->data.batch.initial_metadata; r++; + calld->cq_new = rc->data.batch.cq_new; publish = publish_registered_or_batch; break; case REGISTERED_CALL: *rc->data.registered.deadline = calld->deadline; - grpc_call_set_completion_queue(calld->call, rc->data.registered.cq); + grpc_call_set_completion_queue(calld->call, rc->data.registered.cq_bind); *rc->data.registered.call = calld->call; r->op = GRPC_IOREQ_RECV_INITIAL_METADATA; r->data.recv_metadata = rc->data.registered.initial_metadata; @@ -945,6 +954,7 @@ static void begin_call(grpc_server *server, call_data *calld, r->data.recv_message = rc->data.registered.optional_payload; r++; } + calld->cq_new = rc->data.registered.cq_new; publish = publish_registered_or_batch; break; } @@ -963,13 +973,13 @@ static void fail_call(grpc_server *server, requested_call *rc) { case BATCH_CALL: *rc->data.batch.call = NULL; rc->data.batch.initial_metadata->count = 0; - grpc_cq_end_op_complete(rc->data.batch.cq, rc->tag, NULL, do_nothing, + grpc_cq_end_op_complete(rc->data.batch.cq_new, rc->tag, NULL, do_nothing, NULL, GRPC_OP_ERROR); break; case REGISTERED_CALL: *rc->data.registered.call = NULL; rc->data.registered.initial_metadata->count = 0; - grpc_cq_end_op_complete(rc->data.registered.cq, rc->tag, NULL, do_nothing, + grpc_cq_end_op_complete(rc->data.registered.cq_new, rc->tag, NULL, do_nothing, NULL, GRPC_OP_ERROR); break; } @@ -996,7 +1006,10 @@ static void publish_legacy(grpc_call *call, grpc_op_error status, void *tag) { static void publish_registered_or_batch(grpc_call *call, grpc_op_error status, void *tag) { - grpc_cq_end_op_complete(grpc_call_get_completion_queue(call), tag, call, + grpc_call_element *elem = + grpc_call_stack_element(grpc_call_get_call_stack(call), 0); + call_data *calld = elem->call_data; + grpc_cq_end_op_complete(calld->cq_new, tag, call, do_nothing, NULL, status); } diff --git a/test/core/end2end/tests/cancel_after_accept.c b/test/core/end2end/tests/cancel_after_accept.c index eb26ff14f00..ab7c683e452 100644 --- a/test/core/end2end/tests/cancel_after_accept.c +++ b/test/core/end2end/tests/cancel_after_accept.c @@ -166,7 +166,8 @@ static void test_cancel_after_accept(grpc_end2end_test_config config, GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, &s, &call_details, &request_metadata_recv, - f.server_cq, tag(2))); + f.server_cq, f.server_cq, + tag(2))); cq_expect_completion(v_server, tag(2), GRPC_OP_OK); cq_verify(v_server); diff --git a/test/core/end2end/tests/request_response_with_binary_metadata_and_payload.c b/test/core/end2end/tests/request_response_with_binary_metadata_and_payload.c index fa5df5f5260..cb477144d3f 100644 --- a/test/core/end2end/tests/request_response_with_binary_metadata_and_payload.c +++ b/test/core/end2end/tests/request_response_with_binary_metadata_and_payload.c @@ -175,7 +175,8 @@ static void test_request_response_with_metadata_and_payload( GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, &s, &call_details, &request_metadata_recv, - f.server_cq, tag(101))); + f.server_cq, f.server_cq, + tag(101))); cq_expect_completion(v_server, tag(101), GRPC_OP_OK); cq_verify(v_server); diff --git a/test/core/end2end/tests/request_response_with_metadata_and_payload.c b/test/core/end2end/tests/request_response_with_metadata_and_payload.c index ad01fe70813..0d4822ec91b 100644 --- a/test/core/end2end/tests/request_response_with_metadata_and_payload.c +++ b/test/core/end2end/tests/request_response_with_metadata_and_payload.c @@ -168,7 +168,8 @@ static void test_request_response_with_metadata_and_payload( GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, &s, &call_details, &request_metadata_recv, - f.server_cq, tag(101))); + f.server_cq, f.server_cq, + tag(101))); cq_expect_completion(v_server, tag(101), GRPC_OP_OK); cq_verify(v_server); diff --git a/test/core/end2end/tests/request_response_with_payload.c b/test/core/end2end/tests/request_response_with_payload.c index 6b60c4da651..fe3f05fa954 100644 --- a/test/core/end2end/tests/request_response_with_payload.c +++ b/test/core/end2end/tests/request_response_with_payload.c @@ -162,7 +162,8 @@ static void request_response_with_payload(grpc_end2end_test_fixture f) { GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, &s, &call_details, &request_metadata_recv, - f.server_cq, tag(101))); + f.server_cq, f.server_cq, + tag(101))); cq_expect_completion(v_server, tag(101), GRPC_OP_OK); cq_verify(v_server); diff --git a/test/core/end2end/tests/request_response_with_trailing_metadata_and_payload.c b/test/core/end2end/tests/request_response_with_trailing_metadata_and_payload.c index 5878058c982..86ee405964b 100644 --- a/test/core/end2end/tests/request_response_with_trailing_metadata_and_payload.c +++ b/test/core/end2end/tests/request_response_with_trailing_metadata_and_payload.c @@ -169,7 +169,8 @@ static void test_request_response_with_metadata_and_payload( GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, &s, &call_details, &request_metadata_recv, - f.server_cq, tag(101))); + f.server_cq, f.server_cq, + tag(101))); cq_expect_completion(v_server, tag(101), GRPC_OP_OK); cq_verify(v_server); diff --git a/test/core/end2end/tests/request_with_large_metadata.c b/test/core/end2end/tests/request_with_large_metadata.c index 7e7bec0160c..8e5b1014f54 100644 --- a/test/core/end2end/tests/request_with_large_metadata.c +++ b/test/core/end2end/tests/request_with_large_metadata.c @@ -166,7 +166,8 @@ static void test_request_with_large_metadata(grpc_end2end_test_config config) { GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, &s, &call_details, &request_metadata_recv, - f.server_cq, tag(101))); + f.server_cq, f.server_cq, + tag(101))); cq_expect_completion(v_server, tag(101), GRPC_OP_OK); cq_verify(v_server); diff --git a/test/core/end2end/tests/request_with_payload.c b/test/core/end2end/tests/request_with_payload.c index 2c23f37e0c3..67b15770142 100644 --- a/test/core/end2end/tests/request_with_payload.c +++ b/test/core/end2end/tests/request_with_payload.c @@ -157,7 +157,8 @@ static void test_invoke_request_with_payload(grpc_end2end_test_config config) { GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, &s, &call_details, &request_metadata_recv, - f.server_cq, tag(101))); + f.server_cq, f.server_cq, + tag(101))); cq_expect_completion(v_server, tag(101), GRPC_OP_OK); cq_verify(v_server); diff --git a/test/core/end2end/tests/simple_delayed_request.c b/test/core/end2end/tests/simple_delayed_request.c index 99d1a263864..5c9109f9629 100644 --- a/test/core/end2end/tests/simple_delayed_request.c +++ b/test/core/end2end/tests/simple_delayed_request.c @@ -144,7 +144,8 @@ static void simple_delayed_request_body(grpc_end2end_test_config config, GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f->server, &s, &call_details, &request_metadata_recv, - f->server_cq, tag(101))); + f->server_cq, f->server_cq, + tag(101))); cq_expect_completion(v_server, tag(101), GRPC_OP_OK); cq_verify(v_server); diff --git a/test/core/end2end/tests/simple_request.c b/test/core/end2end/tests/simple_request.c index 0f046ae2d23..280bf98c167 100644 --- a/test/core/end2end/tests/simple_request.c +++ b/test/core/end2end/tests/simple_request.c @@ -150,7 +150,8 @@ static void simple_request_body(grpc_end2end_test_fixture f) { GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(f.server, &s, &call_details, &request_metadata_recv, - f.server_cq, tag(101))); + f.server_cq, f.server_cq, + tag(101))); cq_expect_completion(v_server, tag(101), GRPC_OP_OK); cq_verify(v_server); From 36d18a089e66aff8fc542ec2d98623b56a2e78f1 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Tue, 10 Feb 2015 17:05:56 -0800 Subject: [PATCH 23/26] Fix compile --- src/cpp/server/server.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cpp/server/server.cc b/src/cpp/server/server.cc index 5d44ab2ba42..44c5276b54a 100644 --- a/src/cpp/server/server.cc +++ b/src/cpp/server/server.cc @@ -152,7 +152,7 @@ void Server::RunRpc() { grpc_metadata_array initial_metadata; grpc_metadata_array_init(&initial_metadata); CompletionQueue cq; - grpc_call_error err = grpc_server_request_call(server_, &c_call, &call_details, &initial_metadata, cq.cq(), nullptr); + grpc_call_error err = grpc_server_request_call(server_, &c_call, &call_details, &initial_metadata, cq.cq(), cq.cq(), nullptr); GPR_ASSERT(err == GRPC_CALL_OK); bool ok = false; GPR_ASSERT(cq_.Next(&tag, &ok)); From cbd04850884ed6978fa6ae19ec8d04c3773d8ac4 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Tue, 10 Feb 2015 17:39:54 -0800 Subject: [PATCH 24/26] Simplify server ready for async path --- include/grpc++/server.h | 29 ++++++++++++++++---- src/cpp/server/server.cc | 58 ++++++++++++++-------------------------- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/include/grpc++/server.h b/include/grpc++/server.h index 670ffa78154..eefd4457f95 100644 --- a/include/grpc++/server.h +++ b/include/grpc++/server.h @@ -35,7 +35,7 @@ #define __GRPCPP_SERVER_H__ #include -#include +#include #include #include @@ -69,6 +69,25 @@ class Server { private: friend class ServerBuilder; + class MethodRequestData { + public: + MethodRequestData(RpcServiceMethod* method, void* tag) : method_(method), tag_(tag) {} + static MethodRequestData *Wait(CompletionQueue *cq); + + void Request(CompletionQueue* cq); + + class CallData { + public: + explicit CallData(MethodRequestData *mrd); + + void Run(); + }; + + private: + RpcServiceMethod *const method_; + void *const tag_; + }; + // ServerBuilder use only Server(ThreadPoolInterface* thread_pool, bool thread_pool_owned, ServerCredentials* creds); Server(); @@ -85,7 +104,8 @@ class Server { void ScheduleCallback(); // Completion queue. - CompletionQueue cq_; + std::unique_ptr cq_sync_; + std::unique_ptr cq_async_; // Sever status std::mutex mu_; @@ -95,12 +115,11 @@ class Server { int num_running_cb_; std::condition_variable callback_cv_; + std::list methods_; + // Pointer to the c grpc server. grpc_server* server_; - // A map for all method information. - std::map method_map_; - ThreadPoolInterface* thread_pool_; // Whether the thread pool is created and owned by the server. bool thread_pool_owned_; diff --git a/src/cpp/server/server.cc b/src/cpp/server/server.cc index 44c5276b54a..f5bbfdc6f73 100644 --- a/src/cpp/server/server.cc +++ b/src/cpp/server/server.cc @@ -54,9 +54,9 @@ Server::Server(ThreadPoolInterface *thread_pool, bool thread_pool_owned, ServerC secure_(creds != nullptr) { if (creds) { server_ = - grpc_secure_server_create(creds->GetRawCreds(), cq_.cq(), nullptr); + grpc_secure_server_create(creds->GetRawCreds(), nullptr, nullptr); } else { - server_ = grpc_server_create(cq_.cq(), nullptr); + server_ = grpc_server_create(nullptr, nullptr); } } @@ -80,13 +80,17 @@ Server::~Server() { } bool Server::RegisterService(RpcService *service) { + if (!cq_sync_) { + cq_sync_.reset(new CompletionQueue); + } for (int i = 0; i < service->GetMethodCount(); ++i) { RpcServiceMethod *method = service->GetMethod(i); - if (method_map_.find(method->name()) != method_map_.end()) { + void *tag = grpc_server_register_method(server_, method->name(), nullptr); + if (!tag) { gpr_log(GPR_DEBUG, "Attempt to register %s multiple times", method->name()); return false; } - method_map_.insert(std::make_pair(method->name(), method)); + methods_.emplace_back(method, tag); } return true; } @@ -106,7 +110,11 @@ bool Server::Start() { grpc_server_start(server_); // Start processing rpcs. - if (thread_pool_) { + if (cq_sync_) { + for (auto& m : methods_) { + m.Request(cq_sync_.get()); + } + ScheduleCallback(); } @@ -126,12 +134,6 @@ void Server::Shutdown() { } } } - - // Shutdown the completion queue. - cq_.Shutdown(); - void *tag = nullptr; - bool ok = false; - GPR_ASSERT(false == cq_.Next(&tag, &ok)); } void Server::ScheduleCallback() { @@ -144,35 +146,15 @@ void Server::ScheduleCallback() { void Server::RunRpc() { // Wait for one more incoming rpc. - void *tag = nullptr; - GPR_ASSERT(started_); - grpc_call *c_call = NULL; - grpc_call_details call_details; - grpc_call_details_init(&call_details); - grpc_metadata_array initial_metadata; - grpc_metadata_array_init(&initial_metadata); - CompletionQueue cq; - grpc_call_error err = grpc_server_request_call(server_, &c_call, &call_details, &initial_metadata, cq.cq(), cq.cq(), nullptr); - GPR_ASSERT(err == GRPC_CALL_OK); - bool ok = false; - GPR_ASSERT(cq_.Next(&tag, &ok)); - if (ok) { - ServerContext context; - Call call(c_call, nullptr, &cq); + auto* mrd = MethodRequestData::Wait(cq_sync_.get()); + if (mrd) { + MethodRequestData::CallData cd(mrd); + + mrd->Request(cq_sync_.get()); ScheduleCallback(); - RpcServiceMethod *method = nullptr; - auto iter = method_map_.find(call_details.method); - if (iter != method_map_.end()) { - method = iter->second; - } - // TODO(ctiller): allocate only if necessary - std::unique_ptr request(method->AllocateRequestProto()); - std::unique_ptr response(method->AllocateResponseProto()); - method->handler()->RunHandler(MethodHandler::HandlerParameter( - &call, &context, request.get(), response.get())); + + cd.Run(); } - grpc_call_details_destroy(&call_details); - grpc_metadata_array_destroy(&initial_metadata); { std::unique_lock lock(mu_); From 49959ed9c1e06a8d5aa6e0bd077fd940dfc3741d Mon Sep 17 00:00:00 2001 From: David Klempner Date: Tue, 10 Feb 2015 18:48:48 -0800 Subject: [PATCH 25/26] Have unary pollset check to see if the existing fd is orphaned This avoids an unnecessary upgrade to multipoller if there was no do_work called between an orphan and a subsequent add. Additionally, it avoids the need for epoll based multipoller to check for this case in its upgrade code by ensuring all existing fds are valid at upgrade time. --- src/core/iomgr/pollset_posix.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/core/iomgr/pollset_posix.c b/src/core/iomgr/pollset_posix.c index b0404b870b5..2837a0dff3f 100644 --- a/src/core/iomgr/pollset_posix.c +++ b/src/core/iomgr/pollset_posix.c @@ -202,8 +202,15 @@ static void unary_poll_pollset_add_fd(grpc_pollset *pollset, grpc_fd *fd) { if (fd == pollset->data.ptr) return; fds[0] = pollset->data.ptr; fds[1] = fd; - grpc_platform_become_multipoller(pollset, fds, GPR_ARRAY_SIZE(fds)); - grpc_fd_unref(fds[0]); + if (!grpc_fd_is_orphaned(fds[0])) { + grpc_platform_become_multipoller(pollset, fds, GPR_ARRAY_SIZE(fds)); + grpc_fd_unref(fds[0]); + } else { + /* old fd is orphaned and we haven't cleaned it up until now, so remain a + * unary poller */ + grpc_fd_unref(fds[0]); + pollset->data.ptr = fd; + } } static void unary_poll_pollset_del_fd(grpc_pollset *pollset, grpc_fd *fd) { From d5a04bdc6e5ea6bc81ff15409381323c196b0e0f Mon Sep 17 00:00:00 2001 From: Yang Gao Date: Wed, 11 Feb 2015 00:04:32 -0800 Subject: [PATCH 26/26] Implement FillOps --- include/grpc++/impl/call.h | 14 +++++- src/cpp/common/call.cc | 95 +++++++++++++++++++++++++++++++++++--- 2 files changed, 100 insertions(+), 9 deletions(-) diff --git a/include/grpc++/impl/call.h b/include/grpc++/impl/call.h index 91f3f584439..edc6555b0c4 100644 --- a/include/grpc++/impl/call.h +++ b/include/grpc++/impl/call.h @@ -34,6 +34,7 @@ #ifndef __GRPCPP_CALL_H__ #define __GRPCPP_CALL_H__ +#include #include #include @@ -72,16 +73,25 @@ class CallOpBuffer final : public CompletionQueueTag { // Convert to an array of grpc_op elements void FillOps(grpc_op *ops, size_t *nops); + // Release send buffers. + void ReleaseSendBuffer(); + // Called by completion queue just prior to returning from Next() or Pluck() void FinalizeResult(void *tag, bool *status) override; private: void *return_tag_ = nullptr; - std::multimap* metadata_ = nullptr; + size_t initial_metadata_count_ = 0; + grpc_metadata* initial_metadata_ = nullptr; const google::protobuf::Message* send_message_ = nullptr; + grpc_byte_buffer* write_buffer_ = nullptr; google::protobuf::Message* recv_message_ = nullptr; + grpc_byte_buffer* recv_message_buf_ = nullptr; bool client_send_close_ = false; - Status* status_ = nullptr; + Status* recv_status_ = nullptr; + grpc_status_code status_code_ = GRPC_STATUS_OK; + char* status_details_ = nullptr; + size_t status_details_capacity_ = 0; }; class CCallDeleter { diff --git a/src/cpp/common/call.cc b/src/cpp/common/call.cc index 37e374bad31..1aa79d46150 100644 --- a/src/cpp/common/call.cc +++ b/src/cpp/common/call.cc @@ -31,23 +31,64 @@ * */ +#include #include #include +#include "src/cpp/proto/proto_utils.h" + namespace grpc { void CallOpBuffer::Reset(void* next_return_tag) { return_tag_ = next_return_tag; - metadata_ = nullptr; + initial_metadata_count_ = 0; + if (initial_metadata_) { + gpr_free(initial_metadata_); + } send_message_ = nullptr; + if (write_buffer_) { + grpc_byte_buffer_destroy(write_buffer_); + write_buffer_ = nullptr; + } recv_message_ = nullptr; + if (recv_message_buf_) { + grpc_byte_buffer_destroy(recv_message_buf_); + recv_message_buf_ = nullptr; + } client_send_close_ = false; - status_ = false; + recv_status_ = nullptr; + status_code_ = GRPC_STATUS_OK; + if (status_details_) { + gpr_free(status_details_); + status_details_ = nullptr; + } + status_details_capacity_ = 0; +} + +namespace { +// TODO(yangg) if the map is changed before we send, the pointers will be a +// mess. Make sure it does not happen. +grpc_metadata* FillMetadata( + std::multimap* metadata) { + if (metadata->empty()) { return nullptr; } + grpc_metadata* metadata_array = (grpc_metadata*)gpr_malloc( + metadata->size()* sizeof(grpc_metadata)); + size_t i = 0; + for (auto iter = metadata->cbegin(); + iter != metadata->cend(); + ++iter, ++i) { + metadata_array[i].key = iter->first.c_str(); + metadata_array[i].value = iter->second.c_str(); + metadata_array[i].value_length = iter->second.size(); + } + return metadata_array; } +} // namespace void CallOpBuffer::AddSendInitialMetadata( - std::multimap* metadata) { - metadata_ = metadata; + std::multimap* metadata) { + initial_metadata_count_ = metadata->size(); + initial_metadata_ = FillMetadata(metadata); } void CallOpBuffer::AddSendMessage(const google::protobuf::Message& message) { @@ -59,16 +100,55 @@ void CallOpBuffer::AddRecvMessage(google::protobuf::Message *message) { } void CallOpBuffer::AddClientSendClose() { - client_sent_close_ = true; + client_send_close_ = true; } void CallOpBuffer::AddClientRecvStatus(Status *status) { - status_ = status; + recv_status_ = status; } -void CallOpBuffer::FillOps(grpc_op *ops, size_t *nops) { +void CallOpBuffer::FillOps(grpc_op *ops, size_t *nops) { + *nops = 0; + if (initial_metadata_count_) { + ops[*nops].op = GRPC_OP_SEND_INITIAL_METADATA; + ops[*nops].data.send_initial_metadata.count = initial_metadata_count_; + ops[*nops].data.send_initial_metadata.metadata = initial_metadata_; + (*nops)++; + } + if (send_message_) { + bool success = SerializeProto(*send_message_, &write_buffer_); + if (!success) { + // TODO handle parse failure + } + ops[*nops].op = GRPC_OP_SEND_MESSAGE; + ops[*nops].data.send_message = write_buffer_; + (*nops)++; + } + if (recv_message_) { + ops[*nops].op = GRPC_OP_RECV_MESSAGE; + ops[*nops].data.recv_message = &recv_message_buf_; + (*nops)++; + } + if (client_send_close_) { + ops[*nops].op = GRPC_OP_SEND_CLOSE_FROM_CLIENT; + (*nops)++; + } + if (recv_status_) { + ops[*nops].op = GRPC_OP_RECV_STATUS_ON_CLIENT; + // ops[*nops].data.recv_status_on_client.trailing_metadata = + ops[*nops].data.recv_status_on_client.status = &status_code_; + ops[*nops].data.recv_status_on_client.status_details = &status_details_; + ops[*nops].data.recv_status_on_client.status_details_capacity = &status_details_capacity_; + (*nops)++; + } +} +void CallOpBuffer::ReleaseSendBuffer() { + if (write_buffer_) { + grpc_byte_buffer_destroy(write_buffer_); + write_buffer_ = nullptr; + } } void CallOpBuffer::FinalizeResult(void *tag, bool *status) { @@ -84,6 +164,7 @@ Call::Call(grpc_call* call, ChannelInterface* channel, CompletionQueue* cq) void Call::PerformOps(CallOpBuffer* buffer) { channel_->PerformOpsOnCall(buffer, this); + buffer->ReleaseSendBuffer(); } } // namespace grpc