diff options
-rw-r--r-- | src/php/ext/grpc/call.c | 8 | ||||
-rw-r--r-- | src/php/ext/grpc/call_credentials.c | 12 | ||||
-rw-r--r-- | src/php/ext/grpc/channel.c | 290 | ||||
-rwxr-xr-x | src/php/ext/grpc/channel.h | 27 | ||||
-rw-r--r-- | src/php/ext/grpc/channel_credentials.c | 32 | ||||
-rwxr-xr-x | src/php/ext/grpc/channel_credentials.h | 2 | ||||
-rw-r--r-- | src/php/ext/grpc/php7_wrapper.h | 28 | ||||
-rw-r--r-- | src/php/ext/grpc/php_grpc.c | 2 | ||||
-rw-r--r-- | src/php/ext/grpc/php_grpc.h | 4 | ||||
-rw-r--r-- | src/php/tests/unit_tests/CallTest.php | 3 | ||||
-rw-r--r-- | src/php/tests/unit_tests/ChannelTest.php | 457 | ||||
-rw-r--r-- | src/php/tests/unit_tests/EndToEndTest.php | 7 | ||||
-rw-r--r-- | src/php/tests/unit_tests/SecureEndToEndTest.php | 3 |
13 files changed, 800 insertions, 75 deletions
diff --git a/src/php/ext/grpc/call.c b/src/php/ext/grpc/call.c index 2f67e5cee7..c4997f720d 100644 --- a/src/php/ext/grpc/call.c +++ b/src/php/ext/grpc/call.c @@ -214,10 +214,12 @@ PHP_METHOD(Call, __construct) { return; } wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(channel_obj); - if (channel->wrapped == NULL) { + gpr_mu_lock(&channel->wrapper->mu); + if (channel->wrapper->wrapped == NULL) { zend_throw_exception(spl_ce_InvalidArgumentException, "Call cannot be constructed from a closed Channel", 1 TSRMLS_CC); + gpr_mu_unlock(&channel->wrapper->mu); return; } add_property_zval(getThis(), "channel", channel_obj); @@ -226,13 +228,15 @@ PHP_METHOD(Call, __construct) { grpc_slice host_slice = host_override != NULL ? grpc_slice_from_copied_string(host_override) : grpc_empty_slice(); call->wrapped = - grpc_channel_create_call(channel->wrapped, NULL, GRPC_PROPAGATE_DEFAULTS, + grpc_channel_create_call(channel->wrapper->wrapped, NULL, + GRPC_PROPAGATE_DEFAULTS, completion_queue, method_slice, host_override != NULL ? &host_slice : NULL, deadline->wrapped, NULL); grpc_slice_unref(method_slice); grpc_slice_unref(host_slice); call->owned = true; + gpr_mu_unlock(&channel->wrapper->mu); } /** diff --git a/src/php/ext/grpc/call_credentials.c b/src/php/ext/grpc/call_credentials.c index a990206c08..f46091d709 100644 --- a/src/php/ext/grpc/call_credentials.c +++ b/src/php/ext/grpc/call_credentials.c @@ -109,8 +109,8 @@ PHP_METHOD(CallCredentials, createFromPlugin) { zend_fcall_info *fci; zend_fcall_info_cache *fci_cache; - fci = (zend_fcall_info *)emalloc(sizeof(zend_fcall_info)); - fci_cache = (zend_fcall_info_cache *)emalloc(sizeof(zend_fcall_info_cache)); + fci = (zend_fcall_info *)malloc(sizeof(zend_fcall_info)); + fci_cache = (zend_fcall_info_cache *)malloc(sizeof(zend_fcall_info_cache)); memset(fci, 0, sizeof(zend_fcall_info)); memset(fci_cache, 0, sizeof(zend_fcall_info_cache)); @@ -123,7 +123,7 @@ PHP_METHOD(CallCredentials, createFromPlugin) { } plugin_state *state; - state = (plugin_state *)emalloc(sizeof(plugin_state)); + state = (plugin_state *)malloc(sizeof(plugin_state)); memset(state, 0, sizeof(plugin_state)); /* save the user provided PHP callback function */ @@ -210,13 +210,13 @@ void plugin_get_metadata(void *ptr, grpc_auth_metadata_context context, /* Cleanup function for plugin creds API */ void plugin_destroy_state(void *ptr) { plugin_state *state = (plugin_state *)ptr; - efree(state->fci); - efree(state->fci_cache); + free(state->fci); + free(state->fci_cache); #if PHP_MAJOR_VERSION < 7 PHP_GRPC_FREE_STD_ZVAL(state->fci->params); PHP_GRPC_FREE_STD_ZVAL(state->fci->retval); #endif - efree(state); + free(state); } ZEND_BEGIN_ARG_INFO_EX(arginfo_createComposite, 0, 0, 2) diff --git a/src/php/ext/grpc/channel.c b/src/php/ext/grpc/channel.c index 6c432d2818..f1187e8722 100644 --- a/src/php/ext/grpc/channel.c +++ b/src/php/ext/grpc/channel.c @@ -25,6 +25,13 @@ #include <php.h> #include <php_ini.h> #include <ext/standard/info.h> +#include <ext/standard/php_var.h> +#include <ext/standard/sha1.h> +#if PHP_MAJOR_VERSION < 7 +#include <ext/standard/php_smart_str.h> +#else +#include <zend_smart_str.h> +#endif #include <ext/spl/spl_exceptions.h> #include "php_grpc.h" @@ -44,11 +51,25 @@ zend_class_entry *grpc_ce_channel; #if PHP_MAJOR_VERSION >= 7 static zend_object_handlers channel_ce_handlers; #endif +static gpr_mu global_persistent_list_mu; +int le_plink; /* Frees and destroys an instance of wrapped_grpc_channel */ PHP_GRPC_FREE_WRAPPED_FUNC_START(wrapped_grpc_channel) - if (p->wrapped != NULL) { - grpc_channel_destroy(p->wrapped); + if (p->wrapper != NULL) { + gpr_mu_lock(&p->wrapper->mu); + if (p->wrapper->wrapped != NULL) { + php_grpc_zend_resource *rsrc; + php_grpc_int key_len = strlen(p->wrapper->key); + // only destroy the channel here if not found in the persistent list + gpr_mu_lock(&global_persistent_list_mu); + if (!(PHP_GRPC_PERSISTENT_LIST_FIND(&EG(persistent_list), p->wrapper->key, + key_len, rsrc))) { + grpc_channel_destroy(p->wrapper->wrapped); + } + gpr_mu_unlock(&global_persistent_list_mu); + } + gpr_mu_unlock(&p->wrapper->mu); } PHP_GRPC_FREE_WRAPPED_FUNC_END() @@ -62,15 +83,15 @@ php_grpc_zend_object create_wrapped_grpc_channel(zend_class_entry *class_type PHP_GRPC_FREE_CLASS_OBJECT(wrapped_grpc_channel, channel_ce_handlers); } -void php_grpc_read_args_array(zval *args_array, - grpc_channel_args *args TSRMLS_DC) { +int php_grpc_read_args_array(zval *args_array, + grpc_channel_args *args TSRMLS_DC) { HashTable *array_hash; int args_index; array_hash = Z_ARRVAL_P(args_array); if (!array_hash) { zend_throw_exception(spl_ce_InvalidArgumentException, "array_hash is NULL", 1 TSRMLS_CC); - return; + return FAILURE; } args->num_args = zend_hash_num_elements(array_hash); args->args = ecalloc(args->num_args, sizeof(grpc_arg)); @@ -84,7 +105,7 @@ void php_grpc_read_args_array(zval *args_array, if (key_type != HASH_KEY_IS_STRING) { zend_throw_exception(spl_ce_InvalidArgumentException, "args keys must be strings", 1 TSRMLS_CC); - return; + return FAILURE; } args->args[args_index].key = key; switch (Z_TYPE_P(data)) { @@ -99,16 +120,78 @@ void php_grpc_read_args_array(zval *args_array, default: zend_throw_exception(spl_ce_InvalidArgumentException, "args values must be int or string", 1 TSRMLS_CC); - return; + return FAILURE; } args_index++; PHP_GRPC_HASH_FOREACH_END() + return SUCCESS; +} + +void generate_sha1_str(char *sha1str, char *str, php_grpc_int len) { + PHP_SHA1_CTX context; + unsigned char digest[20]; + sha1str[0] = '\0'; + PHP_SHA1Init(&context); + PHP_GRPC_SHA1Update(&context, str, len); + PHP_SHA1Final(digest, &context); + make_sha1_digest(sha1str, digest); +} + +void create_channel( + wrapped_grpc_channel *channel, + char *target, + grpc_channel_args args, + wrapped_grpc_channel_credentials *creds) { + if (creds == NULL) { + channel->wrapper->wrapped = grpc_insecure_channel_create(target, &args, + NULL); + } else { + channel->wrapper->wrapped = + grpc_secure_channel_create(creds->wrapped, target, &args, NULL); + } + efree(args.args); +} + +void create_and_add_channel_to_persistent_list( + wrapped_grpc_channel *channel, + char *target, + grpc_channel_args args, + wrapped_grpc_channel_credentials *creds, + char *key, + php_grpc_int key_len) { + php_grpc_zend_resource new_rsrc; + channel_persistent_le_t *le; + // this links each persistent list entry to a destructor + new_rsrc.type = le_plink; + le = malloc(sizeof(channel_persistent_le_t)); + + create_channel(channel, target, args, creds); + + le->channel = channel->wrapper; + new_rsrc.ptr = le; + gpr_mu_lock(&global_persistent_list_mu); + PHP_GRPC_PERSISTENT_LIST_UPDATE(&EG(persistent_list), key, key_len, + (void *)&new_rsrc); + gpr_mu_unlock(&global_persistent_list_mu); } /** - * Construct an instance of the Channel class. If the $args array contains a - * "credentials" key mapping to a ChannelCredentials object, a secure channel - * will be created with those credentials. + * Construct an instance of the Channel class. + * + * By default, the underlying grpc_channel is "persistent". That is, given + * the same set of parameters passed to the constructor, the same underlying + * grpc_channel will be returned. + * + * If the $args array contains a "credentials" key mapping to a + * ChannelCredentials object, a secure channel will be created with those + * credentials. + * + * If the $args array contains a "force_new" key mapping to a boolean value + * of "true", a new underlying grpc_channel will be created regardless. If + * there are any opened channels on the same hostname, user must manually + * call close() on those dangling channels before the end of the PHP + * script. + * * @param string $target The hostname to associate with this channel * @param array $args_array The arguments to pass to the Channel */ @@ -121,6 +204,9 @@ PHP_METHOD(Channel, __construct) { grpc_channel_args args; HashTable *array_hash; wrapped_grpc_channel_credentials *creds = NULL; + php_grpc_zend_resource *rsrc; + bool force_new = false; + zval *force_new_obj = NULL; /* "sa" == 1 string, 1 array */ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", &target, @@ -131,7 +217,7 @@ PHP_METHOD(Channel, __construct) { } array_hash = Z_ARRVAL_P(args_array); if (php_grpc_zend_hash_find(array_hash, "credentials", sizeof("credentials"), - (void **)&creds_obj) == SUCCESS) { + (void **)&creds_obj) == SUCCESS) { if (Z_TYPE_P(creds_obj) == IS_NULL) { creds = NULL; php_grpc_zend_hash_del(array_hash, "credentials", sizeof("credentials")); @@ -146,14 +232,82 @@ PHP_METHOD(Channel, __construct) { php_grpc_zend_hash_del(array_hash, "credentials", sizeof("credentials")); } } - php_grpc_read_args_array(args_array, &args TSRMLS_CC); - if (creds == NULL) { - channel->wrapped = grpc_insecure_channel_create(target, &args, NULL); + if (php_grpc_zend_hash_find(array_hash, "force_new", sizeof("force_new"), + (void **)&force_new_obj) == SUCCESS) { + if (PHP_GRPC_BVAL_IS_TRUE(force_new_obj)) { + force_new = true; + } + php_grpc_zend_hash_del(array_hash, "force_new", sizeof("force_new")); + } + + // parse the rest of the channel args array + if (php_grpc_read_args_array(args_array, &args TSRMLS_CC) == FAILURE) { + return; + } + + // Construct a hashkey for the persistent channel + // Currently, the hashkey contains 3 parts: + // 1. hostname + // 2. hash value of the channel args array (excluding "credentials" + // and "force_new") + // 3. (optional) hash value of the ChannelCredentials object + php_serialize_data_t var_hash; + smart_str buf = {0}; + PHP_VAR_SERIALIZE_INIT(var_hash); + PHP_GRPC_VAR_SERIALIZE(&buf, args_array, &var_hash); + PHP_VAR_SERIALIZE_DESTROY(var_hash); + + char sha1str[41]; + generate_sha1_str(sha1str, PHP_GRPC_SERIALIZED_BUF_STR(buf), + PHP_GRPC_SERIALIZED_BUF_LEN(buf)); + + php_grpc_int key_len = target_length + strlen(sha1str); + if (creds != NULL && creds->hashstr != NULL) { + key_len += strlen(creds->hashstr); + } + char *key = malloc(key_len + 1); + strcpy(key, target); + strcat(key, sha1str); + if (creds != NULL && creds->hashstr != NULL) { + strcat(key, creds->hashstr); + } + channel->wrapper = malloc(sizeof(grpc_channel_wrapper)); + channel->wrapper->key = key; + channel->wrapper->target = target; + channel->wrapper->args_hashstr = sha1str; + if (creds != NULL && creds->hashstr != NULL) { + channel->wrapper->creds_hashstr = creds->hashstr; + } + gpr_mu_init(&channel->wrapper->mu); + smart_str_free(&buf); + + if (force_new) { + php_grpc_delete_persistent_list_entry(key, key_len TSRMLS_CC); + } + + if (creds != NULL && creds->has_call_creds) { + // If the ChannelCredentials object was composed with a CallCredentials + // object, there is no way we can tell them apart. Do NOT persist + // them. They should be individually destroyed. + create_channel(channel, target, args, creds); + } else if (!(PHP_GRPC_PERSISTENT_LIST_FIND(&EG(persistent_list), key, + key_len, rsrc))) { + create_and_add_channel_to_persistent_list( + channel, target, args, creds, key, key_len); } else { - channel->wrapped = - grpc_secure_channel_create(creds->wrapped, target, &args, NULL); + // Found a previously stored channel in the persistent list + channel_persistent_le_t *le = (channel_persistent_le_t *)rsrc->ptr; + if (strcmp(target, le->channel->target) != 0 || + strcmp(sha1str, le->channel->args_hashstr) != 0 || + (creds != NULL && creds->hashstr != NULL && + strcmp(creds->hashstr, le->channel->creds_hashstr) != 0)) { + // somehow hash collision + create_and_add_channel_to_persistent_list( + channel, target, args, creds, key, key_len); + } else { + channel->wrapper = le->channel; + } } - efree(args.args); } /** @@ -162,7 +316,16 @@ PHP_METHOD(Channel, __construct) { */ PHP_METHOD(Channel, getTarget) { wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis()); - PHP_GRPC_RETURN_STRING(grpc_channel_get_target(channel->wrapped), 1); + gpr_mu_lock(&channel->wrapper->mu); + if (channel->wrapper->wrapped == NULL) { + zend_throw_exception(spl_ce_RuntimeException, + "Channel already closed", 1 TSRMLS_CC); + gpr_mu_unlock(&channel->wrapper->mu); + return; + } + char *target = grpc_channel_get_target(channel->wrapper->wrapped); + gpr_mu_unlock(&channel->wrapper->mu); + PHP_GRPC_RETURN_STRING(target, 1); } /** @@ -172,6 +335,14 @@ PHP_METHOD(Channel, getTarget) { */ PHP_METHOD(Channel, getConnectivityState) { wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis()); + gpr_mu_lock(&channel->wrapper->mu); + if (channel->wrapper->wrapped == NULL) { + zend_throw_exception(spl_ce_RuntimeException, + "Channel already closed", 1 TSRMLS_CC); + gpr_mu_unlock(&channel->wrapper->mu); + return; + } + bool try_to_connect = false; /* "|b" == 1 optional bool */ @@ -179,10 +350,18 @@ PHP_METHOD(Channel, getConnectivityState) { == FAILURE) { zend_throw_exception(spl_ce_InvalidArgumentException, "getConnectivityState expects a bool", 1 TSRMLS_CC); + gpr_mu_unlock(&channel->wrapper->mu); return; } - RETURN_LONG(grpc_channel_check_connectivity_state(channel->wrapped, - (int)try_to_connect)); + int state = grpc_channel_check_connectivity_state(channel->wrapper->wrapped, + (int)try_to_connect); + // this can happen if another shared Channel object close the underlying + // channel + if (state == GRPC_CHANNEL_SHUTDOWN) { + channel->wrapper->wrapped = NULL; + } + gpr_mu_unlock(&channel->wrapper->mu); + RETURN_LONG(state); } /** @@ -194,25 +373,37 @@ PHP_METHOD(Channel, getConnectivityState) { */ PHP_METHOD(Channel, watchConnectivityState) { wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis()); + gpr_mu_lock(&channel->wrapper->mu); + if (channel->wrapper->wrapped == NULL) { + zend_throw_exception(spl_ce_RuntimeException, + "Channel already closed", 1 TSRMLS_CC); + gpr_mu_unlock(&channel->wrapper->mu); + return; + } + php_grpc_long last_state; zval *deadline_obj; /* "lO" == 1 long 1 object */ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lO", - &last_state, &deadline_obj, grpc_ce_timeval) == FAILURE) { + &last_state, &deadline_obj, + grpc_ce_timeval) == FAILURE) { zend_throw_exception(spl_ce_InvalidArgumentException, - "watchConnectivityState expects 1 long 1 timeval", 1 TSRMLS_CC); + "watchConnectivityState expects 1 long 1 timeval", + 1 TSRMLS_CC); + gpr_mu_unlock(&channel->wrapper->mu); return; } wrapped_grpc_timeval *deadline = Z_WRAPPED_GRPC_TIMEVAL_P(deadline_obj); - grpc_channel_watch_connectivity_state(channel->wrapped, + grpc_channel_watch_connectivity_state(channel->wrapper->wrapped, (grpc_connectivity_state)last_state, deadline->wrapped, completion_queue, NULL); grpc_event event = - grpc_completion_queue_pluck(completion_queue, NULL, - gpr_inf_future(GPR_CLOCK_REALTIME), NULL); + grpc_completion_queue_pluck(completion_queue, NULL, + gpr_inf_future(GPR_CLOCK_REALTIME), NULL); + gpr_mu_unlock(&channel->wrapper->mu); RETURN_BOOL(event.success); } @@ -222,10 +413,48 @@ PHP_METHOD(Channel, watchConnectivityState) { */ PHP_METHOD(Channel, close) { wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis()); - if (channel->wrapped != NULL) { - grpc_channel_destroy(channel->wrapped); - channel->wrapped = NULL; + gpr_mu_lock(&channel->wrapper->mu); + if (channel->wrapper->wrapped != NULL) { + grpc_channel_destroy(channel->wrapper->wrapped); + channel->wrapper->wrapped = NULL; + } + + php_grpc_delete_persistent_list_entry(channel->wrapper->key, + strlen(channel->wrapper->key) + TSRMLS_CC); + gpr_mu_unlock(&channel->wrapper->mu); +} + +// Delete an entry from the persistent list +// Note: this does not destroy or close the underlying grpc_channel +void php_grpc_delete_persistent_list_entry(char *key, php_grpc_int key_len + TSRMLS_DC) { + php_grpc_zend_resource *rsrc; + gpr_mu_lock(&global_persistent_list_mu); + if (PHP_GRPC_PERSISTENT_LIST_FIND(&EG(persistent_list), key, + key_len, rsrc)) { + channel_persistent_le_t *le; + le = (channel_persistent_le_t *)rsrc->ptr; + le->channel = NULL; + php_grpc_zend_hash_del(&EG(persistent_list), key, key_len+1); + } + gpr_mu_unlock(&global_persistent_list_mu); +} + +// A destructor associated with each list entry from the persistent list +static void php_grpc_channel_plink_dtor(php_grpc_zend_resource *rsrc + TSRMLS_DC) { + channel_persistent_le_t *le = (channel_persistent_le_t *)rsrc->ptr; + if (le->channel != NULL) { + gpr_mu_lock(&le->channel->mu); + if (le->channel->wrapped != NULL) { + grpc_channel_destroy(le->channel->wrapped); + free(le->channel->key); + free(le->channel); + } + gpr_mu_unlock(&le->channel->mu); } + free(le); } ZEND_BEGIN_ARG_INFO_EX(arginfo_construct, 0, 0, 2) @@ -262,10 +491,13 @@ static zend_function_entry channel_methods[] = { PHP_FE_END }; -void grpc_init_channel(TSRMLS_D) { +GRPC_STARTUP_FUNCTION(channel) { zend_class_entry ce; INIT_CLASS_ENTRY(ce, "Grpc\\Channel", channel_methods); ce.create_object = create_wrapped_grpc_channel; grpc_ce_channel = zend_register_internal_class(&ce TSRMLS_CC); + le_plink = zend_register_list_destructors_ex( + NULL, php_grpc_channel_plink_dtor, "Persistent Channel", module_number); PHP_GRPC_INIT_HANDLER(wrapped_grpc_channel, channel_ce_handlers); + return SUCCESS; } diff --git a/src/php/ext/grpc/channel.h b/src/php/ext/grpc/channel.h index 45c9744135..69adc4782c 100755 --- a/src/php/ext/grpc/channel.h +++ b/src/php/ext/grpc/channel.h @@ -33,9 +33,18 @@ /* Class entry for the PHP Channel class */ extern zend_class_entry *grpc_ce_channel; +typedef struct _grpc_channel_wrapper { + grpc_channel *wrapped; + char *key; + char *target; + char *args_hashstr; + char *creds_hashstr; + gpr_mu mu; +} grpc_channel_wrapper; + /* Wrapper struct for grpc_channel that can be associated with a PHP object */ PHP_GRPC_WRAP_OBJECT_START(wrapped_grpc_channel) - grpc_channel *wrapped; + grpc_channel_wrapper *wrapper; PHP_GRPC_WRAP_OBJECT_END(wrapped_grpc_channel) #if PHP_MAJOR_VERSION < 7 @@ -57,10 +66,20 @@ static inline wrapped_grpc_channel #endif /* PHP_MAJOR_VERSION */ /* Initializes the Channel class */ -void grpc_init_channel(TSRMLS_D); +GRPC_STARTUP_FUNCTION(channel); /* Iterates through a PHP array and populates args with the contents */ -void php_grpc_read_args_array(zval *args_array, grpc_channel_args *args - TSRMLS_DC); +int php_grpc_read_args_array(zval *args_array, grpc_channel_args *args + TSRMLS_DC); + +void generate_sha1_str(char *sha1str, char *str, php_grpc_int len); + +void php_grpc_delete_persistent_list_entry(char *key, php_grpc_int key_len + TSRMLS_DC); + +typedef struct _channel_persistent_le { + grpc_channel_wrapper *channel; +} channel_persistent_le_t; + #endif /* NET_GRPC_PHP_GRPC_CHANNEL_H_ */ diff --git a/src/php/ext/grpc/channel_credentials.c b/src/php/ext/grpc/channel_credentials.c index 40629c8b00..19e1cefb6f 100644 --- a/src/php/ext/grpc/channel_credentials.c +++ b/src/php/ext/grpc/channel_credentials.c @@ -26,7 +26,9 @@ #include <php.h> #include <php_ini.h> #include <ext/standard/info.h> +#include <ext/standard/sha1.h> #include <ext/spl/spl_exceptions.h> +#include "channel.h" #include "php_grpc.h" #include <zend_exceptions.h> @@ -69,14 +71,17 @@ php_grpc_zend_object create_wrapped_grpc_channel_credentials( channel_credentials_ce_handlers); } -zval *grpc_php_wrap_channel_credentials(grpc_channel_credentials - *wrapped TSRMLS_DC) { +zval *grpc_php_wrap_channel_credentials(grpc_channel_credentials *wrapped, + char *hashstr, + zend_bool has_call_creds TSRMLS_DC) { zval *credentials_object; PHP_GRPC_MAKE_STD_ZVAL(credentials_object); object_init_ex(credentials_object, grpc_ce_channel_credentials); wrapped_grpc_channel_credentials *credentials = Z_WRAPPED_GRPC_CHANNEL_CREDS_P(credentials_object); credentials->wrapped = wrapped; + credentials->hashstr = hashstr; + credentials->has_call_creds = has_call_creds; return credentials_object; } @@ -106,7 +111,8 @@ PHP_METHOD(ChannelCredentials, setDefaultRootsPem) { */ PHP_METHOD(ChannelCredentials, createDefault) { grpc_channel_credentials *creds = grpc_google_default_credentials_create(); - zval *creds_object = grpc_php_wrap_channel_credentials(creds TSRMLS_CC); + zval *creds_object = grpc_php_wrap_channel_credentials(creds, NULL, false + TSRMLS_CC); RETURN_DESTROY_ZVAL(creds_object); } @@ -140,10 +146,24 @@ PHP_METHOD(ChannelCredentials, createSsl) { "createSsl expects 3 optional strings", 1 TSRMLS_CC); return; } + + php_grpc_int hashkey_len = root_certs_length + cert_chain_length; + char hashkey[hashkey_len]; + if (root_certs_length > 0) { + strcpy(hashkey, pem_root_certs); + } + if (cert_chain_length > 0) { + strcpy(hashkey, pem_key_cert_pair.cert_chain); + } + + char *hashstr = malloc(41); + generate_sha1_str(hashstr, hashkey, hashkey_len); + grpc_channel_credentials *creds = grpc_ssl_credentials_create( pem_root_certs, pem_key_cert_pair.private_key == NULL ? NULL : &pem_key_cert_pair, NULL); - zval *creds_object = grpc_php_wrap_channel_credentials(creds TSRMLS_CC); + zval *creds_object = grpc_php_wrap_channel_credentials(creds, hashstr, false + TSRMLS_CC); RETURN_DESTROY_ZVAL(creds_object); } @@ -172,7 +192,9 @@ PHP_METHOD(ChannelCredentials, createComposite) { grpc_channel_credentials *creds = grpc_composite_channel_credentials_create(cred1->wrapped, cred2->wrapped, NULL); - zval *creds_object = grpc_php_wrap_channel_credentials(creds TSRMLS_CC); + zval *creds_object = + grpc_php_wrap_channel_credentials(creds, cred1->hashstr, true + TSRMLS_CC); RETURN_DESTROY_ZVAL(creds_object); } diff --git a/src/php/ext/grpc/channel_credentials.h b/src/php/ext/grpc/channel_credentials.h index 28c7f2c1d3..357d732642 100755 --- a/src/php/ext/grpc/channel_credentials.h +++ b/src/php/ext/grpc/channel_credentials.h @@ -38,6 +38,8 @@ extern zend_class_entry *grpc_ce_channel_credentials; * with a PHP object */ PHP_GRPC_WRAP_OBJECT_START(wrapped_grpc_channel_credentials) grpc_channel_credentials *wrapped; + char *hashstr; + zend_bool has_call_creds; PHP_GRPC_WRAP_OBJECT_END(wrapped_grpc_channel_credentials) #if PHP_MAJOR_VERSION < 7 diff --git a/src/php/ext/grpc/php7_wrapper.h b/src/php/ext/grpc/php7_wrapper.h index d4b4c262a7..96091f9dad 100644 --- a/src/php/ext/grpc/php7_wrapper.h +++ b/src/php/ext/grpc/php7_wrapper.h @@ -113,6 +113,20 @@ static inline int php_grpc_zend_hash_find(HashTable *ht, char *key, int len, } #define php_grpc_zend_hash_del zend_hash_del +#define php_grpc_zend_resource zend_rsrc_list_entry + +#define PHP_GRPC_BVAL_IS_TRUE(zv) Z_LVAL_P(zv) +#define PHP_GRPC_VAR_SERIALIZE(buf, zv, hash) \ + php_var_serialize(buf, &zv, hash TSRMLS_CC) +#define PHP_GRPC_SERIALIZED_BUF_STR(buf) buf.c +#define PHP_GRPC_SERIALIZED_BUF_LEN(buf) buf.len +#define PHP_GRPC_SHA1Update(cxt, str, len) \ + PHP_SHA1Update(cxt, (const unsigned char *)str, len) +#define PHP_GRPC_PERSISTENT_LIST_FIND(plist, key, len, rsrc) \ + zend_hash_find(plist, key, len+1, (void **)&rsrc) != FAILURE +#define PHP_GRPC_PERSISTENT_LIST_UPDATE(plist, key, len, rsrc) \ + zend_hash_update(plist, key, len+1, rsrc, sizeof(php_grpc_zend_resource), \ + NULL) #define PHP_GRPC_GET_CLASS_ENTRY(object) zend_get_class_entry(object TSRMLS_CC) @@ -200,6 +214,20 @@ static inline int php_grpc_zend_hash_find(HashTable *ht, char *key, int len, static inline int php_grpc_zend_hash_del(HashTable *ht, char *key, int len) { return zend_hash_str_del(ht, key, len - 1); } +#define php_grpc_zend_resource zend_resource + +#define PHP_GRPC_BVAL_IS_TRUE(zv) Z_TYPE_P(zv) == IS_TRUE +#define PHP_GRPC_VAR_SERIALIZE(buf, zv, hash) \ + php_var_serialize(buf, zv, hash) +#define PHP_GRPC_SERIALIZED_BUF_STR(buf) ZSTR_VAL(buf.s) +#define PHP_GRPC_SERIALIZED_BUF_LEN(buf) ZSTR_LEN(buf.s) +#define PHP_GRPC_SHA1Update(cxt, str, len) \ + PHP_SHA1Update(cxt, (unsigned char *)str, len) +#define PHP_GRPC_PERSISTENT_LIST_FIND(plist, key, len, rsrc) \ + (rsrc = zend_hash_str_find_ptr(plist, key, len)) != NULL +#define PHP_GRPC_PERSISTENT_LIST_UPDATE(plist, key, len, rsrc) \ + zend_hash_str_update_mem(plist, key, len, rsrc, \ + sizeof(php_grpc_zend_resource)) #define PHP_GRPC_GET_CLASS_ENTRY(object) Z_OBJ_P(object)->ce diff --git a/src/php/ext/grpc/php_grpc.c b/src/php/ext/grpc/php_grpc.c index 281b9e6aba..a96daf7d9b 100644 --- a/src/php/ext/grpc/php_grpc.c +++ b/src/php/ext/grpc/php_grpc.c @@ -221,7 +221,7 @@ PHP_MINIT_FUNCTION(grpc) { CONST_CS | CONST_PERSISTENT); grpc_init_call(TSRMLS_C); - grpc_init_channel(TSRMLS_C); + GRPC_STARTUP(channel); grpc_init_server(TSRMLS_C); grpc_init_timeval(TSRMLS_C); grpc_init_channel_credentials(TSRMLS_C); diff --git a/src/php/ext/grpc/php_grpc.h b/src/php/ext/grpc/php_grpc.h index ed846fdba4..32329178dc 100644 --- a/src/php/ext/grpc/php_grpc.h +++ b/src/php/ext/grpc/php_grpc.h @@ -74,4 +74,8 @@ ZEND_END_MODULE_GLOBALS(grpc) #define GRPC_G(v) (grpc_globals.v) #endif +#define GRPC_STARTUP_FUNCTION(module) ZEND_MINIT_FUNCTION(grpc_##module) +#define GRPC_STARTUP(module) \ + ZEND_MODULE_STARTUP_N(grpc_##module)(INIT_FUNC_ARGS_PASSTHRU) + #endif /* PHP_GRPC_H */ diff --git a/src/php/tests/unit_tests/CallTest.php b/src/php/tests/unit_tests/CallTest.php index 3270e73f82..c5e1890a98 100644 --- a/src/php/tests/unit_tests/CallTest.php +++ b/src/php/tests/unit_tests/CallTest.php @@ -37,8 +37,7 @@ class CallTest extends PHPUnit_Framework_TestCase public function tearDown() { - unset($this->call); - unset($this->channel); + $this->channel->close(); } public function testConstructor() diff --git a/src/php/tests/unit_tests/ChannelTest.php b/src/php/tests/unit_tests/ChannelTest.php index 34e6185031..400df0fb66 100644 --- a/src/php/tests/unit_tests/ChannelTest.php +++ b/src/php/tests/unit_tests/ChannelTest.php @@ -25,17 +25,15 @@ class ChannelTest extends PHPUnit_Framework_TestCase public function tearDown() { - unset($this->channel); + if (!empty($this->channel)) { + $this->channel->close(); + } } public function testInsecureCredentials() { - $this->channel = new Grpc\Channel( - 'localhost:0', - [ - 'credentials' => Grpc\ChannelCredentials::createInsecure(), - ] - ); + $this->channel = new Grpc\Channel('localhost:0', + ['credentials' => Grpc\ChannelCredentials::createInsecure()]); $this->assertSame('Grpc\Channel', get_class($this->channel)); } @@ -111,7 +109,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase */ public function testInvalidConstructorWith() { - $this->channel = new Grpc\Channel('localhost', 'invalid'); + $this->channel = new Grpc\Channel('localhost:0', 'invalid'); $this->assertNull($this->channel); } @@ -120,12 +118,8 @@ class ChannelTest extends PHPUnit_Framework_TestCase */ public function testInvalidCredentials() { - $this->channel = new Grpc\Channel( - 'localhost:0', - [ - 'credentials' => new Grpc\Timeval(100), - ] - ); + $this->channel = new Grpc\Channel('localhost:0', + ['credentials' => new Grpc\Timeval(100)]); } /** @@ -133,12 +127,8 @@ class ChannelTest extends PHPUnit_Framework_TestCase */ public function testInvalidOptionsArray() { - $this->channel = new Grpc\Channel( - 'localhost:0', - [ - 'abc' => [], - ] - ); + $this->channel = new Grpc\Channel('localhost:0', + ['abc' => []]); } /** @@ -170,4 +160,431 @@ class ChannelTest extends PHPUnit_Framework_TestCase ['credentials' => Grpc\ChannelCredentials::createInsecure()]); $this->channel->watchConnectivityState(1, 'hi'); } + + + public function assertConnecting($state) { + $this->assertTrue($state == GRPC\CHANNEL_CONNECTING || + $state == GRPC\CHANNEL_TRANSIENT_FAILURE); + } + + public function waitUntilNotIdle($channel) { + for ($i = 0; $i < 10; $i++) { + $now = Grpc\Timeval::now(); + $deadline = $now->add(new Grpc\Timeval(1000)); + if ($channel->watchConnectivityState(GRPC\CHANNEL_IDLE, + $deadline)) { + return true; + } + } + $this->assertTrue(false); + } + + public function testPersistentChannelSameHost() + { + $this->channel1 = new Grpc\Channel('localhost:1', []); + // the underlying grpc channel is the same by default + // when connecting to the same host + $this->channel2 = new Grpc\Channel('localhost:1', []); + + // both channels should be IDLE + $state = $this->channel1->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + // both channels should now be in the CONNECTING state + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertConnecting($state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelDifferentHost() + { + // two different underlying channels because different hostname + $this->channel1 = new Grpc\Channel('localhost:1', []); + $this->channel2 = new Grpc\Channel('localhost:2', []); + + // both channels should be IDLE + $state = $this->channel1->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + // channel1 should now be in the CONNECTING state + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + // channel2 should still be in the IDLE state + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelSameArgs() + { + $this->channel1 = new Grpc\Channel('localhost:1', ["abc" => "def"]); + $this->channel2 = new Grpc\Channel('localhost:1', ["abc" => "def"]); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertConnecting($state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelDifferentArgs() + { + $this->channel1 = new Grpc\Channel('localhost:1', []); + $this->channel2 = new Grpc\Channel('localhost:1', ["abc" => "def"]); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelSameChannelCredentials() + { + $creds1 = Grpc\ChannelCredentials::createSsl(); + $creds2 = Grpc\ChannelCredentials::createSsl(); + + $this->channel1 = new Grpc\Channel('localhost:1', + ["credentials" => $creds1]); + $this->channel2 = new Grpc\Channel('localhost:1', + ["credentials" => $creds2]); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertConnecting($state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelDifferentChannelCredentials() + { + $creds1 = Grpc\ChannelCredentials::createSsl(); + $creds2 = Grpc\ChannelCredentials::createSsl( + file_get_contents(dirname(__FILE__).'/../data/ca.pem')); + + $this->channel1 = new Grpc\Channel('localhost:1', + ["credentials" => $creds1]); + $this->channel2 = new Grpc\Channel('localhost:1', + ["credentials" => $creds2]); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelSameChannelCredentialsRootCerts() + { + $creds1 = Grpc\ChannelCredentials::createSsl( + file_get_contents(dirname(__FILE__).'/../data/ca.pem')); + $creds2 = Grpc\ChannelCredentials::createSsl( + file_get_contents(dirname(__FILE__).'/../data/ca.pem')); + + $this->channel1 = new Grpc\Channel('localhost:1', + ["credentials" => $creds1]); + $this->channel2 = new Grpc\Channel('localhost:1', + ["credentials" => $creds2]); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertConnecting($state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelDifferentSecureChannelCredentials() + { + $creds1 = Grpc\ChannelCredentials::createSsl(); + $creds2 = Grpc\ChannelCredentials::createInsecure(); + + $this->channel1 = new Grpc\Channel('localhost:1', + ["credentials" => $creds1]); + $this->channel2 = new Grpc\Channel('localhost:1', + ["credentials" => $creds2]); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + $this->channel1->close(); + $this->channel2->close(); + } + + /** + * @expectedException RuntimeException + */ + public function testPersistentChannelSharedChannelClose() + { + // same underlying channel + $this->channel1 = new Grpc\Channel('localhost:1', []); + $this->channel2 = new Grpc\Channel('localhost:1', []); + + // close channel1 + $this->channel1->close(); + + // channel2 is now in SHUTDOWN state + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_FATAL_FAILURE, $state); + + // calling it again will result in an exception because the + // channel is already closed + $state = $this->channel2->getConnectivityState(); + } + + public function testPersistentChannelCreateAfterClose() + { + $this->channel1 = new Grpc\Channel('localhost:1', []); + + $this->channel1->close(); + + $this->channel2 = new Grpc\Channel('localhost:1', []); + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + $this->channel2->close(); + } + + public function testPersistentChannelSharedMoreThanTwo() + { + $this->channel1 = new Grpc\Channel('localhost:1', []); + $this->channel2 = new Grpc\Channel('localhost:1', []); + $this->channel3 = new Grpc\Channel('localhost:1', []); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + // all 3 channels should be in CONNECTING state + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel3->getConnectivityState(); + $this->assertConnecting($state); + + $this->channel1->close(); + } + + public function callbackFunc($context) + { + return []; + } + + public function callbackFunc2($context) + { + return ["k1" => "v1"]; + } + + public function testPersistentChannelWithCallCredentials() + { + $creds = Grpc\ChannelCredentials::createSsl(); + $callCreds = Grpc\CallCredentials::createFromPlugin( + [$this, 'callbackFunc']); + $credsWithCallCreds = Grpc\ChannelCredentials::createComposite( + $creds, $callCreds); + + // If a ChannelCredentials object is composed with a + // CallCredentials object, the underlying grpc channel will + // always be created new and NOT persisted. + $this->channel1 = new Grpc\Channel('localhost:1', + ["credentials" => + $credsWithCallCreds]); + $this->channel2 = new Grpc\Channel('localhost:1', + ["credentials" => + $credsWithCallCreds]); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelWithDifferentCallCredentials() + { + $callCreds1 = Grpc\CallCredentials::createFromPlugin( + [$this, 'callbackFunc']); + $callCreds2 = Grpc\CallCredentials::createFromPlugin( + [$this, 'callbackFunc2']); + + $creds1 = Grpc\ChannelCredentials::createSsl(); + $creds2 = Grpc\ChannelCredentials::createComposite( + $creds1, $callCreds1); + $creds3 = Grpc\ChannelCredentials::createComposite( + $creds1, $callCreds2); + + // Similar to the test above, anytime a ChannelCredentials + // object is composed with a CallCredentials object, the + // underlying grpc channel will always be separate and not + // persisted + $this->channel1 = new Grpc\Channel('localhost:1', + ["credentials" => $creds1]); + $this->channel2 = new Grpc\Channel('localhost:1', + ["credentials" => $creds2]); + $this->channel3 = new Grpc\Channel('localhost:1', + ["credentials" => $creds3]); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + $state = $this->channel3->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + $this->channel1->close(); + $this->channel2->close(); + $this->channel3->close(); + } + + public function testPersistentChannelForceNew() + { + $this->channel1 = new Grpc\Channel('localhost:1', []); + // even though all the channel params are the same, channel2 + // has a new and different underlying channel + $this->channel2 = new Grpc\Channel('localhost:1', + ["force_new" => true]); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + // any dangling old connection to the same host must be + // manually closed + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelForceNewOldChannelIdle() + { + + $this->channel1 = new Grpc\Channel('localhost:1', []); + $this->channel2 = new Grpc\Channel('localhost:1', + ["force_new" => true]); + $this->channel3 = new Grpc\Channel('localhost:1', []); + + // try to connect on channel2 + $state = $this->channel2->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel2); + + $state = $this->channel1->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + $state = $this->channel2->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel3->getConnectivityState(); + $this->assertConnecting($state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelForceNewOldChannelClose() + { + + $this->channel1 = new Grpc\Channel('localhost:1', []); + $this->channel2 = new Grpc\Channel('localhost:1', + ["force_new" => true]); + $this->channel3 = new Grpc\Channel('localhost:1', []); + + $this->channel1->close(); + + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + $state = $this->channel3->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + $this->channel2->close(); + $this->channel3->close(); + } + + public function testPersistentChannelForceNewNewChannelClose() + { + + $this->channel1 = new Grpc\Channel('localhost:1', []); + $this->channel2 = new Grpc\Channel('localhost:1', + ["force_new" => true]); + $this->channel3 = new Grpc\Channel('localhost:1', []); + + $this->channel2->close(); + + $state = $this->channel1->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + // can still connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + + $this->channel1->close(); + } } diff --git a/src/php/tests/unit_tests/EndToEndTest.php b/src/php/tests/unit_tests/EndToEndTest.php index 43d54d9ee6..b54f1d87c9 100644 --- a/src/php/tests/unit_tests/EndToEndTest.php +++ b/src/php/tests/unit_tests/EndToEndTest.php @@ -28,8 +28,7 @@ class EndToEndTest extends PHPUnit_Framework_TestCase public function tearDown() { - unset($this->channel); - unset($this->server); + $this->channel->close(); } public function testSimpleRequestBody() @@ -516,7 +515,7 @@ class EndToEndTest extends PHPUnit_Framework_TestCase $this->assertTrue($idle_state == Grpc\CHANNEL_IDLE); $now = Grpc\Timeval::now(); - $delta = new Grpc\Timeval(500000); // should timeout + $delta = new Grpc\Timeval(50000); // should timeout $deadline = $now->add($delta); $this->assertFalse($this->channel->watchConnectivityState( @@ -545,7 +544,7 @@ class EndToEndTest extends PHPUnit_Framework_TestCase $this->assertTrue($idle_state == Grpc\CHANNEL_IDLE); $now = Grpc\Timeval::now(); - $delta = new Grpc\Timeval(100000); + $delta = new Grpc\Timeval(50000); $deadline = $now->add($delta); $this->assertFalse($this->channel->watchConnectivityState( diff --git a/src/php/tests/unit_tests/SecureEndToEndTest.php b/src/php/tests/unit_tests/SecureEndToEndTest.php index 0fecbfb3dd..dff4e878ea 100644 --- a/src/php/tests/unit_tests/SecureEndToEndTest.php +++ b/src/php/tests/unit_tests/SecureEndToEndTest.php @@ -43,8 +43,7 @@ class SecureEndToEndTest extends PHPUnit_Framework_TestCase public function tearDown() { - unset($this->channel); - unset($this->server); + $this->channel->close(); } public function testSimpleRequestBody() |