From 9ef881e1accc55dca39f07f530cfe309c07a43f1 Mon Sep 17 00:00:00 2001 From: ZhouyihaiDing Date: Thu, 12 Apr 2018 16:43:33 -0700 Subject: PHP: chang shared channel behavior PHP: add persistent list upper bounnd check change upper bound from global to each target add ref/unreef; Only delete ref_count=0; make the code cleaner persistent map update after review u2nd pdate after the review --- src/php/bin/run_tests.sh | 6 +- src/php/ext/grpc/call.c | 12 +- src/php/ext/grpc/call.h | 2 + src/php/ext/grpc/channel.c | 438 +++++++++++++----- src/php/ext/grpc/channel.h | 10 +- src/php/ext/grpc/config.m4 | 8 + src/php/ext/grpc/php7_wrapper.h | 6 +- src/php/ext/grpc/php_grpc.c | 3 + src/php/tests/unit_tests/ChannelTest.php | 237 +++++++--- .../PersistentChannelTest.php | 489 +++++++++++++++++++++ 10 files changed, 1015 insertions(+), 196 deletions(-) create mode 100644 src/php/tests/unit_tests/PersistentChannelTests/PersistentChannelTest.php (limited to 'src/php') diff --git a/src/php/bin/run_tests.sh b/src/php/bin/run_tests.sh index b913166ca2..295bcb2430 100755 --- a/src/php/bin/run_tests.sh +++ b/src/php/bin/run_tests.sh @@ -23,4 +23,8 @@ source ./determine_extension_dir.sh # in some jenkins macos machine, somehow the PHP build script can't find libgrpc.dylib export DYLD_LIBRARY_PATH=$root/libs/$CONFIG php $extension_dir -d max_execution_time=300 $(which phpunit) -v --debug \ - ../tests/unit_tests + --exclude-group persistent_list_bound_tests ../tests/unit_tests + +php $extension_dir -d max_execution_time=300 $(which phpunit) -v --debug \ + ../tests/unit_tests/PersistentChannelTests + diff --git a/src/php/ext/grpc/call.c b/src/php/ext/grpc/call.c index b802f04f53..4a647ec7c2 100644 --- a/src/php/ext/grpc/call.c +++ b/src/php/ext/grpc/call.c @@ -230,7 +230,7 @@ PHP_METHOD(Call, __construct) { } wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(channel_obj); gpr_mu_lock(&channel->wrapper->mu); - if (channel->wrapper->wrapped == NULL) { + if (channel->wrapper == NULL || channel->wrapper->wrapped == NULL) { zend_throw_exception(spl_ce_InvalidArgumentException, "Call cannot be constructed from a closed Channel", 1 TSRMLS_CC); @@ -251,6 +251,7 @@ PHP_METHOD(Call, __construct) { grpc_slice_unref(method_slice); grpc_slice_unref(host_slice); call->owned = true; + call->channel = channel; gpr_mu_unlock(&channel->wrapper->mu); } @@ -270,6 +271,15 @@ PHP_METHOD(Call, startBatch) { zval *message_value; zval *message_flags; wrapped_grpc_call *call = Z_WRAPPED_GRPC_CALL_P(getThis()); + if (call->channel) { + // startBatch in gRPC PHP server doesn't have channel in it. + if (call->channel->wrapper == NULL || + call->channel->wrapper->wrapped == NULL) { + zend_throw_exception(spl_ce_RuntimeException, + "startBatch Error. Channel is closed", + 1 TSRMLS_CC); + } + } grpc_op ops[8]; size_t op_num = 0; diff --git a/src/php/ext/grpc/call.h b/src/php/ext/grpc/call.h index 104ac301c1..bb73354c42 100644 --- a/src/php/ext/grpc/call.h +++ b/src/php/ext/grpc/call.h @@ -27,6 +27,7 @@ #include #include #include "php_grpc.h" +#include "channel.h" #include @@ -37,6 +38,7 @@ extern zend_class_entry *grpc_ce_call; PHP_GRPC_WRAP_OBJECT_START(wrapped_grpc_call) bool owned; grpc_call *wrapped; + wrapped_grpc_channel* channel; PHP_GRPC_WRAP_OBJECT_END(wrapped_grpc_call) #if PHP_MAJOR_VERSION < 7 diff --git a/src/php/ext/grpc/channel.c b/src/php/ext/grpc/channel.c index 35adf6b342..6f3acc7afb 100644 --- a/src/php/ext/grpc/channel.c +++ b/src/php/ext/grpc/channel.c @@ -54,47 +54,51 @@ static zend_object_handlers channel_ce_handlers; #endif static gpr_mu global_persistent_list_mu; int le_plink; +int le_bound; extern HashTable grpc_persistent_list; +extern HashTable grpc_target_upper_bound_map; + +void free_grpc_channel_wrapper(grpc_channel_wrapper* channel, bool free_channel) { + if (free_channel) { + grpc_channel_destroy(channel->wrapped); + channel->wrapped = NULL; + } + free(channel->target); + free(channel->args_hashstr); + free(channel->creds_hashstr); + free(channel->key); + channel->target = NULL; + channel->args_hashstr = NULL; + channel->creds_hashstr = NULL; + channel->key = NULL; +} + +void php_grpc_channel_ref(grpc_channel_wrapper* wrapper) { + gpr_mu_lock(&wrapper->mu); + wrapper->ref_count += 1; + gpr_mu_unlock(&wrapper->mu); +} + +void php_grpc_channel_unref(grpc_channel_wrapper* wrapper) { + gpr_mu_lock(&wrapper->mu); + wrapper->ref_count -= 1; + if (wrapper->ref_count == 0) { + free_grpc_channel_wrapper(wrapper, true); + gpr_mu_unlock(&wrapper->mu); + free(wrapper); + wrapper = NULL; + return; + } + gpr_mu_unlock(&wrapper->mu); +} /* Frees and destroys an instance of wrapped_grpc_channel */ PHP_GRPC_FREE_WRAPPED_FUNC_START(wrapped_grpc_channel) // In_persistent_list is used when the user don't close the channel, // In this case, channels not in the list should be freed. - bool in_persistent_list = true; if (p->wrapper != NULL) { - gpr_mu_lock(&p->wrapper->mu); - if (p->wrapper->wrapped != NULL) { - if (p->wrapper->is_valid) { - 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(&grpc_persistent_list, p->wrapper->key, - key_len, rsrc))) { - in_persistent_list = false; - grpc_channel_destroy(p->wrapper->wrapped); - free(p->wrapper->target); - free(p->wrapper->args_hashstr); - if (p->wrapper->creds_hashstr != NULL) { - free(p->wrapper->creds_hashstr); - p->wrapper->creds_hashstr = NULL; - } - free(p->wrapper->key); - p->wrapper->wrapped = NULL; - p->wrapper->target = NULL; - p->wrapper->args_hashstr = NULL; - p->wrapper->key = NULL; - } - gpr_mu_unlock(&global_persistent_list_mu); - } - } - p->wrapper->ref_count -= 1; - gpr_mu_unlock(&p->wrapper->mu); - if (!in_persistent_list) { - gpr_mu_destroy(&p->wrapper->mu); - free(p->wrapper); - p->wrapper = NULL; - } + php_grpc_channel_unref(p->wrapper); + p->wrapper = NULL; } PHP_GRPC_FREE_WRAPPED_FUNC_END() @@ -162,6 +166,67 @@ void generate_sha1_str(char *sha1str, char *str, php_grpc_int len) { make_sha1_digest(sha1str, digest); } +bool php_grpc_persistent_list_delete_unused_channel( + char* target, + target_bound_le_t* target_bound_status TSRMLS_DC) { + zval *data; + PHP_GRPC_HASH_FOREACH_VAL_START(&grpc_persistent_list, data) + php_grpc_zend_resource *rsrc = (php_grpc_zend_resource*) PHP_GRPC_HASH_VALPTR_TO_VAL(data) + if (rsrc == NULL) { + break; + } + channel_persistent_le_t* le = rsrc->ptr; + // Find the channel sharing the same target. + if (strcmp(le->channel->target, target) == 0) { + // ref_count=1 means that only the map holds the reference to the channel. + if (le->channel->ref_count == 1) { + php_grpc_delete_persistent_list_entry(le->channel->key, + strlen(le->channel->key) + TSRMLS_CC); + target_bound_status->current_count -= 1; + if (target_bound_status->current_count < target_bound_status->upper_bound) { + return true; + } + } + } + PHP_GRPC_HASH_FOREACH_END() + return false; +} + +target_bound_le_t* update_and_get_target_upper_bound(char* target, int bound) { + php_grpc_zend_resource *rsrc; + target_bound_le_t* target_bound_status; + php_grpc_int key_len = strlen(target); + if (!(PHP_GRPC_PERSISTENT_LIST_FIND(&grpc_target_upper_bound_map, target, + key_len, rsrc))) { + // Target is not not persisted. + php_grpc_zend_resource new_rsrc; + target_bound_status = malloc(sizeof(target_bound_le_t)); + if (bound == -1) { + // If the bound is not set, use 1 as default.s + bound = 1; + } + target_bound_status->upper_bound = bound; + // Init current_count with 1. It should be add 1 when the channel is successfully + // created and minus 1 when it is removed from the persistent list. + target_bound_status->current_count = 0; + new_rsrc.type = le_bound; + new_rsrc.ptr = target_bound_status; + gpr_mu_lock(&global_persistent_list_mu); + PHP_GRPC_PERSISTENT_LIST_UPDATE(&grpc_target_upper_bound_map, + target, key_len, (void *)&new_rsrc); + gpr_mu_unlock(&global_persistent_list_mu); + } else { + // The target already in the map recording the upper bound. + // If no newer bound set, use the original now. + target_bound_status = (target_bound_le_t *)rsrc->ptr; + if (bound != -1) { + target_bound_status->upper_bound = bound; + } + } + return target_bound_status; +} + void create_channel( wrapped_grpc_channel *channel, char *target, @@ -174,6 +239,8 @@ void create_channel( channel->wrapper->wrapped = grpc_secure_channel_create(creds->wrapped, target, &args, NULL); } + // There is an Grpc\Channel object refer to it. + php_grpc_channel_ref(channel->wrapper); efree(args.args); } @@ -183,7 +250,28 @@ void create_and_add_channel_to_persistent_list( grpc_channel_args args, wrapped_grpc_channel_credentials *creds, char *key, - php_grpc_int key_len TSRMLS_DC) { + php_grpc_int key_len, + int target_upper_bound TSRMLS_DC) { + target_bound_le_t* target_bound_status = + update_and_get_target_upper_bound(target, target_upper_bound); + // Check the upper bound status before inserting to the persistent map. + if (target_bound_status->current_count >= + target_bound_status->upper_bound) { + if (!php_grpc_persistent_list_delete_unused_channel( + target, target_bound_status TSRMLS_CC)) { + // If no channel can be deleted from the persistent map, + // do not persist this one. + create_channel(channel, target, args, creds); + php_printf("[Warning] The number of channel for the" + " target %s is maxed out bounded.\n", target); + php_printf("[Warning] Target upper bound: %d. Current size: %d.\n", + target_bound_status->upper_bound, + target_bound_status->current_count); + php_printf("[Warning] Target %s will not be persisted.\n", target); + return; + } + } + // There is space in the persistent map. php_grpc_zend_resource new_rsrc; channel_persistent_le_t *le; // this links each persistent list entry to a destructor @@ -191,12 +279,15 @@ void create_and_add_channel_to_persistent_list( le = malloc(sizeof(channel_persistent_le_t)); create_channel(channel, target, args, creds); + target_bound_status->current_count += 1; le->channel = channel->wrapper; new_rsrc.ptr = le; gpr_mu_lock(&global_persistent_list_mu); PHP_GRPC_PERSISTENT_LIST_UPDATE(&grpc_persistent_list, key, key_len, (void *)&new_rsrc); + // Persistent map refer to it. + php_grpc_channel_ref(channel->wrapper); gpr_mu_unlock(&global_persistent_list_mu); } @@ -230,6 +321,7 @@ PHP_METHOD(Channel, __construct) { php_grpc_zend_resource *rsrc; bool force_new = false; zval *force_new_obj = NULL; + int target_upper_bound = -1; /* "sa" == 1 string, 1 array */ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", &target, @@ -263,6 +355,19 @@ PHP_METHOD(Channel, __construct) { php_grpc_zend_hash_del(array_hash, "force_new", sizeof("force_new")); } + if (php_grpc_zend_hash_find(array_hash, "grpc_target_persist_bound", + sizeof("grpc_target_persist_bound"), + (void **)&force_new_obj) == SUCCESS) { + if (Z_TYPE_P(force_new_obj) != IS_LONG) { + zend_throw_exception(spl_ce_InvalidArgumentException, + "plist_bound must be a number", + 1 TSRMLS_CC); + } + target_upper_bound = (int)Z_LVAL_P(force_new_obj); + php_grpc_zend_hash_del(array_hash, "grpc_target_persist_bound", + sizeof("grpc_target_persist_bound")); + } + // parse the rest of the channel args array if (php_grpc_read_args_array(args_array, &args TSRMLS_CC) == FAILURE) { efree(args.args); @@ -296,12 +401,11 @@ PHP_METHOD(Channel, __construct) { strcat(key, creds->hashstr); } channel->wrapper = malloc(sizeof(grpc_channel_wrapper)); + channel->wrapper->ref_count = 0; channel->wrapper->key = key; channel->wrapper->target = strdup(target); channel->wrapper->args_hashstr = strdup(sha1str); channel->wrapper->creds_hashstr = NULL; - channel->wrapper->ref_count = 1; - channel->wrapper->is_valid = true; if (creds != NULL && creds->hashstr != NULL) { php_grpc_int creds_hashstr_len = strlen(creds->hashstr); char *channel_creds_hashstr = malloc(creds_hashstr_len + 1); @@ -319,7 +423,7 @@ PHP_METHOD(Channel, __construct) { } else if (!(PHP_GRPC_PERSISTENT_LIST_FIND(&grpc_persistent_list, key, key_len, rsrc))) { create_and_add_channel_to_persistent_list( - channel, target, args, creds, key, key_len TSRMLS_CC); + channel, target, args, creds, key, key_len, target_upper_bound TSRMLS_CC); } else { // Found a previously stored channel in the persistent list channel_persistent_le_t *le = (channel_persistent_le_t *)rsrc->ptr; @@ -329,20 +433,17 @@ PHP_METHOD(Channel, __construct) { 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 TSRMLS_CC); + channel, target, args, creds, key, key_len, target_upper_bound TSRMLS_CC); } else { efree(args.args); - if (channel->wrapper->creds_hashstr != NULL) { - free(channel->wrapper->creds_hashstr); - channel->wrapper->creds_hashstr = NULL; - } - free(channel->wrapper->creds_hashstr); - free(channel->wrapper->key); - free(channel->wrapper->target); - free(channel->wrapper->args_hashstr); + free_grpc_channel_wrapper(channel->wrapper, false); + gpr_mu_destroy(&channel->wrapper->mu); free(channel->wrapper); + channel->wrapper = NULL; channel->wrapper = le->channel; - channel->wrapper->ref_count += 1; + // One more Grpc\Channel object refer to it. + php_grpc_channel_ref(channel->wrapper); + update_and_get_target_upper_bound(target, target_upper_bound); } } } @@ -353,13 +454,13 @@ PHP_METHOD(Channel, __construct) { */ PHP_METHOD(Channel, getTarget) { wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis()); - gpr_mu_lock(&channel->wrapper->mu); - if (channel->wrapper->wrapped == NULL) { + if (channel->wrapper == NULL) { zend_throw_exception(spl_ce_RuntimeException, - "Channel already closed", 1 TSRMLS_CC); - gpr_mu_unlock(&channel->wrapper->mu); + "getTarget error." + "Channel is already closed.", 1 TSRMLS_CC); return; } + gpr_mu_lock(&channel->wrapper->mu); char *target = grpc_channel_get_target(channel->wrapper->wrapped); gpr_mu_unlock(&channel->wrapper->mu); PHP_GRPC_RETVAL_STRING(target, 1); @@ -373,16 +474,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) { + if (channel->wrapper == NULL) { zend_throw_exception(spl_ce_RuntimeException, - "Channel already closed", 1 TSRMLS_CC); - gpr_mu_unlock(&channel->wrapper->mu); + "getConnectivityState error." + "Channel is already closed.", 1 TSRMLS_CC); return; } - + gpr_mu_lock(&channel->wrapper->mu); bool try_to_connect = false; - /* "|b" == 1 optional bool */ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &try_to_connect) == FAILURE) { @@ -393,11 +492,6 @@ PHP_METHOD(Channel, getConnectivityState) { } 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); } @@ -411,14 +505,13 @@ 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) { + if (channel->wrapper == NULL) { zend_throw_exception(spl_ce_RuntimeException, - "Channel already closed", 1 TSRMLS_CC); - gpr_mu_unlock(&channel->wrapper->mu); + "watchConnectivityState error" + "Channel is already closed.", 1 TSRMLS_CC); return; } - + gpr_mu_lock(&channel->wrapper->mu); php_grpc_long last_state; zval *deadline_obj; @@ -451,46 +544,10 @@ PHP_METHOD(Channel, watchConnectivityState) { */ PHP_METHOD(Channel, close) { wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis()); - bool is_last_wrapper = false; if (channel->wrapper != NULL) { - // Channel_wrapper hasn't call close before. - gpr_mu_lock(&channel->wrapper->mu); - if (channel->wrapper->wrapped != NULL) { - if (channel->wrapper->is_valid) { - // Wrapped channel hasn't been destoryed by other wrapper. - grpc_channel_destroy(channel->wrapper->wrapped); - free(channel->wrapper->target); - free(channel->wrapper->args_hashstr); - free(channel->wrapper->creds_hashstr); - channel->wrapper->creds_hashstr = NULL; - channel->wrapper->target = NULL; - channel->wrapper->args_hashstr = NULL; - channel->wrapper->wrapped = NULL; - channel->wrapper->is_valid = false; - - php_grpc_delete_persistent_list_entry(channel->wrapper->key, - strlen(channel->wrapper->key) - TSRMLS_CC); - } - } - channel->wrapper->ref_count -= 1; - if (channel->wrapper->ref_count == 0) { - // Mark that the wrapper can be freed because mu should be - // destroyed outside the lock. - is_last_wrapper = true; - } - gpr_mu_unlock(&channel->wrapper->mu); + php_grpc_channel_unref(channel->wrapper); + channel->wrapper = NULL; } - gpr_mu_lock(&global_persistent_list_mu); - if (is_last_wrapper) { - gpr_mu_destroy(&channel->wrapper->mu); - free(channel->wrapper->key); - free(channel->wrapper); - } - // Set channel->wrapper to NULL to avoid call close twice for the same - // channel. - channel->wrapper = NULL; - gpr_mu_unlock(&global_persistent_list_mu); } // Delete an entry from the persistent list @@ -501,11 +558,7 @@ void php_grpc_delete_persistent_list_entry(char *key, php_grpc_int key_len gpr_mu_lock(&global_persistent_list_mu); if (PHP_GRPC_PERSISTENT_LIST_FIND(&grpc_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(&grpc_persistent_list, key, key_len+1); - free(le); } gpr_mu_unlock(&global_persistent_list_mu); } @@ -518,20 +571,137 @@ static void php_grpc_channel_plink_dtor(php_grpc_zend_resource *rsrc return; } if (le->channel != NULL) { - gpr_mu_lock(&le->channel->mu); - if (le->channel->wrapped != NULL) { - grpc_channel_destroy(le->channel->wrapped); - free(le->channel->args_hashstr); - le->channel->wrapped = NULL; - le->channel->target = NULL; - le->channel->args_hashstr = NULL; - free(le->channel->key); - le->channel->key = NULL; - } - gpr_mu_unlock(&le->channel->mu); + php_grpc_channel_unref(le->channel); + le->channel = NULL; } + free(le); + le = NULL; } +// A destructor associated with each list entry from the target_bound map +static void php_grpc_target_bound_dtor(php_grpc_zend_resource *rsrc + TSRMLS_DC) { + target_bound_le_t *le = (target_bound_le_t *) rsrc->ptr; + if (le == NULL) { + return; + } + free(le); + le = NULL; +} + +#ifdef GRPC_PHP_DEBUG + +/** +* Clean all channels in the persistent. Test only. +* @return void +*/ +PHP_METHOD(Channel, cleanPersistentList) { + zend_hash_clean(&grpc_persistent_list); + zend_hash_clean(&grpc_target_upper_bound_map); +} + +char *grpc_connectivity_state_name(grpc_connectivity_state state) { + switch (state) { + case GRPC_CHANNEL_IDLE: + return "IDLE"; + case GRPC_CHANNEL_CONNECTING: + return "CONNECTING"; + case GRPC_CHANNEL_READY: + return "READY"; + case GRPC_CHANNEL_TRANSIENT_FAILURE: + return "TRANSIENT_FAILURE"; + case GRPC_CHANNEL_SHUTDOWN: + return "SHUTDOWN"; + } + return "UNKNOWN"; +} + +/** +* Return the info about the current channel. Test only. +* @return array +*/ +PHP_METHOD(Channel, getChannelInfo) { + wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis()); + array_init(return_value); + // Info about the target + PHP_GRPC_ADD_STRING_TO_ARRAY(return_value, "target", + sizeof("target"), channel->wrapper->target, true); + // Info about the upper bound for the target + target_bound_le_t* target_bound_status = + update_and_get_target_upper_bound(channel->wrapper->target, -1); + PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "target_upper_bound", + sizeof("target_upper_bound"), target_bound_status->upper_bound); + PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "target_current_size", + sizeof("target_current_size"), target_bound_status->current_count); + // Info about key + PHP_GRPC_ADD_STRING_TO_ARRAY(return_value, "key", + sizeof("key"), channel->wrapper->key, true); + // Info about persistent channel ref_count + PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "ref_count", + sizeof("ref_count"), channel->wrapper->ref_count); + // Info about connectivity status + int state = + grpc_channel_check_connectivity_state(channel->wrapper->wrapped, (int)0); + // It should be set to 'true' in PHP 5.6.33 + PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "connectivity_status", + sizeof("connectivity_status"), state); + PHP_GRPC_ADD_STRING_TO_ARRAY(return_value, "ob", + sizeof("ob"), + grpc_connectivity_state_name(state), true); + // Info about the channel is closed or not + PHP_GRPC_ADD_BOOL_TO_ARRAY(return_value, "is_valid", + sizeof("is_valid"), (channel->wrapper == NULL)); +} + +/** +* Return an array of all channels in the persistent list. Test only. +* @return array +*/ +PHP_METHOD(Channel, getPersistentList) { + array_init(return_value); + zval *data; + PHP_GRPC_HASH_FOREACH_VAL_START(&grpc_persistent_list, data) + php_grpc_zend_resource *rsrc = + (php_grpc_zend_resource*) PHP_GRPC_HASH_VALPTR_TO_VAL(data) + if (rsrc == NULL) { + break; + } + channel_persistent_le_t* le = rsrc->ptr; + zval* ret_arr; + PHP_GRPC_MAKE_STD_ZVAL(ret_arr); + array_init(ret_arr); + // Info about the target + PHP_GRPC_ADD_STRING_TO_ARRAY(ret_arr, "target", + sizeof("target"), le->channel->target, true); + // Info about the upper bound for the target + target_bound_le_t* target_bound_status = + update_and_get_target_upper_bound(le->channel->target, -1); + PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "target_upper_bound", + sizeof("target_upper_bound"), target_bound_status->upper_bound); + PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "target_current_size", + sizeof("target_current_size"), target_bound_status->current_count); + // Info about key + PHP_GRPC_ADD_STRING_TO_ARRAY(ret_arr, "key", + sizeof("key"), le->channel->key, true); + // Info about persistent channel ref_count + PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "ref_count", + sizeof("ref_count"), le->channel->ref_count); + // Info about connectivity status + int state = + grpc_channel_check_connectivity_state(le->channel->wrapped, (int)0); + // It should be set to 'true' in PHP 5.6.33 + PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "connectivity_status", + sizeof("connectivity_status"), state); + PHP_GRPC_ADD_STRING_TO_ARRAY(ret_arr, "ob", + sizeof("ob"), + grpc_connectivity_state_name(state), true); + add_assoc_zval(return_value, le->channel->key, ret_arr); + PHP_GRPC_FREE_STD_ZVAL(ret_arr); + PHP_GRPC_HASH_FOREACH_END() +} +#endif + + ZEND_BEGIN_ARG_INFO_EX(arginfo_construct, 0, 0, 2) ZEND_ARG_INFO(0, target) ZEND_ARG_INFO(0, args) @@ -552,6 +722,18 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_close, 0, 0, 0) ZEND_END_ARG_INFO() +#ifdef GRPC_PHP_DEBUG +ZEND_BEGIN_ARG_INFO_EX(arginfo_getChannelInfo, 0, 0, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_cleanPersistentList, 0, 0, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_getPersistentList, 0, 0, 0) +ZEND_END_ARG_INFO() +#endif + + static zend_function_entry channel_methods[] = { PHP_ME(Channel, __construct, arginfo_construct, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR) @@ -563,6 +745,14 @@ static zend_function_entry channel_methods[] = { ZEND_ACC_PUBLIC) PHP_ME(Channel, close, arginfo_close, ZEND_ACC_PUBLIC) + #ifdef GRPC_PHP_DEBUG + PHP_ME(Channel, getChannelInfo, arginfo_getChannelInfo, + ZEND_ACC_PUBLIC) + PHP_ME(Channel, cleanPersistentList, arginfo_cleanPersistentList, + ZEND_ACC_PUBLIC) + PHP_ME(Channel, getPersistentList, arginfo_getPersistentList, + ZEND_ACC_PUBLIC) + #endif PHP_FE_END }; @@ -576,6 +766,12 @@ GRPC_STARTUP_FUNCTION(channel) { NULL, php_grpc_channel_plink_dtor, "Persistent Channel", module_number); zend_hash_init_ex(&grpc_persistent_list, 20, NULL, EG(persistent_list).pDestructor, 1, 0); + // Register the target->upper_bound map. + le_bound = zend_register_list_destructors_ex( + NULL, php_grpc_target_bound_dtor, "Target Bound", module_number); + zend_hash_init_ex(&grpc_target_upper_bound_map, 20, NULL, + EG(persistent_list).pDestructor, 1, 0); + 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 86bfdea51a..7aad59abe5 100644 --- a/src/php/ext/grpc/channel.h +++ b/src/php/ext/grpc/channel.h @@ -39,12 +39,8 @@ typedef struct _grpc_channel_wrapper { char *target; char *args_hashstr; char *creds_hashstr; - gpr_mu mu; - // is_valid is used to check the wrapped channel has been freed - // before to avoid double free. - bool is_valid; - // ref_count is used to let the last wrapper free related channel and key. size_t ref_count; + gpr_mu mu; } grpc_channel_wrapper; /* Wrapper struct for grpc_channel that can be associated with a PHP object */ @@ -86,5 +82,9 @@ typedef struct _channel_persistent_le { grpc_channel_wrapper *channel; } channel_persistent_le_t; +typedef struct _target_bound_le { + int upper_bound; + int current_count; +} target_bound_le_t; #endif /* NET_GRPC_PHP_GRPC_CHANNEL_H_ */ diff --git a/src/php/ext/grpc/config.m4 b/src/php/ext/grpc/config.m4 index 0fb843d51f..fa54ebd920 100755 --- a/src/php/ext/grpc/config.m4 +++ b/src/php/ext/grpc/config.m4 @@ -4,6 +4,14 @@ PHP_ARG_ENABLE(grpc, whether to enable grpc support, PHP_ARG_ENABLE(coverage, whether to include code coverage symbols, [ --enable-coverage Enable coverage support], no, no) +PHP_ARG_ENABLE(tests, whether to compile helper methods for tests, +[ --enable-tests Enable tests methods], no, no) + +dnl Check whether to enable tests +if test "$PHP_TESTS" != "no"; then + CPPFLAGS="$CPPFLAGS -DGRPC_PHP_DEBUG" +fi + if test "$PHP_GRPC" != "no"; then dnl Write more examples of tests here... diff --git a/src/php/ext/grpc/php7_wrapper.h b/src/php/ext/grpc/php7_wrapper.h index 0239e04f76..3b95fbe451 100644 --- a/src/php/ext/grpc/php7_wrapper.h +++ b/src/php/ext/grpc/php7_wrapper.h @@ -46,6 +46,8 @@ add_assoc_long_ex(val, key, key_len, str); #define PHP_GRPC_ADD_BOOL_TO_ARRAY(val, key, key_len, str) \ add_assoc_bool_ex(val, key, key_len, str); +#define PHP_GRPC_ADD_LONG_TO_RETVAL(val, key, key_len, str) \ + add_assoc_long_ex(val, key, key_len+1, str); #define RETURN_DESTROY_ZVAL(val) \ RETURN_ZVAL(val, false /* Don't execute copy constructor */, \ @@ -140,7 +142,7 @@ static inline int php_grpc_zend_hash_find(HashTable *ht, char *key, int len, zend_hash_update(plist, key, len+1, rsrc, sizeof(php_grpc_zend_resource), \ NULL) #define PHP_GRPC_PERSISTENT_LIST_SIZE(plist) \ - plist.nTableSize + *plist.nNumOfElements #define PHP_GRPC_GET_CLASS_ENTRY(object) zend_get_class_entry(object TSRMLS_CC) @@ -176,6 +178,8 @@ static inline int php_grpc_zend_hash_find(HashTable *ht, char *key, int len, add_assoc_long_ex(val, key, key_len - 1, str); #define PHP_GRPC_ADD_BOOL_TO_ARRAY(val, key, key_len, str) \ add_assoc_bool_ex(val, key, key_len - 1, str); +#define PHP_GRPC_ADD_LONG_TO_RETVAL(val, key, key_len, str) \ + add_assoc_long_ex(val, key, key_len, str); #define RETURN_DESTROY_ZVAL(val) \ RETVAL_ZVAL(val, false /* Don't execute copy constructor */, \ diff --git a/src/php/ext/grpc/php_grpc.c b/src/php/ext/grpc/php_grpc.c index 883ee6f3e5..1dd1f4fe50 100644 --- a/src/php/ext/grpc/php_grpc.c +++ b/src/php/ext/grpc/php_grpc.c @@ -37,6 +37,7 @@ ZEND_DECLARE_MODULE_GLOBALS(grpc) static PHP_GINIT_FUNCTION(grpc); HashTable grpc_persistent_list; +HashTable grpc_target_upper_bound_map; /* {{{ grpc_functions[] * * Every user visible function must have an entry in grpc_functions[]. @@ -242,6 +243,8 @@ PHP_MSHUTDOWN_FUNCTION(grpc) { if (GRPC_G(initialized)) { zend_hash_clean(&grpc_persistent_list); zend_hash_destroy(&grpc_persistent_list); + zend_hash_clean(&grpc_target_upper_bound_map); + zend_hash_destroy(&grpc_target_upper_bound_map); grpc_shutdown_timeval(TSRMLS_C); grpc_php_shutdown_completion_queue(TSRMLS_C); grpc_shutdown(); diff --git a/src/php/tests/unit_tests/ChannelTest.php b/src/php/tests/unit_tests/ChannelTest.php index 5baff1fbd9..49b0e350f7 100644 --- a/src/php/tests/unit_tests/ChannelTest.php +++ b/src/php/tests/unit_tests/ChannelTest.php @@ -1,7 +1,7 @@ channel = new Grpc\Channel('localhost:0', + $this->channel = new Grpc\Channel('localhost:50000', ['credentials' => Grpc\ChannelCredentials::createInsecure()]); $this->assertSame('Grpc\Channel', get_class($this->channel)); } public function testGetConnectivityState() { - $this->channel = new Grpc\Channel('localhost:0', + $this->channel = new Grpc\Channel('localhost:50001', ['credentials' => Grpc\ChannelCredentials::createInsecure()]); $state = $this->channel->getConnectivityState(); $this->assertEquals(0, $state); @@ -47,7 +47,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase public function testGetConnectivityStateWithInt() { - $this->channel = new Grpc\Channel('localhost:0', + $this->channel = new Grpc\Channel('localhost:50002', ['credentials' => Grpc\ChannelCredentials::createInsecure()]); $state = $this->channel->getConnectivityState(123); $this->assertEquals(0, $state); @@ -55,7 +55,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase public function testGetConnectivityStateWithString() { - $this->channel = new Grpc\Channel('localhost:0', + $this->channel = new Grpc\Channel('localhost:50003', ['credentials' => Grpc\ChannelCredentials::createInsecure()]); $state = $this->channel->getConnectivityState('hello'); $this->assertEquals(0, $state); @@ -63,7 +63,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase public function testGetConnectivityStateWithBool() { - $this->channel = new Grpc\Channel('localhost:0', + $this->channel = new Grpc\Channel('localhost:50004', ['credentials' => Grpc\ChannelCredentials::createInsecure()]); $state = $this->channel->getConnectivityState(true); $this->assertEquals(0, $state); @@ -71,7 +71,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase public function testGetTarget() { - $this->channel = new Grpc\Channel('localhost:8888', + $this->channel = new Grpc\Channel('localhost:50005', ['credentials' => Grpc\ChannelCredentials::createInsecure()]); $target = $this->channel->getTarget(); $this->assertTrue(is_string($target)); @@ -79,7 +79,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase public function testWatchConnectivityState() { - $this->channel = new Grpc\Channel('localhost:0', + $this->channel = new Grpc\Channel('localhost:50006', ['credentials' => Grpc\ChannelCredentials::createInsecure()]); $now = Grpc\Timeval::now(); $deadline = $now->add(new Grpc\Timeval(100*1000)); // 100ms @@ -93,7 +93,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase public function testClose() { - $this->channel = new Grpc\Channel('localhost:0', + $this->channel = new Grpc\Channel('localhost:50007', ['credentials' => Grpc\ChannelCredentials::createInsecure()]); $this->assertNotNull($this->channel); $this->channel->close(); @@ -113,7 +113,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase */ public function testInvalidConstructorWith() { - $this->channel = new Grpc\Channel('localhost:0', 'invalid'); + $this->channel = new Grpc\Channel('localhost:50008', 'invalid'); $this->assertNull($this->channel); } @@ -122,7 +122,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase */ public function testInvalidCredentials() { - $this->channel = new Grpc\Channel('localhost:0', + $this->channel = new Grpc\Channel('localhost:50009', ['credentials' => new Grpc\Timeval(100)]); } @@ -131,7 +131,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase */ public function testInvalidOptionsArray() { - $this->channel = new Grpc\Channel('localhost:0', + $this->channel = new Grpc\Channel('localhost:50010', ['abc' => []]); } @@ -140,7 +140,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase */ public function testInvalidGetConnectivityStateWithArray() { - $this->channel = new Grpc\Channel('localhost:0', + $this->channel = new Grpc\Channel('localhost:50011', ['credentials' => Grpc\ChannelCredentials::createInsecure()]); $this->channel->getConnectivityState([]); } @@ -150,7 +150,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase */ public function testInvalidWatchConnectivityState() { - $this->channel = new Grpc\Channel('localhost:0', + $this->channel = new Grpc\Channel('localhost:50012', ['credentials' => Grpc\ChannelCredentials::createInsecure()]); $this->channel->watchConnectivityState([]); } @@ -160,7 +160,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase */ public function testInvalidWatchConnectivityState2() { - $this->channel = new Grpc\Channel('localhost:0', + $this->channel = new Grpc\Channel('localhost:50013', ['credentials' => Grpc\ChannelCredentials::createInsecure()]); $this->channel->watchConnectivityState(1, 'hi'); } @@ -185,10 +185,12 @@ class ChannelTest extends PHPUnit_Framework_TestCase public function testPersistentChannelSameHost() { - $this->channel1 = new Grpc\Channel('localhost:1', []); + $this->channel1 = new Grpc\Channel('localhost:50014', [ + "grpc_target_persist_bound" => 3, + ]); // the underlying grpc channel is the same by default // when connecting to the same host - $this->channel2 = new Grpc\Channel('localhost:1', []); + $this->channel2 = new Grpc\Channel('localhost:50014', []); // both channels should be IDLE $state = $this->channel1->getConnectivityState(); @@ -213,8 +215,10 @@ class ChannelTest extends PHPUnit_Framework_TestCase public function testPersistentChannelDifferentHost() { // two different underlying channels because different hostname - $this->channel1 = new Grpc\Channel('localhost:1', []); - $this->channel2 = new Grpc\Channel('localhost:2', []); + $this->channel1 = new Grpc\Channel('localhost:50015', [ + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\Channel('localhost:50016', []); // both channels should be IDLE $state = $this->channel1->getConnectivityState(); @@ -239,8 +243,11 @@ class ChannelTest extends PHPUnit_Framework_TestCase public function testPersistentChannelSameArgs() { - $this->channel1 = new Grpc\Channel('localhost:1', ["abc" => "def"]); - $this->channel2 = new Grpc\Channel('localhost:1', ["abc" => "def"]); + $this->channel1 = new Grpc\Channel('localhost:50017', [ + "grpc_target_persist_bound" => 3, + "abc" => "def", + ]); + $this->channel2 = new Grpc\Channel('localhost:50017', ["abc" => "def"]); // try to connect on channel1 $state = $this->channel1->getConnectivityState(true); @@ -257,8 +264,10 @@ class ChannelTest extends PHPUnit_Framework_TestCase public function testPersistentChannelDifferentArgs() { - $this->channel1 = new Grpc\Channel('localhost:1', []); - $this->channel2 = new Grpc\Channel('localhost:1', ["abc" => "def"]); + $this->channel1 = new Grpc\Channel('localhost:50018', [ + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\Channel('localhost:50018', ["abc" => "def"]); // try to connect on channel1 $state = $this->channel1->getConnectivityState(true); @@ -278,9 +287,11 @@ class ChannelTest extends PHPUnit_Framework_TestCase $creds1 = Grpc\ChannelCredentials::createSsl(); $creds2 = Grpc\ChannelCredentials::createSsl(); - $this->channel1 = new Grpc\Channel('localhost:1', - ["credentials" => $creds1]); - $this->channel2 = new Grpc\Channel('localhost:1', + $this->channel1 = new Grpc\Channel('localhost:50019', + ["credentials" => $creds1, + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\Channel('localhost:50019', ["credentials" => $creds2]); // try to connect on channel1 @@ -302,9 +313,11 @@ class ChannelTest extends PHPUnit_Framework_TestCase $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', + $this->channel1 = new Grpc\Channel('localhost:50020', + ["credentials" => $creds1, + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\Channel('localhost:50020', ["credentials" => $creds2]); // try to connect on channel1 @@ -327,9 +340,11 @@ class ChannelTest extends PHPUnit_Framework_TestCase $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', + $this->channel1 = new Grpc\Channel('localhost:50021', + ["credentials" => $creds1, + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\Channel('localhost:50021', ["credentials" => $creds2]); // try to connect on channel1 @@ -350,9 +365,11 @@ class ChannelTest extends PHPUnit_Framework_TestCase $creds1 = Grpc\ChannelCredentials::createSsl(); $creds2 = Grpc\ChannelCredentials::createInsecure(); - $this->channel1 = new Grpc\Channel('localhost:1', - ["credentials" => $creds1]); - $this->channel2 = new Grpc\Channel('localhost:1', + $this->channel1 = new Grpc\Channel('localhost:50022', + ["credentials" => $creds1, + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\Channel('localhost:50022', ["credentials" => $creds2]); // try to connect on channel1 @@ -368,29 +385,55 @@ class ChannelTest extends PHPUnit_Framework_TestCase $this->channel2->close(); } + public function testPersistentChannelSharedChannelClose1() + { + // same underlying channel + $this->channel1 = new Grpc\Channel('localhost:50123', [ + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\Channel('localhost:50123', []); + + // close channel1 + $this->channel1->close(); + + // channel2 can still be use. We need to exclude the possible that + // in testPersistentChannelSharedChannelClose2, the exception is thrown + // by channel1. + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + } + /** * @expectedException RuntimeException */ - public function testPersistentChannelSharedChannelClose() + public function testPersistentChannelSharedChannelClose2() { // same underlying channel - $this->channel1 = new Grpc\Channel('localhost:1', []); - $this->channel2 = new Grpc\Channel('localhost:1', []); + $this->channel1 = new Grpc\Channel('localhost:50223', [ + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\Channel('localhost:50223', []); // close channel1 $this->channel1->close(); - // channel is already closed + // channel2 can still be use $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + // channel 1 is closed + $state = $this->channel1->getConnectivityState(); } public function testPersistentChannelCreateAfterClose() { - $this->channel1 = new Grpc\Channel('localhost:1', []); + $this->channel1 = new Grpc\Channel('localhost:50024', [ + "grpc_target_persist_bound" => 3, + ]); $this->channel1->close(); - $this->channel2 = new Grpc\Channel('localhost:1', []); + $this->channel2 = new Grpc\Channel('localhost:50024', []); $state = $this->channel2->getConnectivityState(); $this->assertEquals(GRPC\CHANNEL_IDLE, $state); @@ -399,9 +442,11 @@ class ChannelTest extends PHPUnit_Framework_TestCase public function testPersistentChannelSharedMoreThanTwo() { - $this->channel1 = new Grpc\Channel('localhost:1', []); - $this->channel2 = new Grpc\Channel('localhost:1', []); - $this->channel3 = new Grpc\Channel('localhost:1', []); + $this->channel1 = new Grpc\Channel('localhost:50025', [ + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\Channel('localhost:50025', []); + $this->channel3 = new Grpc\Channel('localhost:50025', []); // try to connect on channel1 $state = $this->channel1->getConnectivityState(true); @@ -439,10 +484,12 @@ class ChannelTest extends PHPUnit_Framework_TestCase // 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', + $this->channel1 = new Grpc\Channel('localhost:50026', ["credentials" => - $credsWithCallCreds]); - $this->channel2 = new Grpc\Channel('localhost:1', + $credsWithCallCreds, + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\Channel('localhost:50026', ["credentials" => $credsWithCallCreds]); @@ -476,11 +523,13 @@ class ChannelTest extends PHPUnit_Framework_TestCase // 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', + $this->channel1 = new Grpc\Channel('localhost:50027', + ["credentials" => $creds1, + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\Channel('localhost:50027', ["credentials" => $creds2]); - $this->channel3 = new Grpc\Channel('localhost:1', + $this->channel3 = new Grpc\Channel('localhost:50027', ["credentials" => $creds3]); // try to connect on channel1 @@ -501,10 +550,12 @@ class ChannelTest extends PHPUnit_Framework_TestCase public function testPersistentChannelForceNew() { - $this->channel1 = new Grpc\Channel('localhost:1', []); + $this->channel1 = new Grpc\Channel('localhost:50028', [ + "grpc_target_persist_bound" => 2, + ]); // even though all the channel params are the same, channel2 // has a new and different underlying channel - $this->channel2 = new Grpc\Channel('localhost:1', + $this->channel2 = new Grpc\Channel('localhost:50028', ["force_new" => true]); // try to connect on channel1 @@ -520,19 +571,20 @@ class ChannelTest extends PHPUnit_Framework_TestCase $this->channel2->close(); } - public function testPersistentChannelForceNewOldChannelIdle() + public function testPersistentChannelForceNewOldChannelIdle1() { - $this->channel1 = new Grpc\Channel('localhost:1', []); - $this->channel2 = new Grpc\Channel('localhost:1', + $this->channel1 = new Grpc\Channel('localhost:50029', [ + "grpc_target_persist_bound" => 2, + ]); + $this->channel2 = new Grpc\Channel('localhost:50029', ["force_new" => true]); // channel3 shares with channel1 - $this->channel3 = new Grpc\Channel('localhost:1', []); + $this->channel3 = new Grpc\Channel('localhost:50029', []); // 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(); @@ -544,34 +596,85 @@ class ChannelTest extends PHPUnit_Framework_TestCase $this->channel2->close(); } + public function testPersistentChannelForceNewOldChannelIdle2() + { + + $this->channel1 = new Grpc\Channel('localhost:50029', [ + "grpc_target_persist_bound" => 2, + ]); + $this->channel2 = new Grpc\Channel('localhost:50029', []); + + // try to connect on channel2 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel2); + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertConnecting($state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelForceNewOldChannelClose1() + { + + $this->channel1 = new Grpc\Channel('localhost:50130', [ + "grpc_target_persist_bound" => 2, + ]); + $this->channel2 = new Grpc\Channel('localhost:50130', + ["force_new" => true]); + // channel3 shares with channel1 + $this->channel3 = new Grpc\Channel('localhost:50130', []); + + $this->channel1->close(); + + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + // channel3 is still usable. We need to exclude the possibility that in + // testPersistentChannelForceNewOldChannelClose2, the exception is thrown + // by channel1 and channel2. + $state = $this->channel3->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + } + /** * @expectedException RuntimeException */ - public function testPersistentChannelForceNewOldChannelClose() + public function testPersistentChannelForceNewOldChannelClose2() { - $this->channel1 = new Grpc\Channel('localhost:1', []); - $this->channel2 = new Grpc\Channel('localhost:1', - ["force_new" => true]); + $this->channel1 = new Grpc\Channel('localhost:50230', [ + "grpc_target_persist_bound" => 2, + ]); + $this->channel2 = new Grpc\Channel('localhost:50230', + ["force_new" => true]); // channel3 shares with channel1 - $this->channel3 = new Grpc\Channel('localhost:1', []); + $this->channel3 = new Grpc\Channel('localhost:50230', []); $this->channel1->close(); $state = $this->channel2->getConnectivityState(); $this->assertEquals(GRPC\CHANNEL_IDLE, $state); - // channel3 already closed + // channel3 is still usable $state = $this->channel3->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + // channel 1 is closed + $this->channel1->getConnectivityState(); } public function testPersistentChannelForceNewNewChannelClose() { - $this->channel1 = new Grpc\Channel('localhost:1', []); - $this->channel2 = new Grpc\Channel('localhost:1', + $this->channel1 = new Grpc\Channel('localhost:50031', [ + "grpc_target_persist_bound" => 2, + ]); + $this->channel2 = new Grpc\Channel('localhost:50031', ["force_new" => true]); - $this->channel3 = new Grpc\Channel('localhost:1', []); + $this->channel3 = new Grpc\Channel('localhost:50031', []); $this->channel2->close(); diff --git a/src/php/tests/unit_tests/PersistentChannelTests/PersistentChannelTest.php b/src/php/tests/unit_tests/PersistentChannelTests/PersistentChannelTest.php new file mode 100644 index 0000000000..2bb5c4bb85 --- /dev/null +++ b/src/php/tests/unit_tests/PersistentChannelTests/PersistentChannelTest.php @@ -0,0 +1,489 @@ +getPersistentList(); + $channel_clean_persistent->cleanPersistentList(); + } + + 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 assertConnecting($state) { + $this->assertTrue($state == GRPC\CHANNEL_CONNECTING || + $state == GRPC\CHANNEL_TRANSIENT_FAILURE); + } + + public function testInitHelper() + { + // PersistentList is not empty at the beginning of the tests + // because phpunit will cache the channels created by other test + // files. + } + + + public function testChannelNotPersist() + { + $this->channel1 = new Grpc\Channel('localhost:1', ['force_new' => true]); + $channel1_info = $this->channel1->getChannelInfo(); + $plist_info = $this->channel1->getPersistentList(); + $this->assertEquals($channel1_info['target'], 'localhost:1'); + $this->assertEquals($channel1_info['ref_count'], 1); + $this->assertEquals($channel1_info['connectivity_status'], + GRPC\CHANNEL_IDLE); + $this->assertEquals(count($plist_info), 0); + $this->channel1->close(); + } + + public function testPersistentChannelCreateOneChannel() + { + $this->channel1 = new Grpc\Channel('localhost:1', []); + $channel1_info = $this->channel1->getChannelInfo(); + $plist_info = $this->channel1->getPersistentList(); + $this->assertEquals($channel1_info['target'], 'localhost:1'); + $this->assertEquals($channel1_info['ref_count'], 2); + $this->assertEquals($channel1_info['connectivity_status'], + GRPC\CHANNEL_IDLE); + $this->assertArrayHasKey($channel1_info['key'], $plist_info); + $this->assertEquals(count($plist_info), 1); + $this->channel1->close(); + } + + public function testPersistentChannelCreateMultipleChannels() + { + $this->channel1 = new Grpc\Channel('localhost:1', []); + $plist_info = $this->channel1->getPersistentList(); + $this->assertEquals(count($plist_info), 1); + + $this->channel2 = new Grpc\Channel('localhost:2', []); + $plist_info = $this->channel1->getPersistentList(); + $this->assertEquals(count($plist_info), 2); + + $this->channel3 = new Grpc\Channel('localhost:3', []); + $plist_info = $this->channel1->getPersistentList(); + $this->assertEquals(count($plist_info), 3); + } + + public function testPersistentChannelStatusChange() + { + $this->channel1 = new Grpc\Channel('localhost:4', []); + $channel1_info = $this->channel1->getChannelInfo(); + $this->assertEquals($channel1_info['connectivity_status'], + GRPC\CHANNEL_IDLE); + + $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + $channel1_info = $this->channel1->getChannelInfo(); + $this->assertConnecting($channel1_info['connectivity_status']); + $this->channel1->close(); + } + + public function testPersistentChannelCloseChannel() + { + $this->channel1 = new Grpc\Channel('localhost:1', []); + $this->channel2 = new Grpc\Channel('localhost:1', []); + + $channel1_info = $this->channel1->getChannelInfo(); + $this->assertEquals($channel1_info['ref_count'], 3); + $plist_info = $this->channel1->getPersistentList(); + $this->assertEquals($plist_info[$channel1_info['key']]['ref_count'], 3); + + $this->channel1->close(); + $plist_info = $this->channel1->getPersistentList(); + $this->assertEquals($plist_info[$channel1_info['key']]['ref_count'], 2); + + $this->channel2->close(); + $plist_info = $this->channel1->getPersistentList(); + $this->assertEquals($plist_info[$channel1_info['key']]['ref_count'], 1); + } + + public function testPersistentChannelSameTarget() + { + $this->channel1 = new Grpc\Channel('localhost:1', []); + $this->channel2 = new Grpc\Channel('localhost:1', []); + $plist = $this->channel2->getPersistentList(); + $channel1_info = $this->channel1->getChannelInfo(); + $channel2_info = $this->channel2->getChannelInfo(); + // $channel1 and $channel2 shares the same channel, thus only 1 + // channel should be in the persistent list. + $this->assertEquals($channel1_info['key'], $channel2_info['key']); + $this->assertArrayHasKey($channel1_info['key'], $plist); + $this->assertEquals(count($plist), 1); + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelDifferentTarget() + { + $this->channel1 = new Grpc\Channel('localhost:1', []); + $channel1_info = $this->channel1->getChannelInfo(); + $this->channel2 = new Grpc\Channel('localhost:2', []); + $channel2_info = $this->channel1->getChannelInfo(); + $plist_info = $this->channel1->getPersistentList(); + $this->assertArrayHasKey($channel1_info['key'], $plist_info); + $this->assertArrayHasKey($channel2_info['key'], $plist_info); + $this->assertEquals($plist_info[$channel1_info['key']]['ref_count'], 2); + $this->assertEquals($plist_info[$channel2_info['key']]['ref_count'], 2); + $plist_info = $this->channel1->getPersistentList(); + $this->assertEquals(count($plist_info), 2); + $this->channel1->close(); + $this->channel2->close(); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage startBatch Error. Channel is closed + */ + public function testPersistentChannelSharedChannelClose() + { + // same underlying channel + $this->channel1 = new Grpc\Channel('localhost:10010', [ + "grpc_target_persist_bound" => 2, + ]); + $this->channel2 = new Grpc\Channel('localhost:10010', []); + $this->server = new Grpc\Server([]); + $this->port = $this->server->addHttp2Port('localhost:10010'); + + // channel2 can still be use + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + $call1 = new Grpc\Call($this->channel1, + '/foo', + Grpc\Timeval::infFuture()); + $call2 = new Grpc\Call($this->channel2, + '/foo', + Grpc\Timeval::infFuture()); + $call3 = new Grpc\Call($this->channel1, + '/foo', + Grpc\Timeval::infFuture()); + $call4 = new Grpc\Call($this->channel2, + '/foo', + Grpc\Timeval::infFuture()); + $batch = [ + Grpc\OP_SEND_INITIAL_METADATA => [], + ]; + + $result = $call1->startBatch($batch); + $this->assertTrue($result->send_metadata); + $result = $call2->startBatch($batch); + $this->assertTrue($result->send_metadata); + + $this->channel1->close(); + // After closing channel1, channel2 can still be use + $result = $call4->startBatch($batch); + $this->assertTrue($result->send_metadata); + // channel 1 is closed, it will throw an exception. + $result = $call3->startBatch($batch); + } + + public function testPersistentChannelTargetDefaultUpperBound() + { + $this->channel1 = new Grpc\Channel('localhost:10011', []); + $channel1_info = $this->channel1->getChannelInfo(); + $this->assertEquals($channel1_info['target_upper_bound'], 1); + $this->assertEquals($channel1_info['target_current_size'], 1); + } + + public function testPersistentChannelTargetUpperBoundZero() + { + $this->channel1 = new Grpc\Channel('localhost:10011', [ + "grpc_target_persist_bound" => 0, + ]); + // channel1 will not be persisted. + $channel1_info = $this->channel1->getChannelInfo(); + $this->assertEquals($channel1_info['target_upper_bound'], 0); + $this->assertEquals($channel1_info['target_current_size'], 0); + $plist_info = $this->channel1->getPersistentList(); + $this->assertEquals(0, count($plist_info)); + } + + public function testPersistentChannelTargetUpperBoundNotZero() + { + $this->channel1 = new Grpc\Channel('localhost:10011', [ + "grpc_target_persist_bound" => 3, + ]); + $channel1_info = $this->channel1->getChannelInfo(); + $this->assertEquals($channel1_info['target_upper_bound'], 3); + $this->assertEquals($channel1_info['target_current_size'], 1); + + // The upper bound should not be changed + $this->channel2 = new Grpc\Channel('localhost:10011', []); + $channel2_info = $this->channel2->getChannelInfo(); + $this->assertEquals($channel2_info['target_upper_bound'], 3); + $this->assertEquals($channel2_info['target_current_size'], 1); + + // The upper bound should not be changed + $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null, + null); + $this->channel3 = new Grpc\Channel('localhost:10011', + ['credentials' => $channel_credentials]); + $channel3_info = $this->channel3->getChannelInfo(); + $this->assertEquals($channel3_info['target_upper_bound'], 3); + $this->assertEquals($channel3_info['target_current_size'], 2); + + // The upper bound should not be changed + $this->channel4 = new Grpc\Channel('localhost:10011', [ + "grpc_target_persist_bound" => 5, + ]); + $channel4_info = $this->channel4->getChannelInfo(); + $this->assertEquals($channel4_info['target_upper_bound'], 5); + $this->assertEquals($channel4_info['target_current_size'], 2); + } + + public function testPersistentChannelDefaultOutBound1() + { + $this->channel1 = new Grpc\Channel('localhost:10011', []); + // Make channel1 not IDLE. + $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + $channel1_info = $this->channel1->getChannelInfo(); + $this->assertConnecting($channel1_info['connectivity_status']); + + // Since channel1 is CONNECTING, channel 2 will not be persisted + $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null, + null); + $this->channel2 = new Grpc\Channel('localhost:10011', + ['credentials' => $channel_credentials]); + $channel2_info = $this->channel2->getChannelInfo(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $channel2_info['connectivity_status']); + + // By default, target 'localhost:10011' only persist one channel. + // Since channel1 is not Idle channel2 will not be persisted. + $plist_info = $this->channel1->getPersistentList(); + $this->assertEquals(1, count($plist_info)); + $this->assertArrayHasKey($channel1_info['key'], $plist_info); + $this->assertArrayNotHasKey($channel2_info['key'], $plist_info); + } + + public function testPersistentChannelDefaultOutBound2() + { + $this->channel1 = new Grpc\Channel('localhost:10011', []); + $channel1_info = $this->channel1->getChannelInfo(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $channel1_info['connectivity_status']); + + // Although channel1 is IDLE, channel1 still has reference to the underline + // gRPC channel. channel2 will not be persisted + $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null, + null); + $this->channel2 = new Grpc\Channel('localhost:10011', + ['credentials' => $channel_credentials]); + $channel2_info = $this->channel2->getChannelInfo(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $channel2_info['connectivity_status']); + + // By default, target 'localhost:10011' only persist one channel. + // Since channel1 Idle, channel2 will be persisted. + $plist_info = $this->channel1->getPersistentList(); + $this->assertEquals(1, count($plist_info)); + $this->assertArrayHasKey($channel1_info['key'], $plist_info); + $this->assertArrayNotHasKey($channel2_info['key'], $plist_info); + } + + public function testPersistentChannelDefaultOutBound3() + { + $this->channel1 = new Grpc\Channel('localhost:10011', []); + $channel1_info = $this->channel1->getChannelInfo(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $channel1_info['connectivity_status']); + + $this->channel1->close(); + // channel1 is closed, no reference holds to the underline channel. + // channel2 can be persisted. + $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null, + null); + $this->channel2 = new Grpc\Channel('localhost:10011', + ['credentials' => $channel_credentials]); + $channel2_info = $this->channel2->getChannelInfo(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $channel2_info['connectivity_status']); + + // By default, target 'localhost:10011' only persist one channel. + // Since channel1 Idle, channel2 will be persisted. + $plist_info = $this->channel2->getPersistentList(); + $this->assertEquals(1, count($plist_info)); + $this->assertArrayHasKey($channel2_info['key'], $plist_info); + $this->assertArrayNotHasKey($channel1_info['key'], $plist_info); + } + + public function testPersistentChannelTwoUpperBound() + { + $this->channel1 = new Grpc\Channel('localhost:10011', [ + "grpc_target_persist_bound" => 2, + ]); + $channel1_info = $this->channel1->getChannelInfo(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $channel1_info['connectivity_status']); + + // Since channel1 is IDLE, channel 1 will be deleted + $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null, + null); + $this->channel2 = new Grpc\Channel('localhost:10011', + ['credentials' => $channel_credentials]); + $channel2_info = $this->channel2->getChannelInfo(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $channel2_info['connectivity_status']); + + $plist_info = $this->channel1->getPersistentList(); + $this->assertEquals(2, count($plist_info)); + $this->assertArrayHasKey($channel1_info['key'], $plist_info); + $this->assertArrayHasKey($channel2_info['key'], $plist_info); + } + + public function testPersistentChannelTwoUpperBoundOutBound1() + { + $this->channel1 = new Grpc\Channel('localhost:10011', [ + "grpc_target_persist_bound" => 2, + ]); + $channel1_info = $this->channel1->getChannelInfo(); + + $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null, + null); + $this->channel2 = new Grpc\Channel('localhost:10011', + ['credentials' => $channel_credentials]); + $channel2_info = $this->channel2->getChannelInfo(); + + // Close channel1, so that new channel can be persisted. + $this->channel1->close(); + + $channel_credentials = Grpc\ChannelCredentials::createSsl("a", null, + null); + $this->channel3 = new Grpc\Channel('localhost:10011', + ['credentials' => $channel_credentials]); + $channel3_info = $this->channel3->getChannelInfo(); + + $plist_info = $this->channel1->getPersistentList(); + $this->assertEquals(2, count($plist_info)); + $this->assertArrayNotHasKey($channel1_info['key'], $plist_info); + $this->assertArrayHasKey($channel2_info['key'], $plist_info); + $this->assertArrayHasKey($channel3_info['key'], $plist_info); + } + + public function testPersistentChannelTwoUpperBoundOutBound2() + { + $this->channel1 = new Grpc\Channel('localhost:10011', [ + "grpc_target_persist_bound" => 2, + ]); + $channel1_info = $this->channel1->getChannelInfo(); + + $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null, + null); + $this->channel2 = new Grpc\Channel('localhost:10011', + ['credentials' => $channel_credentials]); + $channel2_info = $this->channel2->getChannelInfo(); + + // Close channel2, so that new channel can be persisted. + $this->channel2->close(); + + $channel_credentials = Grpc\ChannelCredentials::createSsl("a", null, + null); + $this->channel3 = new Grpc\Channel('localhost:10011', + ['credentials' => $channel_credentials]); + $channel3_info = $this->channel3->getChannelInfo(); + + $plist_info = $this->channel1->getPersistentList(); + $this->assertEquals(2, count($plist_info)); + $this->assertArrayHasKey($channel1_info['key'], $plist_info); + $this->assertArrayNotHasKey($channel2_info['key'], $plist_info); + $this->assertArrayHasKey($channel3_info['key'], $plist_info); + } + + public function testPersistentChannelTwoUpperBoundOutBound3() + { + $this->channel1 = new Grpc\Channel('localhost:10011', [ + "grpc_target_persist_bound" => 2, + ]); + $channel1_info = $this->channel1->getChannelInfo(); + + $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null, + null); + $this->channel2 = new Grpc\Channel('localhost:10011', + ['credentials' => $channel_credentials]); + $this->channel2->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel2); + $channel2_info = $this->channel2->getChannelInfo(); + $this->assertConnecting($channel2_info['connectivity_status']); + + // Only one channel will be deleted + $this->channel1->close(); + $this->channel2->close(); + + $channel_credentials = Grpc\ChannelCredentials::createSsl("a", null, + null); + $this->channel3 = new Grpc\Channel('localhost:10011', + ['credentials' => $channel_credentials]); + $channel3_info = $this->channel3->getChannelInfo(); + + // Only the Idle Channel will be deleted + $plist_info = $this->channel1->getPersistentList(); + $this->assertEquals(2, count($plist_info)); + $this->assertArrayNotHasKey($channel1_info['key'], $plist_info); + $this->assertArrayHasKey($channel2_info['key'], $plist_info); + $this->assertArrayHasKey($channel3_info['key'], $plist_info); + } + + public function testPersistentChannelTwoUpperBoundOutBound4() + { + $this->channel1 = new Grpc\Channel('localhost:10011', [ + "grpc_target_persist_bound" => 2, + ]); + $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + $channel1_info = $this->channel1->getChannelInfo(); + $this->assertConnecting($channel1_info['connectivity_status']); + + $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null, + null); + $this->channel2 = new Grpc\Channel('localhost:10011', + ['credentials' => $channel_credentials]); + $this->channel2->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel2); + $channel2_info = $this->channel2->getChannelInfo(); + $this->assertConnecting($channel2_info['connectivity_status']); + + $channel_credentials = Grpc\ChannelCredentials::createSsl("a", null, + null); + $this->channel3 = new Grpc\Channel('localhost:10011', + ['credentials' => $channel_credentials]); + $channel3_info = $this->channel3->getChannelInfo(); + + // Channel3 will not be persisted + $plist_info = $this->channel1->getPersistentList(); + $this->assertEquals(2, count($plist_info)); + $this->assertArrayHasKey($channel1_info['key'], $plist_info); + $this->assertArrayHasKey($channel2_info['key'], $plist_info); + $this->assertArrayNotHasKey($channel3_info['key'], $plist_info); + } +} -- cgit v1.2.3