diff options
Diffstat (limited to 'src')
33 files changed, 1064 insertions, 104 deletions
diff --git a/src/core/channel/client_channel.c b/src/core/channel/client_channel.c index bcb024f2ac..507b91b8a6 100644 --- a/src/core/channel/client_channel.c +++ b/src/core/channel/client_channel.c @@ -298,6 +298,7 @@ static void channel_op(grpc_channel_element *elem, grpc_channel_element *from_elem, grpc_channel_op *op) { channel_data *chand = elem->channel_data; grpc_child_channel *child_channel; + grpc_channel_op rop; GPR_ASSERT(elem->filter == &grpc_client_channel_filter); switch (op->type) { @@ -323,6 +324,10 @@ static void channel_op(grpc_channel_element *elem, if (child_channel) { grpc_child_channel_destroy(child_channel, 1); } + /* fake a transport closed to satisfy the refcounting in client */ + rop.type = GRPC_TRANSPORT_CLOSED; + rop.dir = GRPC_CALL_UP; + grpc_channel_next_op(elem, &rop); break; case GRPC_TRANSPORT_GOAWAY: /* receiving goaway: if it's from our active child, drop the active child; diff --git a/src/core/iomgr/pollset_kick.c b/src/core/iomgr/pollset_kick.c index 238ec75c61..f0211b8274 100644 --- a/src/core/iomgr/pollset_kick.c +++ b/src/core/iomgr/pollset_kick.c @@ -48,49 +48,49 @@ /* This implementation is based on a freelist of wakeup fds, with extra logic to * handle kicks while there is no attached fd. */ +/* TODO(klempner): Autosize this, and consider providing a way to disable the + * cap entirely on systems with large fd limits */ #define GRPC_MAX_CACHED_WFDS 50 -#define GRPC_WFD_LOW_WATERMARK 25 static grpc_kick_fd_info *fd_freelist = NULL; static int fd_freelist_count = 0; static gpr_mu fd_freelist_mu; static grpc_kick_fd_info *allocate_wfd(void) { - grpc_kick_fd_info *info; + grpc_kick_fd_info *info = NULL; gpr_mu_lock(&fd_freelist_mu); if (fd_freelist != NULL) { info = fd_freelist; fd_freelist = fd_freelist->next; --fd_freelist_count; - } else { + } + gpr_mu_unlock(&fd_freelist_mu); + if (info == NULL) { info = gpr_malloc(sizeof(*info)); grpc_wakeup_fd_create(&info->wakeup_fd); info->next = NULL; } - gpr_mu_unlock(&fd_freelist_mu); return info; } -static void destroy_wfd(void) { - /* assumes fd_freelist_mu is held */ - grpc_kick_fd_info *current = fd_freelist; - fd_freelist = fd_freelist->next; - fd_freelist_count--; - grpc_wakeup_fd_destroy(¤t->wakeup_fd); - gpr_free(current); +static void destroy_wfd(grpc_kick_fd_info* wfd) { + grpc_wakeup_fd_destroy(&wfd->wakeup_fd); + gpr_free(wfd); } static void free_wfd(grpc_kick_fd_info *fd_info) { gpr_mu_lock(&fd_freelist_mu); - fd_info->next = fd_freelist; - fd_freelist = fd_info; - fd_freelist_count++; - if (fd_freelist_count > GRPC_MAX_CACHED_WFDS) { - while (fd_freelist_count > GRPC_WFD_LOW_WATERMARK) { - destroy_wfd(); - } + if (fd_freelist_count < GRPC_MAX_CACHED_WFDS) { + fd_info->next = fd_freelist; + fd_freelist = fd_info; + fd_freelist_count++; + fd_info = NULL; } gpr_mu_unlock(&fd_freelist_mu); + + if (fd_info) { + destroy_wfd(fd_info); + } } void grpc_pollset_kick_init(grpc_pollset_kick_state *kick_state) { @@ -148,6 +148,11 @@ void grpc_pollset_kick_global_init(void) { } void grpc_pollset_kick_global_destroy(void) { + while (fd_freelist != NULL) { + grpc_kick_fd_info *current = fd_freelist; + fd_freelist = fd_freelist->next; + destroy_wfd(current); + } grpc_wakeup_fd_global_destroy(); gpr_mu_destroy(&fd_freelist_mu); } diff --git a/src/core/security/credentials.c b/src/core/security/credentials.c index 7b7d8f3211..6f0d72c0c3 100644 --- a/src/core/security/credentials.c +++ b/src/core/security/credentials.c @@ -216,14 +216,10 @@ static void ssl_copy_key_material(const char *input, unsigned char **output, static void ssl_build_config(const char *pem_root_certs, grpc_ssl_pem_key_cert_pair *pem_key_cert_pair, grpc_ssl_config *config) { - if (pem_root_certs == NULL) { - /* TODO(jboeuf): Get them from the environment. */ - gpr_log(GPR_ERROR, "Default SSL roots not yet implemented."); - } else { + if (pem_root_certs != NULL) { ssl_copy_key_material(pem_root_certs, &config->pem_root_certs, &config->pem_root_certs_size); } - if (pem_key_cert_pair != NULL) { GPR_ASSERT(pem_key_cert_pair->private_key != NULL); GPR_ASSERT(pem_key_cert_pair->cert_chain != NULL); diff --git a/src/core/security/security_context.c b/src/core/security/security_context.c index 58cd458415..1edec29775 100644 --- a/src/core/security/security_context.c +++ b/src/core/security/security_context.c @@ -39,6 +39,8 @@ #include "src/core/channel/http_client_filter.h" #include "src/core/security/credentials.h" #include "src/core/security/secure_endpoint.h" +#include "src/core/support/env.h" +#include "src/core/support/file.h" #include "src/core/support/string.h" #include "src/core/surface/lame_client.h" #include "src/core/transport/chttp2/alpn.h" @@ -319,6 +321,28 @@ static grpc_security_context_vtable ssl_channel_vtable = { static grpc_security_context_vtable ssl_server_vtable = { ssl_server_destroy, ssl_server_create_handshaker, ssl_server_check_peer}; +static gpr_slice default_pem_root_certs; + +static void init_default_pem_root_certs(void) { + char *default_root_certs_path = + gpr_getenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR); + if (default_root_certs_path == NULL) { + default_pem_root_certs = gpr_empty_slice(); + } else { + default_pem_root_certs = gpr_load_file(default_root_certs_path, NULL); + gpr_free(default_root_certs_path); + } +} + +static size_t get_default_pem_roots(const unsigned char **pem_root_certs) { + /* TODO(jboeuf@google.com): Maybe revisit the approach which consists in + loading all the roots once for the lifetime of the process. */ + static gpr_once once = GPR_ONCE_INIT; + gpr_once_init(&once, init_default_pem_root_certs); + *pem_root_certs = GPR_SLICE_START_PTR(default_pem_root_certs); + return GPR_SLICE_LENGTH(default_pem_root_certs); +} + grpc_security_status grpc_ssl_channel_security_context_create( grpc_credentials *request_metadata_creds, const grpc_ssl_config *config, const char *secure_peer_name, grpc_channel_security_context **ctx) { @@ -330,6 +354,8 @@ grpc_security_status grpc_ssl_channel_security_context_create( tsi_result result = TSI_OK; grpc_ssl_channel_security_context *c; size_t i; + const unsigned char *pem_root_certs; + size_t pem_root_certs_size; for (i = 0; i < num_alpn_protocols; i++) { alpn_protocol_strings[i] = @@ -338,9 +364,8 @@ grpc_security_status grpc_ssl_channel_security_context_create( strlen(grpc_chttp2_get_alpn_version_index(i)); } - if (config == NULL || secure_peer_name == NULL || - config->pem_root_certs == NULL) { - gpr_log(GPR_ERROR, "An ssl channel needs a secure name and root certs."); + if (config == NULL || secure_peer_name == NULL) { + gpr_log(GPR_ERROR, "An ssl channel needs a config and a secure name."); goto error; } if (!check_request_metadata_creds(request_metadata_creds)) { @@ -357,11 +382,20 @@ grpc_security_status grpc_ssl_channel_security_context_create( if (secure_peer_name != NULL) { c->secure_peer_name = gpr_strdup(secure_peer_name); } + if (config->pem_root_certs == NULL) { + pem_root_certs_size = get_default_pem_roots(&pem_root_certs); + if (pem_root_certs == NULL || pem_root_certs_size == 0) { + gpr_log(GPR_ERROR, "Could not get default pem root certs."); + goto error; + } + } else { + pem_root_certs = config->pem_root_certs; + pem_root_certs_size = config->pem_root_certs_size; + } result = tsi_create_ssl_client_handshaker_factory( config->pem_private_key, config->pem_private_key_size, - config->pem_cert_chain, config->pem_cert_chain_size, - config->pem_root_certs, config->pem_root_certs_size, - GRPC_SSL_CIPHER_SUITES, alpn_protocol_strings, + config->pem_cert_chain, config->pem_cert_chain_size, pem_root_certs, + pem_root_certs_size, GRPC_SSL_CIPHER_SUITES, alpn_protocol_strings, alpn_protocol_string_lengths, num_alpn_protocols, &c->handshaker_factory); if (result != TSI_OK) { gpr_log(GPR_ERROR, "Handshaker factory creation failed with %s.", diff --git a/src/core/support/env.h b/src/core/support/env.h new file mode 100644 index 0000000000..81dda7d838 --- /dev/null +++ b/src/core/support/env.h @@ -0,0 +1,60 @@ +/* + * + * 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_ENV_H__ +#define __GRPC_SUPPORT_ENV_H__ + +#include <stdio.h> + +#include <grpc/support/slice.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Env utility functions */ + +/* Gets the environment variable value with the specified name. + Returns a newly allocated string. It is the responsability of the caller to + gpr_free the return value if not NULL (which means that the environment + variable exists). */ +char *gpr_getenv(const char *name); + +/* Sets the the environment with the specified name to the specified value. */ +void gpr_setenv(const char *name, const char *value); + +#ifdef __cplusplus +} +#endif + +#endif /* __GRPC_SUPPORT_ENV_H__ */ diff --git a/src/core/support/env_linux.c b/src/core/support/env_linux.c new file mode 100644 index 0000000000..28e3d1450f --- /dev/null +++ b/src/core/support/env_linux.c @@ -0,0 +1,61 @@ +/* + * + * 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. + * + */ + +/* for secure_getenv. */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <grpc/support/port_platform.h> + +#ifdef GPR_LINUX_ENV + +#include "src/core/support/env.h" + +#include <stdlib.h> + +#include <grpc/support/log.h> + +#include "src/core/support/string.h" + +char *gpr_getenv(const char *name) { + char *result = secure_getenv(name); + return result == NULL ? result : gpr_strdup(result); +} + +void gpr_setenv(const char *name, const char *value) { + int res = setenv(name, value, 1); + GPR_ASSERT(res == 0); +} + +#endif /* GPR_LINUX_ENV */ diff --git a/src/core/support/env_posix.c b/src/core/support/env_posix.c new file mode 100644 index 0000000000..bcbff9a177 --- /dev/null +++ b/src/core/support/env_posix.c @@ -0,0 +1,56 @@ +/* + * + * 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 <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_ENV + +#include "src/core/support/env.h" + +#include <stdlib.h> + +#include <grpc/support/log.h> + +#include "src/core/support/string.h" + +char *gpr_getenv(const char *name) { + char *result = getenv(name); + return result == NULL ? result : gpr_strdup(result); +} + +void gpr_setenv(const char *name, const char *value) { + int res = setenv(name, value, 1); + GPR_ASSERT(res == 0); +} + +#endif /* GPR_POSIX_ENV */ diff --git a/src/core/support/env_win32.c b/src/core/support/env_win32.c new file mode 100644 index 0000000000..a31fa79d68 --- /dev/null +++ b/src/core/support/env_win32.c @@ -0,0 +1,60 @@ +/* + * + * 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 <grpc/support/port_platform.h> + +#ifdef GPR_WIN32 + +#include "src/core/support/env.h" + +#include <stdlib.h> + +#include <grpc/support/log.h> + +char *gpr_getenv(const char *name) { + size_t required_size; + char *result = NULL; + + getenv_s(&required_size, NULL, 0, name); + if (required_size == 0) return NULL; + result = gpr_malloc(required_size); + getenv_s(&required_size, result, required_size, name); + return result; +} + +void gpr_setenv(const char *name, const char *value) { + errno_t res = _putenv_s(name, value); + GPR_ASSERT(res == 0); +} + +#endif /* GPR_WIN32 */ diff --git a/src/core/support/file.c b/src/core/support/file.c new file mode 100644 index 0000000000..c0bb1b66a0 --- /dev/null +++ b/src/core/support/file.c @@ -0,0 +1,89 @@ +/* + * + * 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 "src/core/support/file.h" + +#include <errno.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +#include "src/core/support/string.h" + +gpr_slice gpr_load_file(const char *filename, int *success) { + unsigned char *contents = NULL; + size_t contents_size = 0; + unsigned char buf[4096]; + char *error_msg = NULL; + gpr_slice result = gpr_empty_slice(); + FILE *file = fopen(filename, "rb"); + + if (file == NULL) { + gpr_asprintf(&error_msg, "Could not open file %s (error = %s).", filename, + strerror(errno)); + GPR_ASSERT(error_msg != NULL); + goto end; + } + + while (1) { + size_t bytes_read = fread(buf, 1, sizeof(buf), file); + if (bytes_read > 0) { + contents = gpr_realloc(contents, contents_size + bytes_read); + memcpy(contents + contents_size, buf, bytes_read); + contents_size += bytes_read; + } + if (bytes_read < sizeof(buf)) { + if (ferror(file)) { + gpr_asprintf(&error_msg, "Error %s occured while reading file %s.", + strerror(errno), filename); + GPR_ASSERT(error_msg != NULL); + goto end; + } else { + GPR_ASSERT(feof(file)); + break; + } + } + } + if (success != NULL) *success = 1; + result = gpr_slice_new(contents, contents_size, gpr_free); + +end: + if (error_msg != NULL) { + gpr_log(GPR_ERROR, "%s", error_msg); + gpr_free(error_msg); + if (success != NULL) *success = 0; + } + if (file != NULL) fclose(file); + return result; +} diff --git a/src/core/support/file.h b/src/core/support/file.h new file mode 100644 index 0000000000..92f420e7ce --- /dev/null +++ b/src/core/support/file.h @@ -0,0 +1,61 @@ +/* + * + * 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_FILE_H__ +#define __GRPC_SUPPORT_FILE_H__ + +#include <stdio.h> + +#include <grpc/support/slice.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* File utility functions */ + +/* Loads the content of a file into a slice. The success parameter, if not NULL, + will be set to 1 in case of success and 0 in case of failure. */ +gpr_slice gpr_load_file(const char *filename, int *success); + +/* Creates a temporary file from a prefix. + If tmp_filename is not NULL, *tmp_filename is assigned the name of the + created file and it is the responsibility of the caller to gpr_free it + unless an error occurs in which case it will be set to NULL. */ +FILE *gpr_tmpfile(const char *prefix, char **tmp_filename); + +#ifdef __cplusplus +} +#endif + +#endif /* __GRPC_SUPPORT_FILE_H__ */ diff --git a/src/core/support/file_posix.c b/src/core/support/file_posix.c new file mode 100644 index 0000000000..cb48b3d52f --- /dev/null +++ b/src/core/support/file_posix.c @@ -0,0 +1,97 @@ +/* + * + * 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. + * + */ + +/* Posix code for gpr fdopen and mkstemp support. */ + +#if !defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 200112L +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200112L +#endif + +/* Don't know why I have to do this for mkstemp, looks like _POSIX_C_SOURCE + should be enough... */ +#ifndef _BSD_SOURCE +#define _BSD_SOURCE +#endif + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_FILE + +#include "src/core/support/file.h" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +#include "src/core/support/string.h" + +FILE *gpr_tmpfile(const char *prefix, char **tmp_filename) { + FILE *result = NULL; + char *template; + int fd; + + if (tmp_filename != NULL) *tmp_filename = NULL; + + gpr_asprintf(&template, "/tmp/%s_XXXXXX", prefix); + GPR_ASSERT(template != NULL); + + fd = mkstemp(template); + if (fd == -1) { + gpr_log(GPR_ERROR, "mkstemp failed for template %s with error %s.", + template, strerror(errno)); + goto end; + } + result = fdopen(fd, "w+"); + if (result == NULL) { + gpr_log(GPR_ERROR, "Could not open file %s from fd %d (error = %s).", + template, fd, strerror(errno)); + unlink(template); + close(fd); + goto end; + } + +end: + if (result != NULL && tmp_filename != NULL) { + *tmp_filename = template; + } else { + gpr_free(template); + } + return result; +} + +#endif /* GPR_POSIX_FILE */ diff --git a/src/core/support/file_win32.c b/src/core/support/file_win32.c new file mode 100644 index 0000000000..d415281e0d --- /dev/null +++ b/src/core/support/file_win32.c @@ -0,0 +1,78 @@ +/* + * + * 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 <grpc/support/port_platform.h> + +#ifdef GPR_WIN32 + +#include "src/core/support/file.h" + +#include <io.h> +#include <stdio.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +FILE *gpr_tmpfile(const char *prefix, char **tmp_filename) { + FILE *result = NULL; + char *template; + + if (tmp_filename != NULL) *tmp_filename = NULL; + + gpr_asprintf(&template, "%s_XXXXXX", prefix); + GPR_ASSERT(template != NULL); + + /* _mktemp_s can only create a maximum of 26 file names for any combination of + base and template values which is kind of sad... We may revisit this + function later to have something better... */ + if (_mktemp_s(template, strlen(template) + 1) != 0) { + gpr_log(LOG_ERROR, "Could not create tmp file."); + goto end; + } + if (fopen_s(&result, template, "wb+") != 0) { + gpr_log(GPR_ERROR, "Could not open file %s", template); + result = NULL; + goto end; + } + +end: + if (result != NULL && tmp_filename != NULL) { + *tmp_filename = template; + } else { + gpr_free(template); + } + return result; +} + +#endif /* GPR_WIN32 */ diff --git a/src/core/surface/byte_buffer_queue.c b/src/core/surface/byte_buffer_queue.c index dc280a60c5..9709a665ba 100644 --- a/src/core/surface/byte_buffer_queue.c +++ b/src/core/surface/byte_buffer_queue.c @@ -35,7 +35,13 @@ #include <grpc/support/alloc.h> #include <grpc/support/useful.h> -static void bba_destroy(grpc_bbq_array *array) { gpr_free(array->data); } +static void bba_destroy(grpc_bbq_array *array, size_t start_pos) { + size_t i; + for (i = start_pos; i < array->count; i++) { + grpc_byte_buffer_destroy(array->data[i]); + } + gpr_free(array->data); +} /* Append an operation to an array, expanding as needed */ static void bba_push(grpc_bbq_array *a, grpc_byte_buffer *buffer) { @@ -47,8 +53,8 @@ static void bba_push(grpc_bbq_array *a, grpc_byte_buffer *buffer) { } void grpc_bbq_destroy(grpc_byte_buffer_queue *q) { - bba_destroy(&q->filling); - bba_destroy(&q->draining); + bba_destroy(&q->filling, 0); + bba_destroy(&q->draining, q->drain_pos); } int grpc_bbq_empty(grpc_byte_buffer_queue *q) { diff --git a/src/core/surface/call.c b/src/core/surface/call.c index ebd6ace962..c68ce5a6a8 100644 --- a/src/core/surface/call.c +++ b/src/core/surface/call.c @@ -146,10 +146,10 @@ struct grpc_call { /* Active ioreqs. request_set and request_data contain one element per active ioreq operation. - + request_set[op] is an integer specifying a set of operations to which the request belongs: - - if it is < GRPC_IOREQ_OP_COUNT, then this operation is pending + - if it is < GRPC_IOREQ_OP_COUNT, then this operation is pending completion, and the integer represents to which group of operations the ioreq belongs. Each group is represented by one master, and the integer in request_set is an index into masters to find the master @@ -158,7 +158,7 @@ struct grpc_call { started - finally, if request_set[op] is REQSET_DONE, then the operation is complete and unavailable to be started again - + request_data[op] is the request data as supplied by the initiator of a request, and is valid iff request_set[op] <= GRPC_IOREQ_OP_COUNT. The set fields are as per the request type specified by op. @@ -200,12 +200,12 @@ struct grpc_call { /* Call refcount - to keep the call alive during asynchronous operations */ gpr_refcount internal_refcount; - /* Data that the legacy api needs to track. To be deleted at some point + /* Data that the legacy api needs to track. To be deleted at some point soon */ legacy_state *legacy_state; }; -#define CALL_STACK_FROM_CALL(call) ((grpc_call_stack *)((call) + 1)) +#define CALL_STACK_FROM_CALL(call) ((grpc_call_stack *)((call)+1)) #define CALL_FROM_CALL_STACK(call_stack) (((grpc_call *)(call_stack)) - 1) #define CALL_ELEM_FROM_CALL(call, idx) \ grpc_call_stack_element(CALL_STACK_FROM_CALL(call), idx) @@ -273,6 +273,7 @@ static void destroy_call(void *call, int ignored_success) { if (c->legacy_state) { destroy_legacy_state(c->legacy_state); } + grpc_bbq_destroy(&c->incoming_queue); gpr_free(c); } @@ -334,7 +335,9 @@ static void unlock(grpc_call *call) { completed_request completed_requests[GRPC_IOREQ_OP_COUNT]; int num_completed_requests = call->num_completed_requests; int need_more_data = - call->need_more_data && !is_op_live(call, GRPC_IOREQ_SEND_INITIAL_METADATA); + call->need_more_data && + !call->sending && + call->write_state >= WRITE_STATE_STARTED; int i; if (need_more_data) { @@ -853,7 +856,7 @@ static gpr_uint32 decode_status(grpc_mdelem *md) { gpr_uint32 status; void *user_data = grpc_mdelem_get_user_data(md, destroy_status); if (user_data) { - status = ((gpr_uint32)(gpr_intptr)user_data) - STATUS_OFFSET; + status = ((gpr_uint32)(gpr_intptr) user_data) - STATUS_OFFSET; } else { if (!gpr_parse_bytes_to_uint32(grpc_mdstr_as_c_string(md->value), GPR_SLICE_LENGTH(md->value->slice), @@ -941,6 +944,8 @@ struct legacy_state { char *details; grpc_status_code status; + char *send_details; + size_t msg_in_read_idx; grpc_byte_buffer *msg_in; @@ -966,6 +971,8 @@ static void destroy_legacy_state(legacy_state *ls) { } gpr_free(ls->initial_md_in.metadata); gpr_free(ls->trailing_md_in.metadata); + gpr_free(ls->details); + gpr_free(ls->send_details); gpr_free(ls); } @@ -1214,8 +1221,7 @@ grpc_call_error grpc_call_start_write_status_old(grpc_call *call, reqs[0].data.send_metadata.metadata = ls->md_out[ls->md_out_buffer]; reqs[1].op = GRPC_IOREQ_SEND_STATUS; reqs[1].data.send_status.code = status; - /* MEMLEAK */ - reqs[1].data.send_status.details = gpr_strdup(details); + reqs[1].data.send_status.details = ls->send_details = gpr_strdup(details); reqs[2].op = GRPC_IOREQ_SEND_CLOSE; err = start_ioreq(call, reqs, 3, finish_finish, tag); unlock(call); diff --git a/src/core/surface/channel.c b/src/core/surface/channel.c index c33ea923e8..b33bd7b357 100644 --- a/src/core/surface/channel.c +++ b/src/core/surface/channel.c @@ -52,6 +52,9 @@ struct grpc_channel { }; #define CHANNEL_STACK_FROM_CHANNEL(c) ((grpc_channel_stack *)((c)+1)) +#define CHANNEL_FROM_CHANNEL_STACK(channel_stack) (((grpc_channel *)(channel_stack)) - 1) +#define CHANNEL_FROM_TOP_ELEM(top_elem) \ + CHANNEL_FROM_CHANNEL_STACK(grpc_channel_stack_from_top_element(top_elem)) grpc_channel *grpc_channel_create_from_filters( const grpc_channel_filter **filters, size_t num_filters, @@ -60,8 +63,8 @@ grpc_channel *grpc_channel_create_from_filters( sizeof(grpc_channel) + grpc_channel_stack_size(filters, num_filters); grpc_channel *channel = gpr_malloc(size); channel->is_client = is_client; - /* decremented by grpc_channel_destroy */ - gpr_ref_init(&channel->refs, 1); + /* decremented by grpc_channel_destroy, and grpc_client_channel_closed if is_client */ + gpr_ref_init(&channel->refs, 1 + is_client); channel->metadata_context = mdctx; channel->grpc_status_string = grpc_mdstr_from_string(mdctx, "grpc-status"); channel->grpc_message_string = grpc_mdstr_from_string(mdctx, "grpc-message"); @@ -158,6 +161,10 @@ void grpc_channel_destroy(grpc_channel *channel) { grpc_channel_internal_unref(channel); } +void grpc_client_channel_closed(grpc_channel_element *elem) { + grpc_channel_internal_unref(CHANNEL_FROM_TOP_ELEM(elem)); +} + grpc_channel_stack *grpc_channel_get_channel_stack(grpc_channel *channel) { return CHANNEL_STACK_FROM_CHANNEL(channel); } diff --git a/src/core/surface/channel.h b/src/core/surface/channel.h index b3ea2ede40..ff9bbc237e 100644 --- a/src/core/surface/channel.h +++ b/src/core/surface/channel.h @@ -45,6 +45,8 @@ grpc_mdctx *grpc_channel_get_metadata_context(grpc_channel *channel); grpc_mdstr *grpc_channel_get_status_string(grpc_channel *channel); grpc_mdstr *grpc_channel_get_message_string(grpc_channel *channel); +void grpc_client_channel_closed(grpc_channel_element *elem); + void grpc_channel_internal_ref(grpc_channel *channel); void grpc_channel_internal_unref(grpc_channel *channel); diff --git a/src/core/surface/client.c b/src/core/surface/client.c index fa63e855cc..64ee9d51e8 100644 --- a/src/core/surface/client.c +++ b/src/core/surface/client.c @@ -34,6 +34,7 @@ #include "src/core/surface/client.h" #include "src/core/surface/call.h" +#include "src/core/surface/channel.h" #include "src/core/support/string.h" #include <grpc/support/alloc.h> #include <grpc/support/log.h> @@ -87,7 +88,7 @@ static void channel_op(grpc_channel_element *elem, gpr_log(GPR_ERROR, "Client cannot accept new calls"); break; case GRPC_TRANSPORT_CLOSED: - gpr_log(GPR_ERROR, "Transport closed"); + grpc_client_channel_closed(elem); break; case GRPC_TRANSPORT_GOAWAY: gpr_slice_unref(op->data.goaway.message); diff --git a/src/core/surface/lame_client.c b/src/core/surface/lame_client.c index 2f5eff5584..411dbabfd3 100644 --- a/src/core/surface/lame_client.c +++ b/src/core/surface/lame_client.c @@ -76,6 +76,9 @@ static void channel_op(grpc_channel_element *elem, case GRPC_CHANNEL_GOAWAY: gpr_slice_unref(op->data.goaway.message); break; + case GRPC_CHANNEL_DISCONNECT: + grpc_client_channel_closed(elem); + break; default: break; } diff --git a/src/core/surface/server.c b/src/core/surface/server.c index a057694f13..455bd4337f 100644 --- a/src/core/surface/server.c +++ b/src/core/surface/server.c @@ -258,7 +258,6 @@ static void stream_closed(grpc_call_element *elem) { gpr_mu_lock(&chand->server->mu); switch (calld->state) { case ACTIVATED: - grpc_call_stream_closed(elem); break; case PENDING: call_list_remove(chand->server, calld, PENDING_START); @@ -271,6 +270,7 @@ static void stream_closed(grpc_call_element *elem) { break; } gpr_mu_unlock(&chand->server->mu); + grpc_call_stream_closed(elem); } static void read_closed(grpc_call_element *elem) { diff --git a/src/csharp/GrpcCore/Call.cs b/src/csharp/GrpcCore/Call.cs index bf257e5d59..d3847a8009 100644 --- a/src/csharp/GrpcCore/Call.cs +++ b/src/csharp/GrpcCore/Call.cs @@ -8,10 +8,8 @@ namespace Google.GRPC.Core readonly string methodName; readonly Func<TRequest, byte[]> requestSerializer; readonly Func<byte[], TResponse> responseDeserializer; - readonly TimeSpan timeout; readonly Channel channel; - // TODO: channel param should be removed in the future. public Call(string methodName, Func<TRequest, byte[]> requestSerializer, Func<byte[], TResponse> responseDeserializer, @@ -20,24 +18,22 @@ namespace Google.GRPC.Core this.methodName = methodName; this.requestSerializer = requestSerializer; this.responseDeserializer = responseDeserializer; - this.timeout = timeout; this.channel = channel; } - - public Channel Channel + public Call(Method<TRequest, TResponse> method, Channel channel) { - get - { - return this.channel; - } + this.methodName = method.Name; + this.requestSerializer = method.RequestMarshaller.Serialize; + this.responseDeserializer = method.ResponseMarshaller.Deserialize; + this.channel = channel; } - public TimeSpan Timeout + public Channel Channel { get { - return this.timeout; + return this.channel; } } diff --git a/src/csharp/GrpcCore/GrpcCore.csproj b/src/csharp/GrpcCore/GrpcCore.csproj index f0c84e78ea..2ad0f9154c 100644 --- a/src/csharp/GrpcCore/GrpcCore.csproj +++ b/src/csharp/GrpcCore/GrpcCore.csproj @@ -54,6 +54,11 @@ <Compile Include="Internal\AsyncCall.cs" /> <Compile Include="Internal\ServerSafeHandle.cs" /> <Compile Include="Internal\StreamingInputObserver.cs" /> + <Compile Include="Method.cs" /> + <Compile Include="IMarshaller.cs" /> + <Compile Include="ServerCalls.cs" /> + <Compile Include="ServerCallHandler.cs" /> + <Compile Include="Internal\ServerWritingObserver.cs" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <ItemGroup> diff --git a/src/csharp/GrpcCore/IMarshaller.cs b/src/csharp/GrpcCore/IMarshaller.cs new file mode 100644 index 0000000000..eb08d8d386 --- /dev/null +++ b/src/csharp/GrpcCore/IMarshaller.cs @@ -0,0 +1,31 @@ +using System; + +namespace Google.GRPC.Core +{ + /// <summary> + /// For serializing and deserializing messages. + /// </summary> + public interface IMarshaller<T> + { + byte[] Serialize(T value); + + T Deserialize(byte[] payload); + } + + /// <summary> + /// UTF-8 Marshalling for string. Useful for testing. + /// </summary> + internal class StringMarshaller : IMarshaller<string> { + + public byte[] Serialize(string value) + { + return System.Text.Encoding.UTF8.GetBytes(value); + } + + public string Deserialize(byte[] payload) + { + return System.Text.Encoding.UTF8.GetString(payload); + } + } +} + diff --git a/src/csharp/GrpcCore/Internal/AsyncCall.cs b/src/csharp/GrpcCore/Internal/AsyncCall.cs index e83ca0eaa9..c38363bb2b 100644 --- a/src/csharp/GrpcCore/Internal/AsyncCall.cs +++ b/src/csharp/GrpcCore/Internal/AsyncCall.cs @@ -86,6 +86,14 @@ namespace Google.GRPC.Core.Internal return StartRead().Task; } + public Task Halfclosed + { + get + { + return halfcloseTcs.Task; + } + } + public Task<Status> Finished { get diff --git a/src/csharp/GrpcCore/Internal/ServerSafeHandle.cs b/src/csharp/GrpcCore/Internal/ServerSafeHandle.cs index 0d38bce63e..08d4cf0192 100644 --- a/src/csharp/GrpcCore/Internal/ServerSafeHandle.cs +++ b/src/csharp/GrpcCore/Internal/ServerSafeHandle.cs @@ -30,8 +30,8 @@ namespace Google.GRPC.Core.Internal [DllImport("libgrpc.so")] static extern void grpc_server_shutdown(ServerSafeHandle server); - [DllImport("libgrpc.so")] - static extern void grpc_server_shutdown_and_notify(ServerSafeHandle server, IntPtr tag); + [DllImport("libgrpc.so", EntryPoint = "grpc_server_shutdown_and_notify")] + static extern void grpc_server_shutdown_and_notify_CALLBACK(ServerSafeHandle server, [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate callback); [DllImport("libgrpc.so")] static extern void grpc_server_destroy(IntPtr server); @@ -62,6 +62,11 @@ namespace Google.GRPC.Core.Internal grpc_server_shutdown(this); } + public void ShutdownAndNotify(EventCallbackDelegate callback) + { + grpc_server_shutdown_and_notify_CALLBACK(this, callback); + } + public GRPCCallError RequestCall(EventCallbackDelegate callback) { return grpc_server_request_call_old_CALLBACK(this, callback); diff --git a/src/csharp/GrpcCore/Internal/ServerWritingObserver.cs b/src/csharp/GrpcCore/Internal/ServerWritingObserver.cs new file mode 100644 index 0000000000..2b46e9c53d --- /dev/null +++ b/src/csharp/GrpcCore/Internal/ServerWritingObserver.cs @@ -0,0 +1,38 @@ +using System; +using Google.GRPC.Core.Internal; + +namespace Google.GRPC.Core.Internal +{ + /// <summary> + /// Observer that writes all arriving messages to a call abstraction (in blocking fashion) + /// and then halfcloses the call. Used for server-side call handling. + /// </summary> + internal class ServerWritingObserver<TWrite, TRead> : IObserver<TWrite> + { + readonly AsyncCall<TWrite, TRead> call; + + public ServerWritingObserver(AsyncCall<TWrite, TRead> call) + { + this.call = call; + } + + public void OnCompleted() + { + // TODO: how bad is the Wait here? + call.WriteStatusAsync(new Status(StatusCode.GRPC_STATUS_OK, "")).Wait(); + } + + public void OnError(Exception error) + { + // TODO: handle this... + throw new InvalidOperationException("This should never be called."); + } + + public void OnNext(TWrite value) + { + // TODO: how bad is the Wait here? + call.WriteAsync(value).Wait(); + } + } +} + diff --git a/src/csharp/GrpcCore/Internal/StreamingInputObserver.cs b/src/csharp/GrpcCore/Internal/StreamingInputObserver.cs index d483e53a2d..c5de979351 100644 --- a/src/csharp/GrpcCore/Internal/StreamingInputObserver.cs +++ b/src/csharp/GrpcCore/Internal/StreamingInputObserver.cs @@ -1,7 +1,7 @@ using System; using Google.GRPC.Core.Internal; -namespace Google.GRPC.Core +namespace Google.GRPC.Core.Internal { internal class StreamingInputObserver<TWrite, TRead> : IObserver<TWrite> { diff --git a/src/csharp/GrpcCore/Method.cs b/src/csharp/GrpcCore/Method.cs new file mode 100644 index 0000000000..2790115695 --- /dev/null +++ b/src/csharp/GrpcCore/Method.cs @@ -0,0 +1,64 @@ +using System; + +namespace Google.GRPC.Core +{ + public enum MethodType + { + Unary, + ClientStreaming, + ServerStreaming, + DuplexStreaming + } + + /// <summary> + /// A description of a service method. + /// </summary> + public class Method<TRequest, TResponse> + { + readonly MethodType type; + readonly string name; + readonly IMarshaller<TRequest> requestMarshaller; + readonly IMarshaller<TResponse> responseMarshaller; + + public Method(MethodType type, string name, IMarshaller<TRequest> requestMarshaller, IMarshaller<TResponse> responseMarshaller) + { + this.type = type; + this.name = name; + this.requestMarshaller = requestMarshaller; + this.responseMarshaller = responseMarshaller; + } + + public MethodType Type + { + get + { + return this.type; + } + } + + public string Name + { + get + { + return this.name; + } + } + + public IMarshaller<TRequest> RequestMarshaller + { + get + { + return this.requestMarshaller; + } + } + + public IMarshaller<TResponse> ResponseMarshaller + { + get + { + return this.responseMarshaller; + } + } + } +} + diff --git a/src/csharp/GrpcCore/Server.cs b/src/csharp/GrpcCore/Server.cs index 68da1a8300..4e9d114f85 100644 --- a/src/csharp/GrpcCore/Server.cs +++ b/src/csharp/GrpcCore/Server.cs @@ -1,7 +1,9 @@ using System; using System.Runtime.InteropServices; using System.Diagnostics; +using System.Threading.Tasks; using System.Collections.Concurrent; +using System.Collections.Generic; using Google.GRPC.Core.Internal; namespace Google.GRPC.Core @@ -15,10 +17,15 @@ namespace Google.GRPC.Core // TODO: make sure the delegate doesn't get garbage collected while // native callbacks are in the completion queue. readonly EventCallbackDelegate newRpcHandler; + readonly EventCallbackDelegate serverShutdownHandler; readonly BlockingCollection<NewRpcInfo> newRpcQueue = new BlockingCollection<NewRpcInfo>(); readonly ServerSafeHandle handle; + readonly Dictionary<string, IServerCallHandler> callHandlers = new Dictionary<string, IServerCallHandler>(); + + readonly TaskCompletionSource<object> shutdownTcs = new TaskCompletionSource<object>(); + static Server() { GrpcEnvironment.EnsureInitialized(); } @@ -28,8 +35,14 @@ namespace Google.GRPC.Core // TODO: what is the tag for server shutdown? this.handle = ServerSafeHandle.NewServer(GetCompletionQueue(), IntPtr.Zero); this.newRpcHandler = HandleNewRpc; + this.serverShutdownHandler = HandleServerShutdown; } + // only call before Start(), this will be in server builder in the future. + internal void AddCallHandler(string methodName, IServerCallHandler handler) { + callHandlers.Add(methodName, handler); + } + // only call before Start() public int AddPort(string addr) { return handle.AddPort(addr); } @@ -37,49 +50,57 @@ namespace Google.GRPC.Core public void Start() { handle.Start(); + + // TODO: this basically means the server is single threaded.... + StartHandlingRpcs(); } - public void RunRpc() + /// <summary> + /// Requests and handles single RPC call. + /// </summary> + internal void RunRpc() { AllowOneRpc(); - try { - var rpcInfo = newRpcQueue.Take(); - - Console.WriteLine("Server received RPC " + rpcInfo.Method); - - AsyncCall<byte[], byte[]> asyncCall = new AsyncCall<byte[], byte[]>( - (payload) => payload, (payload) => payload); - - asyncCall.InitializeServer(rpcInfo.Call); + try + { + var rpcInfo = newRpcQueue.Take(); - asyncCall.Accept(GetCompletionQueue()); + Console.WriteLine("Server received RPC " + rpcInfo.Method); - while(true) { - byte[] payload = asyncCall.ReadAsync().Result; - if (payload == null) + IServerCallHandler callHandler; + if (!callHandlers.TryGetValue(rpcInfo.Method, out callHandler)) { - break; - } + callHandler = new NoSuchMethodCallHandler(); + } + callHandler.StartCall(rpcInfo.Method, rpcInfo.Call, GetCompletionQueue()); } - - asyncCall.WriteAsync(new byte[] { }).Wait(); - - // TODO: what should be the details? - asyncCall.WriteStatusAsync(new Status(StatusCode.GRPC_STATUS_OK, "")).Wait(); - - asyncCall.Finished.Wait(); - } catch(Exception e) { + catch(Exception e) + { Console.WriteLine("Exception while handling RPC: " + e); } } - // TODO: implement disposal properly... - public void Shutdown() { - handle.Shutdown(); + /// <summary> + /// Requests server shutdown and when there are no more calls being serviced, + /// cleans up used resources. + /// </summary> + /// <returns>The async.</returns> + public async Task ShutdownAsync() { + handle.ShutdownAndNotify(serverShutdownHandler); + await shutdownTcs.Task; + handle.Dispose(); + } + public void Kill() { + handle.Dispose(); + } - //handle.Dispose(); + private async Task StartHandlingRpcs() { + while (true) + { + await Task.Factory.StartNew(RunRpc); + } } private void AllowOneRpc() @@ -100,6 +121,18 @@ namespace Google.GRPC.Core } } + private void HandleServerShutdown(IntPtr eventPtr) + { + try + { + shutdownTcs.SetResult(null); + } + catch (Exception e) + { + Console.WriteLine("Caught exception in a native handler: " + e); + } + } + private static void AssertCallOk(GRPCCallError callError) { Trace.Assert(callError == GRPCCallError.GRPC_CALL_OK, "Status not GRPC_CALL_OK"); diff --git a/src/csharp/GrpcCore/ServerCallHandler.cs b/src/csharp/GrpcCore/ServerCallHandler.cs new file mode 100644 index 0000000000..08d527a019 --- /dev/null +++ b/src/csharp/GrpcCore/ServerCallHandler.cs @@ -0,0 +1,93 @@ +using System; +using Google.GRPC.Core.Internal; + +namespace Google.GRPC.Core +{ + internal interface IServerCallHandler + { + void StartCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq); + } + + internal class UnaryRequestServerCallHandler<TRequest, TResponse> : IServerCallHandler + { + readonly Method<TRequest, TResponse> method; + readonly UnaryRequestServerMethod<TRequest, TResponse> handler; + + public UnaryRequestServerCallHandler(Method<TRequest, TResponse> method, UnaryRequestServerMethod<TRequest, TResponse> handler) + { + this.method = method; + this.handler = handler; + } + + public void StartCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq) + { + var asyncCall = new AsyncCall<TResponse, TRequest>( + (msg) => method.ResponseMarshaller.Serialize(msg), + (payload) => method.RequestMarshaller.Deserialize(payload)); + + asyncCall.InitializeServer(call); + asyncCall.Accept(cq); + + var request = asyncCall.ReadAsync().Result; + + var responseObserver = new ServerWritingObserver<TResponse, TRequest>(asyncCall); + handler(request, responseObserver); + + asyncCall.Halfclosed.Wait(); + // TODO: wait until writing is finished + + asyncCall.WriteStatusAsync(new Status(StatusCode.GRPC_STATUS_OK, "")).Wait(); + asyncCall.Finished.Wait(); + } + } + + internal class StreamingRequestServerCallHandler<TRequest, TResponse> : IServerCallHandler + { + readonly Method<TRequest, TResponse> method; + readonly StreamingRequestServerMethod<TRequest, TResponse> handler; + + public StreamingRequestServerCallHandler(Method<TRequest, TResponse> method, StreamingRequestServerMethod<TRequest, TResponse> handler) + { + this.method = method; + this.handler = handler; + } + + public void StartCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq) + { + var asyncCall = new AsyncCall<TResponse, TRequest>( + (msg) => method.ResponseMarshaller.Serialize(msg), + (payload) => method.RequestMarshaller.Deserialize(payload)); + + asyncCall.InitializeServer(call); + asyncCall.Accept(cq); + + var responseObserver = new ServerWritingObserver<TResponse, TRequest>(asyncCall); + var requestObserver = handler(responseObserver); + + // feed the requests + asyncCall.StartReadingToStream(requestObserver); + + asyncCall.Halfclosed.Wait(); + + asyncCall.WriteStatusAsync(new Status(StatusCode.GRPC_STATUS_OK, "")).Wait(); + asyncCall.Finished.Wait(); + } + } + + internal class NoSuchMethodCallHandler : IServerCallHandler + { + public void StartCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq) + { + // We don't care about the payload type here. + AsyncCall<byte[], byte[]> asyncCall = new AsyncCall<byte[], byte[]>( + (payload) => payload, (payload) => payload); + + asyncCall.InitializeServer(call); + asyncCall.Accept(cq); + asyncCall.WriteStatusAsync(new Status(StatusCode.GRPC_STATUS_UNIMPLEMENTED, "No such method.")).Wait(); + + asyncCall.Finished.Wait(); + } + } +} + diff --git a/src/csharp/GrpcCore/ServerCalls.cs b/src/csharp/GrpcCore/ServerCalls.cs new file mode 100644 index 0000000000..86c4718932 --- /dev/null +++ b/src/csharp/GrpcCore/ServerCalls.cs @@ -0,0 +1,25 @@ +using System; + +namespace Google.GRPC.Core +{ + // TODO: perhaps add also serverSideStreaming and clientSideStreaming + + public delegate void UnaryRequestServerMethod<TRequest, TResponse> (TRequest request, IObserver<TResponse> responseObserver); + + public delegate IObserver<TRequest> StreamingRequestServerMethod<TRequest, TResponse> (IObserver<TResponse> responseObserver); + + internal static class ServerCalls { + + public static IServerCallHandler UnaryRequestCall<TRequest, TResponse>(Method<TRequest, TResponse> method, UnaryRequestServerMethod<TRequest, TResponse> handler) + { + return new UnaryRequestServerCallHandler<TRequest, TResponse>(method, handler); + } + + public static IServerCallHandler StreamingRequestCall<TRequest, TResponse>(Method<TRequest, TResponse> method, StreamingRequestServerMethod<TRequest, TResponse> handler) + { + return new StreamingRequestServerCallHandler<TRequest, TResponse>(method, handler); + } + + } +} + diff --git a/src/csharp/GrpcCoreTests/ClientServerTest.cs b/src/csharp/GrpcCoreTests/ClientServerTest.cs index 823ee94288..511683b003 100644 --- a/src/csharp/GrpcCoreTests/ClientServerTest.cs +++ b/src/csharp/GrpcCoreTests/ClientServerTest.cs @@ -8,41 +8,48 @@ namespace Google.GRPC.Core.Tests { public class ClientServerTest { - string request = "REQUEST"; string serverAddr = "localhost:" + Utils.PickUnusedPort(); + private Method<string, string> unaryEchoStringMethod = new Method<string, string>( + MethodType.Unary, + "/tests.Test/UnaryEchoString", + new StringMarshaller(), + new StringMarshaller()); + [Test] public void EmptyCall() { Server server = new Server(); + + server.AddCallHandler(unaryEchoStringMethod.Name, + ServerCalls.UnaryRequestCall(unaryEchoStringMethod, HandleUnaryEchoString)); + server.AddPort(serverAddr); server.Start(); - Task.Factory.StartNew( - () => { - server.RunRpc(); - } - ); - using (Channel channel = new Channel(serverAddr)) { - CreateCall(channel); - string response = Calls.BlockingUnaryCall(CreateCall(channel), request, default(CancellationToken)); - Console.WriteLine("Received response: " + response); + var call = CreateUnaryEchoStringCall(channel); + + Assert.AreEqual("ABC", Calls.BlockingUnaryCall(call, "ABC", default(CancellationToken))); + Assert.AreEqual("abcdef", Calls.BlockingUnaryCall(call, "abcdef", default(CancellationToken))); } - server.Shutdown(); + server.ShutdownAsync().Wait(); GrpcEnvironment.Shutdown(); } - private Call<string, string> CreateCall(Channel channel) + private Call<string, string> CreateUnaryEchoStringCall(Channel channel) { - return new Call<string, string>("/tests.Test/EmptyCall", - (s) => System.Text.Encoding.ASCII.GetBytes(s), - (b) => System.Text.Encoding.ASCII.GetString(b), - Timeout.InfiniteTimeSpan, channel); + return new Call<string, string>(unaryEchoStringMethod, channel); + } + + private void HandleUnaryEchoString(string request, IObserver<string> responseObserver) { + responseObserver.OnNext(request); + responseObserver.OnCompleted(); } + } } diff --git a/src/csharp/GrpcCoreTests/ServerTest.cs b/src/csharp/GrpcCoreTests/ServerTest.cs index b34101bbf5..e6de95c336 100644 --- a/src/csharp/GrpcCoreTests/ServerTest.cs +++ b/src/csharp/GrpcCoreTests/ServerTest.cs @@ -12,7 +12,7 @@ namespace Google.GRPC.Core.Tests Server server = new Server(); server.AddPort("localhost:" + Utils.PickUnusedPort()); server.Start(); - server.Shutdown(); + server.ShutdownAsync().Wait(); GrpcEnvironment.Shutdown(); } diff --git a/src/csharp/README.md b/src/csharp/README.md index 75bfb26252..c2f988cc5e 100755 --- a/src/csharp/README.md +++ b/src/csharp/README.md @@ -14,6 +14,34 @@ EXPERIMENTAL ONLY - It is very possible that some parts of the code will be heavily refactored or completely rewritten. + +INSTALLATION AND USAGE +---------------------- + +- Compile and install the gRPC C Core library +``` +make shared_c +sudo make install +``` + +- Prerequisites for development: Mono framework, MonoDevelop (IDE) +``` +sudo apt-get install mono-devel +sudo apt-get install monodevelop monodevelop-nunit +sudo apt-get install nunit nunit-console +``` + +- Use MonoDevelop to open the solution Grpc.sln (you can also run unit tests + from there). + +- After building the solution with MonoDevelop, you can use + nunit-console to run the unit tests (currently only running one by + one will make them pass. + +``` +nunit-console GrpcCoreTests.dll +``` + CONTENTS -------- |