/*
*
* 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.
*
*/
#include "channel.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 <zend_exceptions.h>
#include <grpc/grpc_security.h>
#include <grpc/support/alloc.h>
#include "completion_queue.h"
#include "channel_credentials.h"
#include "timeval.h"
zend_class_entry *grpc_ce_channel;
PHP_GRPC_DECLARE_OBJECT_HANDLER(channel_ce_handlers)
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.
if (p->wrapper != NULL) {
php_grpc_channel_unref(p->wrapper);
p->wrapper = NULL;
}
PHP_GRPC_FREE_WRAPPED_FUNC_END()
/* Initializes an instance of wrapped_grpc_channel to be associated with an
* object of a class specified by class_type */
php_grpc_zend_object create_wrapped_grpc_channel(zend_class_entry *class_type
TSRMLS_DC) {
PHP_GRPC_ALLOC_CLASS_OBJECT(wrapped_grpc_channel);
zend_object_std_init(&intern->std, class_type TSRMLS_CC);
object_properties_init(&intern->std, class_type);
PHP_GRPC_FREE_CLASS_OBJECT(wrapped_grpc_channel, channel_ce_handlers);
}
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 FAILURE;
}
args->num_args = zend_hash_num_elements(array_hash);
args->args = ecalloc(args->num_args, sizeof(grpc_arg));
args_index = 0;
char *key = NULL;
zval *data;
int key_type;
PHP_GRPC_HASH_FOREACH_STR_KEY_VAL_START(array_hash, key, key_type, data)
if (key_type != HASH_KEY_IS_STRING) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"args keys must be strings", 1 TSRMLS_CC);
return FAILURE;
}
args->args[args_index].key = key;
switch (Z_TYPE_P(data)) {
case IS_LONG:
args->args[args_index].value.integer = (int)Z_LVAL_P(data);
args->args[args_index].type = GRPC_ARG_INTEGER;
break;
case IS_STRING:
args->args[args_index].value.string = Z_STRVAL_P(data);
args->args[args_index].type = GRPC_ARG_STRING;
break;
default:
zend_throw_exception(spl_ce_InvalidArgumentException,
"args values must be int or string", 1 TSRMLS_CC);
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);
}
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,
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);
}
// There is an Grpc\Channel object refer to it.
php_grpc_channel_ref(channel->wrapper);
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,
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
new_rsrc.type = le_plink;
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);
}
/**
* 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
*/
PHP_METHOD(Channel, __construct) {
wrapped_grpc_channel *channel =
PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
zval *creds_obj = NULL;
char *target;
php_grpc_int target_length;
zval *args_array = NULL;
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;
int target_upper_bound = -1;
/* "sa" == 1 string, 1 array */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", &target,
&target_length, &args_array) == FAILURE) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Channel expects a string and an array", 1 TSRMLS_CC);
return;
}
array_hash = Z_ARRVAL_P(args_array);
if (php_grpc_zend_hash_find(array_hash, "credentials", sizeof("credentials"),
(void **)&creds_obj) == SUCCESS) {
if (Z_TYPE_P(creds_obj) == IS_NULL) {
creds = NULL;
php_grpc_zend_hash_del(array_hash, "credentials", sizeof("credentials"));
} else if (PHP_GRPC_GET_CLASS_ENTRY(creds_obj) !=
grpc_ce_channel_credentials) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"credentials must be a ChannelCredentials object",
1 TSRMLS_CC);
return;
} else {
creds = PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel_credentials,
creds_obj);
php_grpc_zend_hash_del(array_hash, "credentials", sizeof("credentials"));
}
}
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"));
}
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);
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->ref_count = 0;
channel->wrapper->key = key;
channel->wrapper->target = strdup(target);
channel->wrapper->args_hashstr = strdup(sha1str);
channel->wrapper->creds_hashstr = NULL;
if (creds != NULL && creds->hashstr != NULL) {
php_grpc_int creds_hashstr_len = strlen(creds->hashstr);
char *channel_creds_hashstr = malloc(creds_hashstr_len + 1);
strcpy(channel_creds_hashstr, creds->hashstr);
channel->wrapper->creds_hashstr = channel_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(&grpc_persistent_list, key,
key_len, rsrc))) {
create_and_add_channel_to_persistent_list(
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;
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, target_upper_bound TSRMLS_CC);
} else {
efree(args.args);
free_grpc_channel_wrapper(channel->wrapper, false);
gpr_mu_destroy(&channel->wrapper->mu);
free(channel->wrapper);
channel->wrapper = NULL;
channel->wrapper = le->channel;
// One more Grpc\Channel object refer to it.
php_grpc_channel_ref(channel->wrapper);
update_and_get_target_upper_bound(target, target_upper_bound);
}
}
}
/**
* Get the endpoint this call/stream is connected to
* @return string The URI of the endpoint
*/
PHP_METHOD(Channel, getTarget) {
wrapped_grpc_channel *channel =
PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
if (channel->wrapper == NULL) {
zend_throw_exception(spl_ce_RuntimeException,
"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);
gpr_free(target);
}
/**
* Get the connectivity state of the channel
* @param bool $try_to_connect Try to connect on the channel (optional)
* @return long The grpc connectivity state
*/
PHP_METHOD(Channel, getConnectivityState) {
wrapped_grpc_channel *channel =
PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
if (channel->wrapper == NULL) {
zend_throw_exception(spl_ce_RuntimeException,
"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) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"getConnectivityState expects a bool", 1 TSRMLS_CC);
gpr_mu_unlock(&channel->wrapper->mu);
return;
}
int state = grpc_channel_check_connectivity_state(channel->wrapper->wrapped,
(int)try_to_connect);
gpr_mu_unlock(&channel->wrapper->mu);
RETURN_LONG(state);
}
/**
* Watch the connectivity state of the channel until it changed
* @param long $last_state The previous connectivity state of the channel
* @param Timeval $deadline_obj The deadline this function should wait until
* @return bool If the connectivity state changes from last_state
* before deadline
*/
PHP_METHOD(Channel, watchConnectivityState) {
wrapped_grpc_channel *channel =
PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
if (channel->wrapper == NULL) {
zend_throw_exception(spl_ce_RuntimeException,
"watchConnectivityState error"
"Channel is already closed.", 1 TSRMLS_CC);
return;
}
gpr_mu_lock(&channel->wrapper->mu);
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) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"watchConnectivityState expects 1 long 1 timeval",
1 TSRMLS_CC);
gpr_mu_unlock(&channel->wrapper->mu);
return;
}
wrapped_grpc_timeval *deadline =
PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_timeval, deadline_obj);
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);
gpr_mu_unlock(&channel->wrapper->mu);
RETURN_BOOL(event.success);
}
/**
* Close the channel
* @return void
*/
PHP_METHOD(Channel, close) {
wrapped_grpc_channel *channel =
PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
if (channel->wrapper != NULL) {
php_grpc_channel_unref(channel->wrapper);
channel->wrapper = NULL;
}
}
// 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(&grpc_persistent_list, key,
key_len, rsrc)) {
php_grpc_zend_hash_del(&grpc_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 == NULL) {
return;
}
if (le->channel != NULL) {
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 =
PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, 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)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_getTarget, 0, 0, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_getConnectivityState, 0, 0, 0)
ZEND_ARG_INFO(0, try_to_connect)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_watchConnectivityState, 0, 0, 2)
ZEND_ARG_INFO(0, last_state)
ZEND_ARG_INFO(0, deadline)
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)
PHP_ME(Channel, getTarget, arginfo_getTarget,
ZEND_ACC_PUBLIC)
PHP_ME(Channel, getConnectivityState, arginfo_getConnectivityState,
ZEND_ACC_PUBLIC)
PHP_ME(Channel, watchConnectivityState, arginfo_watchConnectivityState,
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
};
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);
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;
}