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
pull/15218/head
ZhouyihaiDing 7 years ago
parent 23e17baa23
commit 9ef881e1ac
  1. 6
      src/php/bin/run_tests.sh
  2. 12
      src/php/ext/grpc/call.c
  3. 2
      src/php/ext/grpc/call.h
  4. 438
      src/php/ext/grpc/channel.c
  5. 10
      src/php/ext/grpc/channel.h
  6. 8
      src/php/ext/grpc/config.m4
  7. 6
      src/php/ext/grpc/php7_wrapper.h
  8. 3
      src/php/ext/grpc/php_grpc.c
  9. 237
      src/php/tests/unit_tests/ChannelTest.php
  10. 489
      src/php/tests/unit_tests/PersistentChannelTests/PersistentChannelTest.php
  11. 4
      tools/run_tests/helper_scripts/build_php.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

@ -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;

@ -27,6 +27,7 @@
#include <php_ini.h>
#include <ext/standard/info.h>
#include "php_grpc.h"
#include "channel.h"
#include <grpc/grpc.h>
@ -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

@ -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;
}

@ -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_ */

@ -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...

@ -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 */, \

@ -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();

@ -1,7 +1,7 @@
<?php
/*
*
* Copyright 2015 gRPC authors.
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -32,14 +32,14 @@ class ChannelTest extends PHPUnit_Framework_TestCase
public function testInsecureCredentials()
{
$this->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();

@ -0,0 +1,489 @@
<?php
/*
*
* Copyright 2015 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* @group persistent_list_bound_tests
*/
class PersistentListTest extends PHPUnit_Framework_TestCase
{
public function setUp()
{
}
public function tearDown()
{
$channel_clean_persistent =
new Grpc\Channel('localhost:50010', []);
$plist = $channel_clean_persistent->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);
}
}

@ -30,8 +30,8 @@ cd src/php
cd ext/grpc
phpize
if [ "$CONFIG" != "gcov" ] ; then
./configure --enable-grpc="$root"
./configure --enable-grpc="$root" --enable-tests
else
./configure --enable-grpc="$root" --enable-coverage
./configure --enable-grpc="$root" --enable-coverage --enable-tests
fi
make

Loading…
Cancel
Save