aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/php
diff options
context:
space:
mode:
authorGravatar Craig Tiller <ctiller@google.com>2017-08-29 14:16:14 -0700
committerGravatar Craig Tiller <ctiller@google.com>2017-08-29 14:16:14 -0700
commite944c5d4907732b2bc0a9c6e355ff1e6a65b37e2 (patch)
tree7be516e29013f54061921af50862ad736b8fb020 /src/php
parent33aeabad66e8083d47f47ddc4bafa4483f1585f8 (diff)
parent33b51aaa755b2e5f10aade60b67f216bff86cb36 (diff)
Merge branch 'stats' into stats_histo
Diffstat (limited to 'src/php')
-rw-r--r--src/php/composer.json2
-rw-r--r--src/php/ext/grpc/call.c8
-rw-r--r--src/php/ext/grpc/call_credentials.c14
-rw-r--r--src/php/ext/grpc/channel.c288
-rwxr-xr-xsrc/php/ext/grpc/channel.h27
-rw-r--r--src/php/ext/grpc/channel_credentials.c38
-rwxr-xr-xsrc/php/ext/grpc/channel_credentials.h2
-rw-r--r--src/php/ext/grpc/php7_wrapper.h28
-rw-r--r--src/php/ext/grpc/php_grpc.c44
-rw-r--r--src/php/ext/grpc/php_grpc.h10
-rw-r--r--src/php/ext/grpc/version.h2
-rw-r--r--src/php/lib/Grpc/ServerStreamingCall.php28
-rw-r--r--src/php/lib/Grpc/UnaryCall.php28
-rw-r--r--src/php/tests/unit_tests/CallTest.php3
-rw-r--r--src/php/tests/unit_tests/ChannelTest.php458
-rw-r--r--src/php/tests/unit_tests/EndToEndTest.php7
-rw-r--r--src/php/tests/unit_tests/SecureEndToEndTest.php3
17 files changed, 889 insertions, 101 deletions
diff --git a/src/php/composer.json b/src/php/composer.json
index c6a74c5c84..3606a18f34 100644
--- a/src/php/composer.json
+++ b/src/php/composer.json
@@ -2,7 +2,7 @@
"name": "grpc/grpc-dev",
"description": "gRPC library for PHP - for Developement use only",
"license": "Apache-2.0",
- "version": "1.5.0",
+ "version": "1.7.0",
"require": {
"php": ">=5.5.0",
"google/protobuf": "^v3.3.0"
diff --git a/src/php/ext/grpc/call.c b/src/php/ext/grpc/call.c
index 2f67e5cee7..c4997f720d 100644
--- a/src/php/ext/grpc/call.c
+++ b/src/php/ext/grpc/call.c
@@ -214,10 +214,12 @@ PHP_METHOD(Call, __construct) {
return;
}
wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(channel_obj);
- if (channel->wrapped == NULL) {
+ gpr_mu_lock(&channel->wrapper->mu);
+ if (channel->wrapper->wrapped == NULL) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Call cannot be constructed from a closed Channel",
1 TSRMLS_CC);
+ gpr_mu_unlock(&channel->wrapper->mu);
return;
}
add_property_zval(getThis(), "channel", channel_obj);
@@ -226,13 +228,15 @@ PHP_METHOD(Call, __construct) {
grpc_slice host_slice = host_override != NULL ?
grpc_slice_from_copied_string(host_override) : grpc_empty_slice();
call->wrapped =
- grpc_channel_create_call(channel->wrapped, NULL, GRPC_PROPAGATE_DEFAULTS,
+ grpc_channel_create_call(channel->wrapper->wrapped, NULL,
+ GRPC_PROPAGATE_DEFAULTS,
completion_queue, method_slice,
host_override != NULL ? &host_slice : NULL,
deadline->wrapped, NULL);
grpc_slice_unref(method_slice);
grpc_slice_unref(host_slice);
call->owned = true;
+ gpr_mu_unlock(&channel->wrapper->mu);
}
/**
diff --git a/src/php/ext/grpc/call_credentials.c b/src/php/ext/grpc/call_credentials.c
index a990206c08..1eee8645df 100644
--- a/src/php/ext/grpc/call_credentials.c
+++ b/src/php/ext/grpc/call_credentials.c
@@ -109,8 +109,8 @@ PHP_METHOD(CallCredentials, createFromPlugin) {
zend_fcall_info *fci;
zend_fcall_info_cache *fci_cache;
- fci = (zend_fcall_info *)emalloc(sizeof(zend_fcall_info));
- fci_cache = (zend_fcall_info_cache *)emalloc(sizeof(zend_fcall_info_cache));
+ fci = (zend_fcall_info *)malloc(sizeof(zend_fcall_info));
+ fci_cache = (zend_fcall_info_cache *)malloc(sizeof(zend_fcall_info_cache));
memset(fci, 0, sizeof(zend_fcall_info));
memset(fci_cache, 0, sizeof(zend_fcall_info_cache));
@@ -123,7 +123,7 @@ PHP_METHOD(CallCredentials, createFromPlugin) {
}
plugin_state *state;
- state = (plugin_state *)emalloc(sizeof(plugin_state));
+ state = (plugin_state *)malloc(sizeof(plugin_state));
memset(state, 0, sizeof(plugin_state));
/* save the user provided PHP callback function */
@@ -179,7 +179,7 @@ void plugin_get_metadata(void *ptr, grpc_auth_metadata_context context,
grpc_metadata_array metadata;
bool cleanup = true;
- if (Z_TYPE_P(retval) != IS_ARRAY) {
+ if (retval == NULL || Z_TYPE_P(retval) != IS_ARRAY) {
cleanup = false;
code = GRPC_STATUS_INVALID_ARGUMENT;
} else if (!create_metadata_array(retval, &metadata)) {
@@ -210,13 +210,13 @@ void plugin_get_metadata(void *ptr, grpc_auth_metadata_context context,
/* Cleanup function for plugin creds API */
void plugin_destroy_state(void *ptr) {
plugin_state *state = (plugin_state *)ptr;
- efree(state->fci);
- efree(state->fci_cache);
+ free(state->fci);
+ free(state->fci_cache);
#if PHP_MAJOR_VERSION < 7
PHP_GRPC_FREE_STD_ZVAL(state->fci->params);
PHP_GRPC_FREE_STD_ZVAL(state->fci->retval);
#endif
- efree(state);
+ free(state);
}
ZEND_BEGIN_ARG_INFO_EX(arginfo_createComposite, 0, 0, 2)
diff --git a/src/php/ext/grpc/channel.c b/src/php/ext/grpc/channel.c
index 6c432d2818..dc3acc89bb 100644
--- a/src/php/ext/grpc/channel.c
+++ b/src/php/ext/grpc/channel.c
@@ -25,6 +25,13 @@
#include <php.h>
#include <php_ini.h>
#include <ext/standard/info.h>
+#include <ext/standard/php_var.h>
+#include <ext/standard/sha1.h>
+#if PHP_MAJOR_VERSION < 7
+#include <ext/standard/php_smart_str.h>
+#else
+#include <zend_smart_str.h>
+#endif
#include <ext/spl/spl_exceptions.h>
#include "php_grpc.h"
@@ -44,11 +51,27 @@ zend_class_entry *grpc_ce_channel;
#if PHP_MAJOR_VERSION >= 7
static zend_object_handlers channel_ce_handlers;
#endif
+static gpr_mu global_persistent_list_mu;
+int le_plink;
/* Frees and destroys an instance of wrapped_grpc_channel */
PHP_GRPC_FREE_WRAPPED_FUNC_START(wrapped_grpc_channel)
- if (p->wrapped != NULL) {
- grpc_channel_destroy(p->wrapped);
+ if (p->wrapper != NULL) {
+ gpr_mu_lock(&p->wrapper->mu);
+ if (p->wrapper->wrapped != NULL) {
+ php_grpc_zend_resource *rsrc;
+ php_grpc_int key_len = strlen(p->wrapper->key);
+ // only destroy the channel here if not found in the persistent list
+ gpr_mu_lock(&global_persistent_list_mu);
+ if (!(PHP_GRPC_PERSISTENT_LIST_FIND(&EG(persistent_list), p->wrapper->key,
+ key_len, rsrc))) {
+ grpc_channel_destroy(p->wrapper->wrapped);
+ free(p->wrapper->target);
+ free(p->wrapper->args_hashstr);
+ }
+ gpr_mu_unlock(&global_persistent_list_mu);
+ }
+ gpr_mu_unlock(&p->wrapper->mu);
}
PHP_GRPC_FREE_WRAPPED_FUNC_END()
@@ -62,15 +85,15 @@ php_grpc_zend_object create_wrapped_grpc_channel(zend_class_entry *class_type
PHP_GRPC_FREE_CLASS_OBJECT(wrapped_grpc_channel, channel_ce_handlers);
}
-void php_grpc_read_args_array(zval *args_array,
- grpc_channel_args *args TSRMLS_DC) {
+int php_grpc_read_args_array(zval *args_array,
+ grpc_channel_args *args TSRMLS_DC) {
HashTable *array_hash;
int args_index;
array_hash = Z_ARRVAL_P(args_array);
if (!array_hash) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"array_hash is NULL", 1 TSRMLS_CC);
- return;
+ return FAILURE;
}
args->num_args = zend_hash_num_elements(array_hash);
args->args = ecalloc(args->num_args, sizeof(grpc_arg));
@@ -84,7 +107,7 @@ void php_grpc_read_args_array(zval *args_array,
if (key_type != HASH_KEY_IS_STRING) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"args keys must be strings", 1 TSRMLS_CC);
- return;
+ return FAILURE;
}
args->args[args_index].key = key;
switch (Z_TYPE_P(data)) {
@@ -99,16 +122,76 @@ void php_grpc_read_args_array(zval *args_array,
default:
zend_throw_exception(spl_ce_InvalidArgumentException,
"args values must be int or string", 1 TSRMLS_CC);
- return;
+ return FAILURE;
}
args_index++;
PHP_GRPC_HASH_FOREACH_END()
+ return SUCCESS;
+}
+
+void generate_sha1_str(char *sha1str, char *str, php_grpc_int len) {
+ PHP_SHA1_CTX context;
+ unsigned char digest[20];
+ sha1str[0] = '\0';
+ PHP_SHA1Init(&context);
+ PHP_GRPC_SHA1Update(&context, str, len);
+ PHP_SHA1Final(digest, &context);
+ make_sha1_digest(sha1str, digest);
+}
+
+void create_channel(
+ wrapped_grpc_channel *channel,
+ char *target,
+ grpc_channel_args args,
+ wrapped_grpc_channel_credentials *creds) {
+ if (creds == NULL) {
+ channel->wrapper->wrapped = grpc_insecure_channel_create(target, &args,
+ NULL);
+ } else {
+ channel->wrapper->wrapped =
+ grpc_secure_channel_create(creds->wrapped, target, &args, NULL);
+ }
+ efree(args.args);
+}
+
+void create_and_add_channel_to_persistent_list(
+ wrapped_grpc_channel *channel,
+ char *target,
+ grpc_channel_args args,
+ wrapped_grpc_channel_credentials *creds,
+ char *key,
+ php_grpc_int key_len TSRMLS_DC) {
+ php_grpc_zend_resource new_rsrc;
+ channel_persistent_le_t *le;
+ // this links each persistent list entry to a destructor
+ new_rsrc.type = le_plink;
+ le = malloc(sizeof(channel_persistent_le_t));
+
+ create_channel(channel, target, args, creds);
+
+ le->channel = channel->wrapper;
+ new_rsrc.ptr = le;
+ gpr_mu_lock(&global_persistent_list_mu);
+ PHP_GRPC_PERSISTENT_LIST_UPDATE(&EG(persistent_list), key, key_len,
+ (void *)&new_rsrc);
+ gpr_mu_unlock(&global_persistent_list_mu);
}
/**
- * Construct an instance of the Channel class. If the $args array contains a
- * "credentials" key mapping to a ChannelCredentials object, a secure channel
- * will be created with those credentials.
+ * Construct an instance of the Channel class.
+ *
+ * By default, the underlying grpc_channel is "persistent". That is, given
+ * the same set of parameters passed to the constructor, the same underlying
+ * grpc_channel will be returned.
+ *
+ * If the $args array contains a "credentials" key mapping to a
+ * ChannelCredentials object, a secure channel will be created with those
+ * credentials.
+ *
+ * If the $args array contains a "force_new" key mapping to a boolean value
+ * of "true", a new and separate underlying grpc_channel will be created
+ * and returned. This will not affect existing channels.
+ *
* @param string $target The hostname to associate with this channel
* @param array $args_array The arguments to pass to the Channel
*/
@@ -121,6 +204,9 @@ PHP_METHOD(Channel, __construct) {
grpc_channel_args args;
HashTable *array_hash;
wrapped_grpc_channel_credentials *creds = NULL;
+ php_grpc_zend_resource *rsrc;
+ bool force_new = false;
+ zval *force_new_obj = NULL;
/* "sa" == 1 string, 1 array */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", &target,
@@ -131,7 +217,7 @@ PHP_METHOD(Channel, __construct) {
}
array_hash = Z_ARRVAL_P(args_array);
if (php_grpc_zend_hash_find(array_hash, "credentials", sizeof("credentials"),
- (void **)&creds_obj) == SUCCESS) {
+ (void **)&creds_obj) == SUCCESS) {
if (Z_TYPE_P(creds_obj) == IS_NULL) {
creds = NULL;
php_grpc_zend_hash_del(array_hash, "credentials", sizeof("credentials"));
@@ -146,14 +232,78 @@ PHP_METHOD(Channel, __construct) {
php_grpc_zend_hash_del(array_hash, "credentials", sizeof("credentials"));
}
}
- php_grpc_read_args_array(args_array, &args TSRMLS_CC);
- if (creds == NULL) {
- channel->wrapped = grpc_insecure_channel_create(target, &args, NULL);
+ if (php_grpc_zend_hash_find(array_hash, "force_new", sizeof("force_new"),
+ (void **)&force_new_obj) == SUCCESS) {
+ if (PHP_GRPC_BVAL_IS_TRUE(force_new_obj)) {
+ force_new = true;
+ }
+ php_grpc_zend_hash_del(array_hash, "force_new", sizeof("force_new"));
+ }
+
+ // parse the rest of the channel args array
+ if (php_grpc_read_args_array(args_array, &args TSRMLS_CC) == FAILURE) {
+ return;
+ }
+
+ // Construct a hashkey for the persistent channel
+ // Currently, the hashkey contains 3 parts:
+ // 1. hostname
+ // 2. hash value of the channel args array (excluding "credentials"
+ // and "force_new")
+ // 3. (optional) hash value of the ChannelCredentials object
+ php_serialize_data_t var_hash;
+ smart_str buf = {0};
+ PHP_VAR_SERIALIZE_INIT(var_hash);
+ PHP_GRPC_VAR_SERIALIZE(&buf, args_array, &var_hash);
+ PHP_VAR_SERIALIZE_DESTROY(var_hash);
+
+ char sha1str[41];
+ generate_sha1_str(sha1str, PHP_GRPC_SERIALIZED_BUF_STR(buf),
+ PHP_GRPC_SERIALIZED_BUF_LEN(buf));
+
+ php_grpc_int key_len = target_length + strlen(sha1str);
+ if (creds != NULL && creds->hashstr != NULL) {
+ key_len += strlen(creds->hashstr);
+ }
+ char *key = malloc(key_len + 1);
+ strcpy(key, target);
+ strcat(key, sha1str);
+ if (creds != NULL && creds->hashstr != NULL) {
+ strcat(key, creds->hashstr);
+ }
+ channel->wrapper = malloc(sizeof(grpc_channel_wrapper));
+ channel->wrapper->key = key;
+ channel->wrapper->target = strdup(target);
+ channel->wrapper->args_hashstr = strdup(sha1str);
+ if (creds != NULL && creds->hashstr != NULL) {
+ channel->wrapper->creds_hashstr = creds->hashstr;
+ }
+ gpr_mu_init(&channel->wrapper->mu);
+ smart_str_free(&buf);
+
+ if (force_new || (creds != NULL && creds->has_call_creds)) {
+ // If the ChannelCredentials object was composed with a CallCredentials
+ // object, there is no way we can tell them apart. Do NOT persist
+ // them. They should be individually destroyed.
+ create_channel(channel, target, args, creds);
+ } else if (!(PHP_GRPC_PERSISTENT_LIST_FIND(&EG(persistent_list), key,
+ key_len, rsrc))) {
+ create_and_add_channel_to_persistent_list(
+ channel, target, args, creds, key, key_len TSRMLS_CC);
} else {
- channel->wrapped =
- grpc_secure_channel_create(creds->wrapped, target, &args, NULL);
+ // Found a previously stored channel in the persistent list
+ channel_persistent_le_t *le = (channel_persistent_le_t *)rsrc->ptr;
+ if (strcmp(target, le->channel->target) != 0 ||
+ strcmp(sha1str, le->channel->args_hashstr) != 0 ||
+ (creds != NULL && creds->hashstr != NULL &&
+ strcmp(creds->hashstr, le->channel->creds_hashstr) != 0)) {
+ // somehow hash collision
+ create_and_add_channel_to_persistent_list(
+ channel, target, args, creds, key, key_len TSRMLS_CC);
+ } else {
+ channel->wrapper = le->channel;
+ }
}
- efree(args.args);
}
/**
@@ -162,7 +312,16 @@ PHP_METHOD(Channel, __construct) {
*/
PHP_METHOD(Channel, getTarget) {
wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
- PHP_GRPC_RETURN_STRING(grpc_channel_get_target(channel->wrapped), 1);
+ gpr_mu_lock(&channel->wrapper->mu);
+ if (channel->wrapper->wrapped == NULL) {
+ zend_throw_exception(spl_ce_RuntimeException,
+ "Channel already closed", 1 TSRMLS_CC);
+ gpr_mu_unlock(&channel->wrapper->mu);
+ return;
+ }
+ char *target = grpc_channel_get_target(channel->wrapper->wrapped);
+ gpr_mu_unlock(&channel->wrapper->mu);
+ PHP_GRPC_RETURN_STRING(target, 1);
}
/**
@@ -172,6 +331,14 @@ PHP_METHOD(Channel, getTarget) {
*/
PHP_METHOD(Channel, getConnectivityState) {
wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
+ gpr_mu_lock(&channel->wrapper->mu);
+ if (channel->wrapper->wrapped == NULL) {
+ zend_throw_exception(spl_ce_RuntimeException,
+ "Channel already closed", 1 TSRMLS_CC);
+ gpr_mu_unlock(&channel->wrapper->mu);
+ return;
+ }
+
bool try_to_connect = false;
/* "|b" == 1 optional bool */
@@ -179,10 +346,18 @@ PHP_METHOD(Channel, getConnectivityState) {
== FAILURE) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"getConnectivityState expects a bool", 1 TSRMLS_CC);
+ gpr_mu_unlock(&channel->wrapper->mu);
return;
}
- RETURN_LONG(grpc_channel_check_connectivity_state(channel->wrapped,
- (int)try_to_connect));
+ int state = grpc_channel_check_connectivity_state(channel->wrapper->wrapped,
+ (int)try_to_connect);
+ // this can happen if another shared Channel object close the underlying
+ // channel
+ if (state == GRPC_CHANNEL_SHUTDOWN) {
+ channel->wrapper->wrapped = NULL;
+ }
+ gpr_mu_unlock(&channel->wrapper->mu);
+ RETURN_LONG(state);
}
/**
@@ -194,25 +369,37 @@ PHP_METHOD(Channel, getConnectivityState) {
*/
PHP_METHOD(Channel, watchConnectivityState) {
wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
+ gpr_mu_lock(&channel->wrapper->mu);
+ if (channel->wrapper->wrapped == NULL) {
+ zend_throw_exception(spl_ce_RuntimeException,
+ "Channel already closed", 1 TSRMLS_CC);
+ gpr_mu_unlock(&channel->wrapper->mu);
+ return;
+ }
+
php_grpc_long last_state;
zval *deadline_obj;
/* "lO" == 1 long 1 object */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lO",
- &last_state, &deadline_obj, grpc_ce_timeval) == FAILURE) {
+ &last_state, &deadline_obj,
+ grpc_ce_timeval) == FAILURE) {
zend_throw_exception(spl_ce_InvalidArgumentException,
- "watchConnectivityState expects 1 long 1 timeval", 1 TSRMLS_CC);
+ "watchConnectivityState expects 1 long 1 timeval",
+ 1 TSRMLS_CC);
+ gpr_mu_unlock(&channel->wrapper->mu);
return;
}
wrapped_grpc_timeval *deadline = Z_WRAPPED_GRPC_TIMEVAL_P(deadline_obj);
- grpc_channel_watch_connectivity_state(channel->wrapped,
+ grpc_channel_watch_connectivity_state(channel->wrapper->wrapped,
(grpc_connectivity_state)last_state,
deadline->wrapped, completion_queue,
NULL);
grpc_event event =
- grpc_completion_queue_pluck(completion_queue, NULL,
- gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
+ grpc_completion_queue_pluck(completion_queue, NULL,
+ gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
+ gpr_mu_unlock(&channel->wrapper->mu);
RETURN_BOOL(event.success);
}
@@ -222,9 +409,48 @@ PHP_METHOD(Channel, watchConnectivityState) {
*/
PHP_METHOD(Channel, close) {
wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
- if (channel->wrapped != NULL) {
- grpc_channel_destroy(channel->wrapped);
- channel->wrapped = NULL;
+ gpr_mu_lock(&channel->wrapper->mu);
+ if (channel->wrapper->wrapped != NULL) {
+ grpc_channel_destroy(channel->wrapper->wrapped);
+ free(channel->wrapper->target);
+ free(channel->wrapper->args_hashstr);
+ channel->wrapper->wrapped = NULL;
+
+ php_grpc_delete_persistent_list_entry(channel->wrapper->key,
+ strlen(channel->wrapper->key)
+ TSRMLS_CC);
+ }
+ gpr_mu_unlock(&channel->wrapper->mu);
+}
+
+// Delete an entry from the persistent list
+// Note: this does not destroy or close the underlying grpc_channel
+void php_grpc_delete_persistent_list_entry(char *key, php_grpc_int key_len
+ TSRMLS_DC) {
+ php_grpc_zend_resource *rsrc;
+ gpr_mu_lock(&global_persistent_list_mu);
+ if (PHP_GRPC_PERSISTENT_LIST_FIND(&EG(persistent_list), key,
+ key_len, rsrc)) {
+ channel_persistent_le_t *le;
+ le = (channel_persistent_le_t *)rsrc->ptr;
+ le->channel = NULL;
+ php_grpc_zend_hash_del(&EG(persistent_list), key, key_len+1);
+ }
+ gpr_mu_unlock(&global_persistent_list_mu);
+}
+
+// A destructor associated with each list entry from the persistent list
+static void php_grpc_channel_plink_dtor(php_grpc_zend_resource *rsrc
+ TSRMLS_DC) {
+ channel_persistent_le_t *le = (channel_persistent_le_t *)rsrc->ptr;
+ if (le->channel != NULL) {
+ gpr_mu_lock(&le->channel->mu);
+ if (le->channel->wrapped != NULL) {
+ grpc_channel_destroy(le->channel->wrapped);
+ free(le->channel->target);
+ free(le->channel->args_hashstr);
+ }
+ gpr_mu_unlock(&le->channel->mu);
}
}
@@ -262,10 +488,14 @@ static zend_function_entry channel_methods[] = {
PHP_FE_END
};
-void grpc_init_channel(TSRMLS_D) {
+GRPC_STARTUP_FUNCTION(channel) {
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, "Grpc\\Channel", channel_methods);
ce.create_object = create_wrapped_grpc_channel;
grpc_ce_channel = zend_register_internal_class(&ce TSRMLS_CC);
+ gpr_mu_init(&global_persistent_list_mu);
+ le_plink = zend_register_list_destructors_ex(
+ NULL, php_grpc_channel_plink_dtor, "Persistent Channel", module_number);
PHP_GRPC_INIT_HANDLER(wrapped_grpc_channel, channel_ce_handlers);
+ return SUCCESS;
}
diff --git a/src/php/ext/grpc/channel.h b/src/php/ext/grpc/channel.h
index 45c9744135..69adc4782c 100755
--- a/src/php/ext/grpc/channel.h
+++ b/src/php/ext/grpc/channel.h
@@ -33,9 +33,18 @@
/* Class entry for the PHP Channel class */
extern zend_class_entry *grpc_ce_channel;
+typedef struct _grpc_channel_wrapper {
+ grpc_channel *wrapped;
+ char *key;
+ char *target;
+ char *args_hashstr;
+ char *creds_hashstr;
+ gpr_mu mu;
+} grpc_channel_wrapper;
+
/* Wrapper struct for grpc_channel that can be associated with a PHP object */
PHP_GRPC_WRAP_OBJECT_START(wrapped_grpc_channel)
- grpc_channel *wrapped;
+ grpc_channel_wrapper *wrapper;
PHP_GRPC_WRAP_OBJECT_END(wrapped_grpc_channel)
#if PHP_MAJOR_VERSION < 7
@@ -57,10 +66,20 @@ static inline wrapped_grpc_channel
#endif /* PHP_MAJOR_VERSION */
/* Initializes the Channel class */
-void grpc_init_channel(TSRMLS_D);
+GRPC_STARTUP_FUNCTION(channel);
/* Iterates through a PHP array and populates args with the contents */
-void php_grpc_read_args_array(zval *args_array, grpc_channel_args *args
- TSRMLS_DC);
+int php_grpc_read_args_array(zval *args_array, grpc_channel_args *args
+ TSRMLS_DC);
+
+void generate_sha1_str(char *sha1str, char *str, php_grpc_int len);
+
+void php_grpc_delete_persistent_list_entry(char *key, php_grpc_int key_len
+ TSRMLS_DC);
+
+typedef struct _channel_persistent_le {
+ grpc_channel_wrapper *channel;
+} channel_persistent_le_t;
+
#endif /* NET_GRPC_PHP_GRPC_CHANNEL_H_ */
diff --git a/src/php/ext/grpc/channel_credentials.c b/src/php/ext/grpc/channel_credentials.c
index 40629c8b00..86e4f46c67 100644
--- a/src/php/ext/grpc/channel_credentials.c
+++ b/src/php/ext/grpc/channel_credentials.c
@@ -26,7 +26,9 @@
#include <php.h>
#include <php_ini.h>
#include <ext/standard/info.h>
+#include <ext/standard/sha1.h>
#include <ext/spl/spl_exceptions.h>
+#include "channel.h"
#include "php_grpc.h"
#include <zend_exceptions.h>
@@ -69,14 +71,17 @@ php_grpc_zend_object create_wrapped_grpc_channel_credentials(
channel_credentials_ce_handlers);
}
-zval *grpc_php_wrap_channel_credentials(grpc_channel_credentials
- *wrapped TSRMLS_DC) {
+zval *grpc_php_wrap_channel_credentials(grpc_channel_credentials *wrapped,
+ char *hashstr,
+ zend_bool has_call_creds TSRMLS_DC) {
zval *credentials_object;
PHP_GRPC_MAKE_STD_ZVAL(credentials_object);
object_init_ex(credentials_object, grpc_ce_channel_credentials);
wrapped_grpc_channel_credentials *credentials =
Z_WRAPPED_GRPC_CHANNEL_CREDS_P(credentials_object);
credentials->wrapped = wrapped;
+ credentials->hashstr = hashstr;
+ credentials->has_call_creds = has_call_creds;
return credentials_object;
}
@@ -106,7 +111,8 @@ PHP_METHOD(ChannelCredentials, setDefaultRootsPem) {
*/
PHP_METHOD(ChannelCredentials, createDefault) {
grpc_channel_credentials *creds = grpc_google_default_credentials_create();
- zval *creds_object = grpc_php_wrap_channel_credentials(creds TSRMLS_CC);
+ zval *creds_object = grpc_php_wrap_channel_credentials(creds, NULL, false
+ TSRMLS_CC);
RETURN_DESTROY_ZVAL(creds_object);
}
@@ -129,6 +135,8 @@ PHP_METHOD(ChannelCredentials, createSsl) {
pem_key_cert_pair.private_key = pem_key_cert_pair.cert_chain = NULL;
+ grpc_set_ssl_roots_override_callback(get_ssl_roots_override);
+
/* "|s!s!s!" == 3 optional nullable strings */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!s!s!",
&pem_root_certs, &root_certs_length,
@@ -140,10 +148,25 @@ PHP_METHOD(ChannelCredentials, createSsl) {
"createSsl expects 3 optional strings", 1 TSRMLS_CC);
return;
}
+
+ php_grpc_int hashkey_len = root_certs_length + cert_chain_length;
+ char *hashkey = emalloc(hashkey_len);
+ if (root_certs_length > 0) {
+ strcpy(hashkey, pem_root_certs);
+ }
+ if (cert_chain_length > 0) {
+ strcpy(hashkey, pem_key_cert_pair.cert_chain);
+ }
+
+ char *hashstr = malloc(41);
+ generate_sha1_str(hashstr, hashkey, hashkey_len);
+
grpc_channel_credentials *creds = grpc_ssl_credentials_create(
pem_root_certs,
pem_key_cert_pair.private_key == NULL ? NULL : &pem_key_cert_pair, NULL);
- zval *creds_object = grpc_php_wrap_channel_credentials(creds TSRMLS_CC);
+ zval *creds_object = grpc_php_wrap_channel_credentials(creds, hashstr, false
+ TSRMLS_CC);
+ efree(hashkey);
RETURN_DESTROY_ZVAL(creds_object);
}
@@ -157,6 +180,8 @@ PHP_METHOD(ChannelCredentials, createComposite) {
zval *cred1_obj;
zval *cred2_obj;
+ grpc_set_ssl_roots_override_callback(get_ssl_roots_override);
+
/* "OO" == 2 Objects */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "OO", &cred1_obj,
grpc_ce_channel_credentials, &cred2_obj,
@@ -172,7 +197,9 @@ PHP_METHOD(ChannelCredentials, createComposite) {
grpc_channel_credentials *creds =
grpc_composite_channel_credentials_create(cred1->wrapped, cred2->wrapped,
NULL);
- zval *creds_object = grpc_php_wrap_channel_credentials(creds TSRMLS_CC);
+ zval *creds_object =
+ grpc_php_wrap_channel_credentials(creds, cred1->hashstr, true
+ TSRMLS_CC);
RETURN_DESTROY_ZVAL(creds_object);
}
@@ -223,7 +250,6 @@ void grpc_init_channel_credentials(TSRMLS_D) {
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, "Grpc\\ChannelCredentials",
channel_credentials_methods);
- grpc_set_ssl_roots_override_callback(get_ssl_roots_override);
ce.create_object = create_wrapped_grpc_channel_credentials;
grpc_ce_channel_credentials = zend_register_internal_class(&ce TSRMLS_CC);
PHP_GRPC_INIT_HANDLER(wrapped_grpc_channel_credentials,
diff --git a/src/php/ext/grpc/channel_credentials.h b/src/php/ext/grpc/channel_credentials.h
index 28c7f2c1d3..357d732642 100755
--- a/src/php/ext/grpc/channel_credentials.h
+++ b/src/php/ext/grpc/channel_credentials.h
@@ -38,6 +38,8 @@ extern zend_class_entry *grpc_ce_channel_credentials;
* with a PHP object */
PHP_GRPC_WRAP_OBJECT_START(wrapped_grpc_channel_credentials)
grpc_channel_credentials *wrapped;
+ char *hashstr;
+ zend_bool has_call_creds;
PHP_GRPC_WRAP_OBJECT_END(wrapped_grpc_channel_credentials)
#if PHP_MAJOR_VERSION < 7
diff --git a/src/php/ext/grpc/php7_wrapper.h b/src/php/ext/grpc/php7_wrapper.h
index d4b4c262a7..96091f9dad 100644
--- a/src/php/ext/grpc/php7_wrapper.h
+++ b/src/php/ext/grpc/php7_wrapper.h
@@ -113,6 +113,20 @@ static inline int php_grpc_zend_hash_find(HashTable *ht, char *key, int len,
}
#define php_grpc_zend_hash_del zend_hash_del
+#define php_grpc_zend_resource zend_rsrc_list_entry
+
+#define PHP_GRPC_BVAL_IS_TRUE(zv) Z_LVAL_P(zv)
+#define PHP_GRPC_VAR_SERIALIZE(buf, zv, hash) \
+ php_var_serialize(buf, &zv, hash TSRMLS_CC)
+#define PHP_GRPC_SERIALIZED_BUF_STR(buf) buf.c
+#define PHP_GRPC_SERIALIZED_BUF_LEN(buf) buf.len
+#define PHP_GRPC_SHA1Update(cxt, str, len) \
+ PHP_SHA1Update(cxt, (const unsigned char *)str, len)
+#define PHP_GRPC_PERSISTENT_LIST_FIND(plist, key, len, rsrc) \
+ zend_hash_find(plist, key, len+1, (void **)&rsrc) != FAILURE
+#define PHP_GRPC_PERSISTENT_LIST_UPDATE(plist, key, len, rsrc) \
+ zend_hash_update(plist, key, len+1, rsrc, sizeof(php_grpc_zend_resource), \
+ NULL)
#define PHP_GRPC_GET_CLASS_ENTRY(object) zend_get_class_entry(object TSRMLS_CC)
@@ -200,6 +214,20 @@ static inline int php_grpc_zend_hash_find(HashTable *ht, char *key, int len,
static inline int php_grpc_zend_hash_del(HashTable *ht, char *key, int len) {
return zend_hash_str_del(ht, key, len - 1);
}
+#define php_grpc_zend_resource zend_resource
+
+#define PHP_GRPC_BVAL_IS_TRUE(zv) Z_TYPE_P(zv) == IS_TRUE
+#define PHP_GRPC_VAR_SERIALIZE(buf, zv, hash) \
+ php_var_serialize(buf, zv, hash)
+#define PHP_GRPC_SERIALIZED_BUF_STR(buf) ZSTR_VAL(buf.s)
+#define PHP_GRPC_SERIALIZED_BUF_LEN(buf) ZSTR_LEN(buf.s)
+#define PHP_GRPC_SHA1Update(cxt, str, len) \
+ PHP_SHA1Update(cxt, (unsigned char *)str, len)
+#define PHP_GRPC_PERSISTENT_LIST_FIND(plist, key, len, rsrc) \
+ (rsrc = zend_hash_str_find_ptr(plist, key, len)) != NULL
+#define PHP_GRPC_PERSISTENT_LIST_UPDATE(plist, key, len, rsrc) \
+ zend_hash_str_update_mem(plist, key, len, rsrc, \
+ sizeof(php_grpc_zend_resource))
#define PHP_GRPC_GET_CLASS_ENTRY(object) Z_OBJ_P(object)->ce
diff --git a/src/php/ext/grpc/php_grpc.c b/src/php/ext/grpc/php_grpc.c
index 281b9e6aba..4ed56de993 100644
--- a/src/php/ext/grpc/php_grpc.c
+++ b/src/php/ext/grpc/php_grpc.c
@@ -34,7 +34,8 @@
#include <ext/standard/info.h>
#include "php_grpc.h"
-// ZEND_DECLARE_MODULE_GLOBALS(grpc)
+ZEND_DECLARE_MODULE_GLOBALS(grpc)
+static PHP_GINIT_FUNCTION(grpc);
/* {{{ grpc_functions[]
*
@@ -55,13 +56,17 @@ zend_module_entry grpc_module_entry = {
grpc_functions,
PHP_MINIT(grpc),
PHP_MSHUTDOWN(grpc),
- NULL,
+ PHP_RINIT(grpc),
NULL,
PHP_MINFO(grpc),
#if ZEND_MODULE_API_NO >= 20010901
PHP_GRPC_VERSION,
#endif
- STANDARD_MODULE_PROPERTIES};
+ PHP_MODULE_GLOBALS(grpc),
+ PHP_GINIT(grpc),
+ NULL,
+ NULL,
+ STANDARD_MODULE_PROPERTIES_EX};
/* }}} */
#ifdef COMPILE_DL_GRPC
@@ -99,7 +104,6 @@ PHP_MINIT_FUNCTION(grpc) {
REGISTER_INI_ENTRIES();
*/
/* Register call error constants */
- grpc_init();
REGISTER_LONG_CONSTANT("Grpc\\CALL_OK", GRPC_CALL_OK,
CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR", GRPC_CALL_ERROR,
@@ -221,13 +225,12 @@ PHP_MINIT_FUNCTION(grpc) {
CONST_CS | CONST_PERSISTENT);
grpc_init_call(TSRMLS_C);
- grpc_init_channel(TSRMLS_C);
+ GRPC_STARTUP(channel);
grpc_init_server(TSRMLS_C);
grpc_init_timeval(TSRMLS_C);
grpc_init_channel_credentials(TSRMLS_C);
grpc_init_call_credentials(TSRMLS_C);
grpc_init_server_credentials(TSRMLS_C);
- grpc_php_init_completion_queue(TSRMLS_C);
return SUCCESS;
}
/* }}} */
@@ -240,9 +243,12 @@ PHP_MSHUTDOWN_FUNCTION(grpc) {
*/
// WARNING: This function IS being called by PHP when the extension
// is unloaded but the logs were somehow suppressed.
- grpc_shutdown_timeval(TSRMLS_C);
- grpc_php_shutdown_completion_queue(TSRMLS_C);
- grpc_shutdown();
+ if (GRPC_G(initialized)) {
+ grpc_shutdown_timeval(TSRMLS_C);
+ grpc_php_shutdown_completion_queue(TSRMLS_C);
+ grpc_shutdown();
+ GRPC_G(initialized) = 0;
+ }
return SUCCESS;
}
/* }}} */
@@ -259,6 +265,26 @@ PHP_MINFO_FUNCTION(grpc) {
*/
}
/* }}} */
+
+/* {{{ PHP_RINIT_FUNCTION
+ */
+PHP_RINIT_FUNCTION(grpc) {
+ if (!GRPC_G(initialized)) {
+ grpc_init();
+ grpc_php_init_completion_queue(TSRMLS_C);
+ GRPC_G(initialized) = 1;
+ }
+ return SUCCESS;
+}
+/* }}} */
+
+/* {{{ PHP_GINIT_FUNCTION
+ */
+static PHP_GINIT_FUNCTION(grpc) {
+ grpc_globals->initialized = 0;
+}
+/* }}} */
+
/* The previous line is meant for vim and emacs, so it can correctly fold and
unfold functions in source code. See the corresponding marks just before
function definition, where the functions purpose is also documented. Please
diff --git a/src/php/ext/grpc/php_grpc.h b/src/php/ext/grpc/php_grpc.h
index ed846fdba4..e30f011c39 100644
--- a/src/php/ext/grpc/php_grpc.h
+++ b/src/php/ext/grpc/php_grpc.h
@@ -49,14 +49,16 @@ PHP_MINIT_FUNCTION(grpc);
PHP_MSHUTDOWN_FUNCTION(grpc);
/* Displays information about the module */
PHP_MINFO_FUNCTION(grpc);
+/* Code that runs at request start */
+PHP_RINIT_FUNCTION(grpc);
/*
Declare any global variables you may need between the BEGIN
and END macros here:
-
+*/
ZEND_BEGIN_MODULE_GLOBALS(grpc)
+ zend_bool initialized;
ZEND_END_MODULE_GLOBALS(grpc)
-*/
/* In every utility function you add that needs to use variables
in php_grpc_globals, call TSRMLS_FETCH(); after declaring other
@@ -74,4 +76,8 @@ ZEND_END_MODULE_GLOBALS(grpc)
#define GRPC_G(v) (grpc_globals.v)
#endif
+#define GRPC_STARTUP_FUNCTION(module) ZEND_MINIT_FUNCTION(grpc_##module)
+#define GRPC_STARTUP(module) \
+ ZEND_MODULE_STARTUP_N(grpc_##module)(INIT_FUNC_ARGS_PASSTHRU)
+
#endif /* PHP_GRPC_H */
diff --git a/src/php/ext/grpc/version.h b/src/php/ext/grpc/version.h
index 744028b2ca..07d8eee7fe 100644
--- a/src/php/ext/grpc/version.h
+++ b/src/php/ext/grpc/version.h
@@ -20,6 +20,6 @@
#ifndef VERSION_H
#define VERSION_H
-#define PHP_GRPC_VERSION "1.5.0dev"
+#define PHP_GRPC_VERSION "1.7.0dev"
#endif /* VERSION_H */
diff --git a/src/php/lib/Grpc/ServerStreamingCall.php b/src/php/lib/Grpc/ServerStreamingCall.php
index 269a99ab59..f8fddfea01 100644
--- a/src/php/lib/Grpc/ServerStreamingCall.php
+++ b/src/php/lib/Grpc/ServerStreamingCall.php
@@ -40,13 +40,11 @@ class ServerStreamingCall extends AbstractCall
if (array_key_exists('flags', $options)) {
$message_array['flags'] = $options['flags'];
}
- $event = $this->call->startBatch([
+ $this->call->startBatch([
OP_SEND_INITIAL_METADATA => $metadata,
- OP_RECV_INITIAL_METADATA => true,
OP_SEND_MESSAGE => $message_array,
OP_SEND_CLOSE_FROM_CLIENT => true,
]);
- $this->metadata = $event->metadata;
}
/**
@@ -54,9 +52,15 @@ class ServerStreamingCall extends AbstractCall
*/
public function responses()
{
- $response = $this->call->startBatch([
- OP_RECV_MESSAGE => true,
- ])->message;
+ $batch = [OP_RECV_MESSAGE => true];
+ if ($this->metadata === null) {
+ $batch[OP_RECV_INITIAL_METADATA] = true;
+ }
+ $read_event = $this->call->startBatch($batch);
+ if ($this->metadata === null) {
+ $this->metadata = $read_event->metadata;
+ }
+ $response = $read_event->message;
while ($response !== null) {
yield $this->_deserializeResponse($response);
$response = $this->call->startBatch([
@@ -81,4 +85,16 @@ class ServerStreamingCall extends AbstractCall
return $status_event->status;
}
+
+ /**
+ * @return mixed The metadata sent by the server
+ */
+ public function getMetadata()
+ {
+ if ($this->metadata === null) {
+ $event = $this->call->startBatch([OP_RECV_INITIAL_METADATA => true]);
+ $this->metadata = $event->metadata;
+ }
+ return $this->metadata;
+ }
}
diff --git a/src/php/lib/Grpc/UnaryCall.php b/src/php/lib/Grpc/UnaryCall.php
index dd55d02165..e8496492b8 100644
--- a/src/php/lib/Grpc/UnaryCall.php
+++ b/src/php/lib/Grpc/UnaryCall.php
@@ -40,13 +40,11 @@ class UnaryCall extends AbstractCall
if (isset($options['flags'])) {
$message_array['flags'] = $options['flags'];
}
- $event = $this->call->startBatch([
+ $this->call->startBatch([
OP_SEND_INITIAL_METADATA => $metadata,
- OP_RECV_INITIAL_METADATA => true,
OP_SEND_MESSAGE => $message_array,
OP_SEND_CLOSE_FROM_CLIENT => true,
]);
- $this->metadata = $event->metadata;
}
/**
@@ -56,14 +54,32 @@ class UnaryCall extends AbstractCall
*/
public function wait()
{
- $event = $this->call->startBatch([
+ $batch = [
OP_RECV_MESSAGE => true,
OP_RECV_STATUS_ON_CLIENT => true,
- ]);
-
+ ];
+ if ($this->metadata === null) {
+ $batch[OP_RECV_INITIAL_METADATA] = true;
+ }
+ $event = $this->call->startBatch($batch);
+ if ($this->metadata === null) {
+ $this->metadata = $event->metadata;
+ }
$status = $event->status;
$this->trailing_metadata = $status->metadata;
return [$this->_deserializeResponse($event->message), $status];
}
+
+ /**
+ * @return mixed The metadata sent by the server
+ */
+ public function getMetadata()
+ {
+ if ($this->metadata === null) {
+ $event = $this->call->startBatch([OP_RECV_INITIAL_METADATA => true]);
+ $this->metadata = $event->metadata;
+ }
+ return $this->metadata;
+ }
}
diff --git a/src/php/tests/unit_tests/CallTest.php b/src/php/tests/unit_tests/CallTest.php
index 3270e73f82..c5e1890a98 100644
--- a/src/php/tests/unit_tests/CallTest.php
+++ b/src/php/tests/unit_tests/CallTest.php
@@ -37,8 +37,7 @@ class CallTest extends PHPUnit_Framework_TestCase
public function tearDown()
{
- unset($this->call);
- unset($this->channel);
+ $this->channel->close();
}
public function testConstructor()
diff --git a/src/php/tests/unit_tests/ChannelTest.php b/src/php/tests/unit_tests/ChannelTest.php
index 34e6185031..c375a16269 100644
--- a/src/php/tests/unit_tests/ChannelTest.php
+++ b/src/php/tests/unit_tests/ChannelTest.php
@@ -25,17 +25,15 @@ class ChannelTest extends PHPUnit_Framework_TestCase
public function tearDown()
{
- unset($this->channel);
+ if (!empty($this->channel)) {
+ $this->channel->close();
+ }
}
public function testInsecureCredentials()
{
- $this->channel = new Grpc\Channel(
- 'localhost:0',
- [
- 'credentials' => Grpc\ChannelCredentials::createInsecure(),
- ]
- );
+ $this->channel = new Grpc\Channel('localhost:0',
+ ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
$this->assertSame('Grpc\Channel', get_class($this->channel));
}
@@ -111,7 +109,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase
*/
public function testInvalidConstructorWith()
{
- $this->channel = new Grpc\Channel('localhost', 'invalid');
+ $this->channel = new Grpc\Channel('localhost:0', 'invalid');
$this->assertNull($this->channel);
}
@@ -120,12 +118,8 @@ class ChannelTest extends PHPUnit_Framework_TestCase
*/
public function testInvalidCredentials()
{
- $this->channel = new Grpc\Channel(
- 'localhost:0',
- [
- 'credentials' => new Grpc\Timeval(100),
- ]
- );
+ $this->channel = new Grpc\Channel('localhost:0',
+ ['credentials' => new Grpc\Timeval(100)]);
}
/**
@@ -133,12 +127,8 @@ class ChannelTest extends PHPUnit_Framework_TestCase
*/
public function testInvalidOptionsArray()
{
- $this->channel = new Grpc\Channel(
- 'localhost:0',
- [
- 'abc' => [],
- ]
- );
+ $this->channel = new Grpc\Channel('localhost:0',
+ ['abc' => []]);
}
/**
@@ -170,4 +160,432 @@ class ChannelTest extends PHPUnit_Framework_TestCase
['credentials' => Grpc\ChannelCredentials::createInsecure()]);
$this->channel->watchConnectivityState(1, 'hi');
}
+
+
+ public function assertConnecting($state) {
+ $this->assertTrue($state == GRPC\CHANNEL_CONNECTING ||
+ $state == GRPC\CHANNEL_TRANSIENT_FAILURE);
+ }
+
+ public function waitUntilNotIdle($channel) {
+ for ($i = 0; $i < 10; $i++) {
+ $now = Grpc\Timeval::now();
+ $deadline = $now->add(new Grpc\Timeval(1000));
+ if ($channel->watchConnectivityState(GRPC\CHANNEL_IDLE,
+ $deadline)) {
+ return true;
+ }
+ }
+ $this->assertTrue(false);
+ }
+
+ public function testPersistentChannelSameHost()
+ {
+ $this->channel1 = new Grpc\Channel('localhost:1', []);
+ // the underlying grpc channel is the same by default
+ // when connecting to the same host
+ $this->channel2 = new Grpc\Channel('localhost:1', []);
+
+ // both channels should be IDLE
+ $state = $this->channel1->getConnectivityState();
+ $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+ $state = $this->channel2->getConnectivityState();
+ $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+
+ // try to connect on channel1
+ $state = $this->channel1->getConnectivityState(true);
+ $this->waitUntilNotIdle($this->channel1);
+
+ // both channels should now be in the CONNECTING state
+ $state = $this->channel1->getConnectivityState();
+ $this->assertConnecting($state);
+ $state = $this->channel2->getConnectivityState();
+ $this->assertConnecting($state);
+
+ $this->channel1->close();
+ $this->channel2->close();
+ }
+
+ public function testPersistentChannelDifferentHost()
+ {
+ // two different underlying channels because different hostname
+ $this->channel1 = new Grpc\Channel('localhost:1', []);
+ $this->channel2 = new Grpc\Channel('localhost:2', []);
+
+ // both channels should be IDLE
+ $state = $this->channel1->getConnectivityState();
+ $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+ $state = $this->channel2->getConnectivityState();
+ $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+
+ // try to connect on channel1
+ $state = $this->channel1->getConnectivityState(true);
+ $this->waitUntilNotIdle($this->channel1);
+
+ // channel1 should now be in the CONNECTING state
+ $state = $this->channel1->getConnectivityState();
+ $this->assertConnecting($state);
+ // channel2 should still be in the IDLE state
+ $state = $this->channel2->getConnectivityState();
+ $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+
+ $this->channel1->close();
+ $this->channel2->close();
+ }
+
+ public function testPersistentChannelSameArgs()
+ {
+ $this->channel1 = new Grpc\Channel('localhost:1', ["abc" => "def"]);
+ $this->channel2 = new Grpc\Channel('localhost:1', ["abc" => "def"]);
+
+ // try to connect on channel1
+ $state = $this->channel1->getConnectivityState(true);
+ $this->waitUntilNotIdle($this->channel1);
+
+ $state = $this->channel1->getConnectivityState();
+ $this->assertConnecting($state);
+ $state = $this->channel2->getConnectivityState();
+ $this->assertConnecting($state);
+
+ $this->channel1->close();
+ $this->channel2->close();
+ }
+
+ public function testPersistentChannelDifferentArgs()
+ {
+ $this->channel1 = new Grpc\Channel('localhost:1', []);
+ $this->channel2 = new Grpc\Channel('localhost:1', ["abc" => "def"]);
+
+ // try to connect on channel1
+ $state = $this->channel1->getConnectivityState(true);
+ $this->waitUntilNotIdle($this->channel1);
+
+ $state = $this->channel1->getConnectivityState();
+ $this->assertConnecting($state);
+ $state = $this->channel2->getConnectivityState();
+ $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+
+ $this->channel1->close();
+ $this->channel2->close();
+ }
+
+ public function testPersistentChannelSameChannelCredentials()
+ {
+ $creds1 = Grpc\ChannelCredentials::createSsl();
+ $creds2 = Grpc\ChannelCredentials::createSsl();
+
+ $this->channel1 = new Grpc\Channel('localhost:1',
+ ["credentials" => $creds1]);
+ $this->channel2 = new Grpc\Channel('localhost:1',
+ ["credentials" => $creds2]);
+
+ // try to connect on channel1
+ $state = $this->channel1->getConnectivityState(true);
+ $this->waitUntilNotIdle($this->channel1);
+
+ $state = $this->channel1->getConnectivityState();
+ $this->assertConnecting($state);
+ $state = $this->channel2->getConnectivityState();
+ $this->assertConnecting($state);
+
+ $this->channel1->close();
+ $this->channel2->close();
+ }
+
+ public function testPersistentChannelDifferentChannelCredentials()
+ {
+ $creds1 = Grpc\ChannelCredentials::createSsl();
+ $creds2 = Grpc\ChannelCredentials::createSsl(
+ file_get_contents(dirname(__FILE__).'/../data/ca.pem'));
+
+ $this->channel1 = new Grpc\Channel('localhost:1',
+ ["credentials" => $creds1]);
+ $this->channel2 = new Grpc\Channel('localhost:1',
+ ["credentials" => $creds2]);
+
+ // try to connect on channel1
+ $state = $this->channel1->getConnectivityState(true);
+ $this->waitUntilNotIdle($this->channel1);
+
+ $state = $this->channel1->getConnectivityState();
+ $this->assertConnecting($state);
+ $state = $this->channel2->getConnectivityState();
+ $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+
+ $this->channel1->close();
+ $this->channel2->close();
+ }
+
+ public function testPersistentChannelSameChannelCredentialsRootCerts()
+ {
+ $creds1 = Grpc\ChannelCredentials::createSsl(
+ file_get_contents(dirname(__FILE__).'/../data/ca.pem'));
+ $creds2 = Grpc\ChannelCredentials::createSsl(
+ file_get_contents(dirname(__FILE__).'/../data/ca.pem'));
+
+ $this->channel1 = new Grpc\Channel('localhost:1',
+ ["credentials" => $creds1]);
+ $this->channel2 = new Grpc\Channel('localhost:1',
+ ["credentials" => $creds2]);
+
+ // try to connect on channel1
+ $state = $this->channel1->getConnectivityState(true);
+ $this->waitUntilNotIdle($this->channel1);
+
+ $state = $this->channel1->getConnectivityState();
+ $this->assertConnecting($state);
+ $state = $this->channel2->getConnectivityState();
+ $this->assertConnecting($state);
+
+ $this->channel1->close();
+ $this->channel2->close();
+ }
+
+ public function testPersistentChannelDifferentSecureChannelCredentials()
+ {
+ $creds1 = Grpc\ChannelCredentials::createSsl();
+ $creds2 = Grpc\ChannelCredentials::createInsecure();
+
+ $this->channel1 = new Grpc\Channel('localhost:1',
+ ["credentials" => $creds1]);
+ $this->channel2 = new Grpc\Channel('localhost:1',
+ ["credentials" => $creds2]);
+
+ // try to connect on channel1
+ $state = $this->channel1->getConnectivityState(true);
+ $this->waitUntilNotIdle($this->channel1);
+
+ $state = $this->channel1->getConnectivityState();
+ $this->assertConnecting($state);
+ $state = $this->channel2->getConnectivityState();
+ $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+
+ $this->channel1->close();
+ $this->channel2->close();
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testPersistentChannelSharedChannelClose()
+ {
+ // same underlying channel
+ $this->channel1 = new Grpc\Channel('localhost:1', []);
+ $this->channel2 = new Grpc\Channel('localhost:1', []);
+
+ // close channel1
+ $this->channel1->close();
+
+ // channel2 is now in SHUTDOWN state
+ $state = $this->channel2->getConnectivityState();
+ $this->assertEquals(GRPC\CHANNEL_FATAL_FAILURE, $state);
+
+ // calling it again will result in an exception because the
+ // channel is already closed
+ $state = $this->channel2->getConnectivityState();
+ }
+
+ public function testPersistentChannelCreateAfterClose()
+ {
+ $this->channel1 = new Grpc\Channel('localhost:1', []);
+
+ $this->channel1->close();
+
+ $this->channel2 = new Grpc\Channel('localhost:1', []);
+ $state = $this->channel2->getConnectivityState();
+ $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+
+ $this->channel2->close();
+ }
+
+ public function testPersistentChannelSharedMoreThanTwo()
+ {
+ $this->channel1 = new Grpc\Channel('localhost:1', []);
+ $this->channel2 = new Grpc\Channel('localhost:1', []);
+ $this->channel3 = new Grpc\Channel('localhost:1', []);
+
+ // try to connect on channel1
+ $state = $this->channel1->getConnectivityState(true);
+ $this->waitUntilNotIdle($this->channel1);
+
+ // all 3 channels should be in CONNECTING state
+ $state = $this->channel1->getConnectivityState();
+ $this->assertConnecting($state);
+ $state = $this->channel2->getConnectivityState();
+ $this->assertConnecting($state);
+ $state = $this->channel3->getConnectivityState();
+ $this->assertConnecting($state);
+
+ $this->channel1->close();
+ }
+
+ public function callbackFunc($context)
+ {
+ return [];
+ }
+
+ public function callbackFunc2($context)
+ {
+ return ["k1" => "v1"];
+ }
+
+ public function testPersistentChannelWithCallCredentials()
+ {
+ $creds = Grpc\ChannelCredentials::createSsl();
+ $callCreds = Grpc\CallCredentials::createFromPlugin(
+ [$this, 'callbackFunc']);
+ $credsWithCallCreds = Grpc\ChannelCredentials::createComposite(
+ $creds, $callCreds);
+
+ // If a ChannelCredentials object is composed with a
+ // CallCredentials object, the underlying grpc channel will
+ // always be created new and NOT persisted.
+ $this->channel1 = new Grpc\Channel('localhost:1',
+ ["credentials" =>
+ $credsWithCallCreds]);
+ $this->channel2 = new Grpc\Channel('localhost:1',
+ ["credentials" =>
+ $credsWithCallCreds]);
+
+ // try to connect on channel1
+ $state = $this->channel1->getConnectivityState(true);
+ $this->waitUntilNotIdle($this->channel1);
+
+ $state = $this->channel1->getConnectivityState();
+ $this->assertConnecting($state);
+ $state = $this->channel2->getConnectivityState();
+ $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+
+ $this->channel1->close();
+ $this->channel2->close();
+ }
+
+ public function testPersistentChannelWithDifferentCallCredentials()
+ {
+ $callCreds1 = Grpc\CallCredentials::createFromPlugin(
+ [$this, 'callbackFunc']);
+ $callCreds2 = Grpc\CallCredentials::createFromPlugin(
+ [$this, 'callbackFunc2']);
+
+ $creds1 = Grpc\ChannelCredentials::createSsl();
+ $creds2 = Grpc\ChannelCredentials::createComposite(
+ $creds1, $callCreds1);
+ $creds3 = Grpc\ChannelCredentials::createComposite(
+ $creds1, $callCreds2);
+
+ // Similar to the test above, anytime a ChannelCredentials
+ // object is composed with a CallCredentials object, the
+ // underlying grpc channel will always be separate and not
+ // persisted
+ $this->channel1 = new Grpc\Channel('localhost:1',
+ ["credentials" => $creds1]);
+ $this->channel2 = new Grpc\Channel('localhost:1',
+ ["credentials" => $creds2]);
+ $this->channel3 = new Grpc\Channel('localhost:1',
+ ["credentials" => $creds3]);
+
+ // try to connect on channel1
+ $state = $this->channel1->getConnectivityState(true);
+ $this->waitUntilNotIdle($this->channel1);
+
+ $state = $this->channel1->getConnectivityState();
+ $this->assertConnecting($state);
+ $state = $this->channel2->getConnectivityState();
+ $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+ $state = $this->channel3->getConnectivityState();
+ $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+
+ $this->channel1->close();
+ $this->channel2->close();
+ $this->channel3->close();
+ }
+
+ public function testPersistentChannelForceNew()
+ {
+ $this->channel1 = new Grpc\Channel('localhost:1', []);
+ // even though all the channel params are the same, channel2
+ // has a new and different underlying channel
+ $this->channel2 = new Grpc\Channel('localhost:1',
+ ["force_new" => true]);
+
+ // try to connect on channel1
+ $state = $this->channel1->getConnectivityState(true);
+ $this->waitUntilNotIdle($this->channel1);
+
+ $state = $this->channel1->getConnectivityState();
+ $this->assertConnecting($state);
+ $state = $this->channel2->getConnectivityState();
+ $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+
+ $this->channel1->close();
+ $this->channel2->close();
+ }
+
+ public function testPersistentChannelForceNewOldChannelIdle()
+ {
+
+ $this->channel1 = new Grpc\Channel('localhost:1', []);
+ $this->channel2 = new Grpc\Channel('localhost:1',
+ ["force_new" => true]);
+ // channel3 shares with channel1
+ $this->channel3 = new Grpc\Channel('localhost:1', []);
+
+ // try to connect on channel2
+ $state = $this->channel2->getConnectivityState(true);
+ $this->waitUntilNotIdle($this->channel2);
+
+ $state = $this->channel1->getConnectivityState();
+ $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+ $state = $this->channel2->getConnectivityState();
+ $this->assertConnecting($state);
+ $state = $this->channel3->getConnectivityState();
+ $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+
+ $this->channel1->close();
+ $this->channel2->close();
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testPersistentChannelForceNewOldChannelClose()
+ {
+
+ $this->channel1 = new Grpc\Channel('localhost:1', []);
+ $this->channel2 = new Grpc\Channel('localhost:1',
+ ["force_new" => true]);
+ // channel3 shares with channel1
+ $this->channel3 = new Grpc\Channel('localhost:1', []);
+
+ $this->channel1->close();
+
+ $state = $this->channel2->getConnectivityState();
+ $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+
+ // channel3 already closed
+ $state = $this->channel3->getConnectivityState();
+ }
+
+ public function testPersistentChannelForceNewNewChannelClose()
+ {
+
+ $this->channel1 = new Grpc\Channel('localhost:1', []);
+ $this->channel2 = new Grpc\Channel('localhost:1',
+ ["force_new" => true]);
+ $this->channel3 = new Grpc\Channel('localhost:1', []);
+
+ $this->channel2->close();
+
+ $state = $this->channel1->getConnectivityState();
+ $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
+
+ // can still connect on channel1
+ $state = $this->channel1->getConnectivityState(true);
+ $this->waitUntilNotIdle($this->channel1);
+
+ $state = $this->channel1->getConnectivityState();
+ $this->assertConnecting($state);
+
+ $this->channel1->close();
+ }
}
diff --git a/src/php/tests/unit_tests/EndToEndTest.php b/src/php/tests/unit_tests/EndToEndTest.php
index 43d54d9ee6..b54f1d87c9 100644
--- a/src/php/tests/unit_tests/EndToEndTest.php
+++ b/src/php/tests/unit_tests/EndToEndTest.php
@@ -28,8 +28,7 @@ class EndToEndTest extends PHPUnit_Framework_TestCase
public function tearDown()
{
- unset($this->channel);
- unset($this->server);
+ $this->channel->close();
}
public function testSimpleRequestBody()
@@ -516,7 +515,7 @@ class EndToEndTest extends PHPUnit_Framework_TestCase
$this->assertTrue($idle_state == Grpc\CHANNEL_IDLE);
$now = Grpc\Timeval::now();
- $delta = new Grpc\Timeval(500000); // should timeout
+ $delta = new Grpc\Timeval(50000); // should timeout
$deadline = $now->add($delta);
$this->assertFalse($this->channel->watchConnectivityState(
@@ -545,7 +544,7 @@ class EndToEndTest extends PHPUnit_Framework_TestCase
$this->assertTrue($idle_state == Grpc\CHANNEL_IDLE);
$now = Grpc\Timeval::now();
- $delta = new Grpc\Timeval(100000);
+ $delta = new Grpc\Timeval(50000);
$deadline = $now->add($delta);
$this->assertFalse($this->channel->watchConnectivityState(
diff --git a/src/php/tests/unit_tests/SecureEndToEndTest.php b/src/php/tests/unit_tests/SecureEndToEndTest.php
index 0fecbfb3dd..dff4e878ea 100644
--- a/src/php/tests/unit_tests/SecureEndToEndTest.php
+++ b/src/php/tests/unit_tests/SecureEndToEndTest.php
@@ -43,8 +43,7 @@ class SecureEndToEndTest extends PHPUnit_Framework_TestCase
public function tearDown()
{
- unset($this->channel);
- unset($this->server);
+ $this->channel->close();
}
public function testSimpleRequestBody()