1 /*
2 *
3 * Copyright 2015 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19 /**
20 * class Channel
21 * @see https://github.com/grpc/grpc/tree/master/src/php/ext/grpc/channel.c
22 */
23
24 #include "channel.h"
25
26 #include <ext/standard/php_var.h>
27 #include <ext/standard/sha1.h>
28 #if PHP_MAJOR_VERSION < 7
29 #include <ext/standard/php_smart_str.h>
30 #else
31 #include <zend_smart_str.h>
32 #endif
33 #include <ext/spl/spl_exceptions.h>
34 #include <zend_exceptions.h>
35
36 #include <grpc/grpc_security.h>
37 #include <grpc/support/alloc.h>
38 #include <grpc/support/log.h>
39
40 #include "completion_queue.h"
41 #include "channel_credentials.h"
42 #include "timeval.h"
43
44 zend_class_entry *grpc_ce_channel;
45 PHP_GRPC_DECLARE_OBJECT_HANDLER(channel_ce_handlers)
46 static gpr_mu global_persistent_list_mu;
47 int le_plink;
48 int le_bound;
49 extern HashTable grpc_persistent_list;
50 extern HashTable grpc_target_upper_bound_map;
51
free_grpc_channel_wrapper(grpc_channel_wrapper * channel,bool free_channel)52 void free_grpc_channel_wrapper(grpc_channel_wrapper* channel, bool free_channel) {
53 if (free_channel) {
54 grpc_channel_destroy(channel->wrapped);
55 channel->wrapped = NULL;
56 }
57 free(channel->target);
58 free(channel->args_hashstr);
59 free(channel->creds_hashstr);
60 free(channel->key);
61 channel->target = NULL;
62 channel->args_hashstr = NULL;
63 channel->creds_hashstr = NULL;
64 channel->key = NULL;
65 }
66
php_grpc_channel_ref(grpc_channel_wrapper * wrapper)67 void php_grpc_channel_ref(grpc_channel_wrapper* wrapper) {
68 gpr_mu_lock(&wrapper->mu);
69 wrapper->ref_count += 1;
70 gpr_mu_unlock(&wrapper->mu);
71 }
72
php_grpc_channel_unref(grpc_channel_wrapper * wrapper)73 void php_grpc_channel_unref(grpc_channel_wrapper* wrapper) {
74 gpr_mu_lock(&wrapper->mu);
75 wrapper->ref_count -= 1;
76 if (wrapper->ref_count == 0) {
77 free_grpc_channel_wrapper(wrapper, true);
78 gpr_mu_unlock(&wrapper->mu);
79 free(wrapper);
80 wrapper = NULL;
81 return;
82 }
83 gpr_mu_unlock(&wrapper->mu);
84 }
85
86 /* Frees and destroys an instance of wrapped_grpc_channel */
87 PHP_GRPC_FREE_WRAPPED_FUNC_START(wrapped_grpc_channel)
88 // In_persistent_list is used when the user don't close the channel,
89 // In this case, channels not in the list should be freed.
90 if (p->wrapper != NULL) {
91 php_grpc_channel_unref(p->wrapper);
92 p->wrapper = NULL;
93 }
PHP_GRPC_FREE_WRAPPED_FUNC_END()94 PHP_GRPC_FREE_WRAPPED_FUNC_END()
95
96 /* Initializes an instance of wrapped_grpc_channel to be associated with an
97 * object of a class specified by class_type */
98 php_grpc_zend_object create_wrapped_grpc_channel(zend_class_entry *class_type
99 TSRMLS_DC) {
100 PHP_GRPC_ALLOC_CLASS_OBJECT(wrapped_grpc_channel);
101 zend_object_std_init(&intern->std, class_type TSRMLS_CC);
102 object_properties_init(&intern->std, class_type);
103 PHP_GRPC_FREE_CLASS_OBJECT(wrapped_grpc_channel, channel_ce_handlers);
104 }
105
php_grpc_not_channel_arg_key(const char * key)106 static bool php_grpc_not_channel_arg_key(const char* key) {
107 static const char* ignoredKeys[] = {
108 "credentials",
109 "force_new",
110 "grpc_target_persist_bound",
111 };
112
113 for (int i = 0; i < sizeof(ignoredKeys) / sizeof(ignoredKeys[0]); i++) {
114 if (strcmp(key, ignoredKeys[i]) == 0) {
115 return true;
116 }
117 }
118 return false;
119 }
120
php_grpc_read_args_array(zval * args_array,grpc_channel_args * args TSRMLS_DC)121 int php_grpc_read_args_array(zval *args_array,
122 grpc_channel_args *args TSRMLS_DC) {
123 HashTable *array_hash;
124 int args_index;
125 array_hash = Z_ARRVAL_P(args_array);
126 if (!array_hash) {
127 zend_throw_exception(spl_ce_InvalidArgumentException,
128 "array_hash is NULL", 1 TSRMLS_CC);
129 return FAILURE;
130 }
131
132 args->args = ecalloc(zend_hash_num_elements(array_hash), sizeof(grpc_arg));
133 args_index = 0;
134
135 char *key = NULL;
136 zval *data;
137 int key_type;
138
139 PHP_GRPC_HASH_FOREACH_STR_KEY_VAL_START(array_hash, key, key_type, data)
140 if (key_type != HASH_KEY_IS_STRING) {
141 zend_throw_exception(spl_ce_InvalidArgumentException,
142 "args keys must be strings", 1 TSRMLS_CC);
143 return FAILURE;
144 }
145
146 if (php_grpc_not_channel_arg_key(key)) {
147 continue;
148 }
149
150 args->args[args_index].key = key;
151 switch (Z_TYPE_P(data)) {
152 case IS_LONG:
153 args->args[args_index].value.integer = (int)Z_LVAL_P(data);
154 args->args[args_index].type = GRPC_ARG_INTEGER;
155 break;
156 case IS_STRING:
157 args->args[args_index].value.string = Z_STRVAL_P(data);
158 args->args[args_index].type = GRPC_ARG_STRING;
159 break;
160 default:
161 zend_throw_exception(spl_ce_InvalidArgumentException,
162 "args values must be int or string", 1 TSRMLS_CC);
163 return FAILURE;
164 }
165 args_index++;
166 PHP_GRPC_HASH_FOREACH_END()
167 args->num_args = args_index;
168 return SUCCESS;
169 }
170
generate_sha1_str(char * sha1str,char * str,php_grpc_int len)171 void generate_sha1_str(char *sha1str, char *str, php_grpc_int len) {
172 PHP_SHA1_CTX context;
173 unsigned char digest[20];
174 sha1str[0] = '\0';
175 PHP_SHA1Init(&context);
176 PHP_GRPC_SHA1Update(&context, str, len);
177 PHP_SHA1Final(digest, &context);
178 make_sha1_digest(sha1str, digest);
179 }
180
php_grpc_persistent_list_delete_unused_channel(char * target,target_bound_le_t * target_bound_status TSRMLS_DC)181 bool php_grpc_persistent_list_delete_unused_channel(
182 char* target,
183 target_bound_le_t* target_bound_status TSRMLS_DC) {
184 zval *data;
185 PHP_GRPC_HASH_FOREACH_VAL_START(&grpc_persistent_list, data)
186 php_grpc_zend_resource *rsrc = (php_grpc_zend_resource*) PHP_GRPC_HASH_VALPTR_TO_VAL(data)
187 if (rsrc == NULL) {
188 break;
189 }
190 channel_persistent_le_t* le = rsrc->ptr;
191 // Find the channel sharing the same target.
192 if (strcmp(le->channel->target, target) == 0) {
193 // ref_count=1 means that only the map holds the reference to the channel.
194 if (le->channel->ref_count == 1) {
195 php_grpc_delete_persistent_list_entry(le->channel->key,
196 strlen(le->channel->key)
197 TSRMLS_CC);
198 target_bound_status->current_count -= 1;
199 if (target_bound_status->current_count < target_bound_status->upper_bound) {
200 return true;
201 }
202 }
203 }
204 PHP_GRPC_HASH_FOREACH_END()
205 return false;
206 }
207
update_and_get_target_upper_bound(char * target,int bound)208 target_bound_le_t* update_and_get_target_upper_bound(char* target, int bound) {
209 php_grpc_zend_resource *rsrc;
210 target_bound_le_t* target_bound_status;
211 php_grpc_int key_len = strlen(target);
212 if (!(PHP_GRPC_PERSISTENT_LIST_FIND(&grpc_target_upper_bound_map, target,
213 key_len, rsrc))) {
214 // Target is not persisted.
215 php_grpc_zend_resource new_rsrc;
216 target_bound_status = malloc(sizeof(target_bound_le_t));
217 if (bound == -1) {
218 // If the bound is not set, use 1 as default.s
219 bound = 1;
220 }
221 target_bound_status->upper_bound = bound;
222 // Init current_count with 1. It should be add 1 when the channel is successfully
223 // created and minus 1 when it is removed from the persistent list.
224 target_bound_status->current_count = 0;
225 new_rsrc.type = le_bound;
226 new_rsrc.ptr = target_bound_status;
227 gpr_mu_lock(&global_persistent_list_mu);
228 PHP_GRPC_PERSISTENT_LIST_UPDATE(&grpc_target_upper_bound_map,
229 target, key_len, (void *)&new_rsrc);
230 gpr_mu_unlock(&global_persistent_list_mu);
231 } else {
232 // The target already in the map recording the upper bound.
233 // If no newer bound set, use the original now.
234 target_bound_status = (target_bound_le_t *)rsrc->ptr;
235 if (bound != -1) {
236 target_bound_status->upper_bound = bound;
237 }
238 }
239 return target_bound_status;
240 }
241
create_channel(wrapped_grpc_channel * channel,char * target,grpc_channel_args args,wrapped_grpc_channel_credentials * creds)242 void create_channel(
243 wrapped_grpc_channel *channel,
244 char *target,
245 grpc_channel_args args,
246 wrapped_grpc_channel_credentials *creds) {
247 if (creds == NULL) {
248 channel->wrapper->wrapped = grpc_insecure_channel_create(target, &args,
249 NULL);
250 } else {
251 channel->wrapper->wrapped =
252 grpc_secure_channel_create(creds->wrapped, target, &args, NULL);
253 }
254 // There is an Grpc\Channel object refer to it.
255 php_grpc_channel_ref(channel->wrapper);
256 efree(args.args);
257 }
258
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)259 void create_and_add_channel_to_persistent_list(
260 wrapped_grpc_channel *channel,
261 char *target,
262 grpc_channel_args args,
263 wrapped_grpc_channel_credentials *creds,
264 char *key,
265 php_grpc_int key_len,
266 int target_upper_bound TSRMLS_DC) {
267 target_bound_le_t* target_bound_status =
268 update_and_get_target_upper_bound(target, target_upper_bound);
269 // Check the upper bound status before inserting to the persistent map.
270 if (target_bound_status->current_count >=
271 target_bound_status->upper_bound) {
272 if (!php_grpc_persistent_list_delete_unused_channel(
273 target, target_bound_status TSRMLS_CC)) {
274 // If no channel can be deleted from the persistent map,
275 // do not persist this one.
276 create_channel(channel, target, args, creds);
277 gpr_log(GPR_INFO, "[Warning] The number of channel for the"
278 " target %s is maxed out bounded.\n", target);
279 gpr_log(GPR_INFO, "[Warning] Target upper bound: %d. Current size: %d.\n",
280 target_bound_status->upper_bound,
281 target_bound_status->current_count);
282 gpr_log(GPR_INFO, "[Warning] Target %s will not be persisted.\n", target);
283 return;
284 }
285 }
286 // There is space in the persistent map.
287 php_grpc_zend_resource new_rsrc;
288 channel_persistent_le_t *le;
289 // this links each persistent list entry to a destructor
290 new_rsrc.type = le_plink;
291 le = malloc(sizeof(channel_persistent_le_t));
292
293 create_channel(channel, target, args, creds);
294 target_bound_status->current_count += 1;
295
296 le->channel = channel->wrapper;
297 new_rsrc.ptr = le;
298 gpr_mu_lock(&global_persistent_list_mu);
299 PHP_GRPC_PERSISTENT_LIST_UPDATE(&grpc_persistent_list, key, key_len,
300 (void *)&new_rsrc);
301 // Persistent map refer to it.
302 php_grpc_channel_ref(channel->wrapper);
303 gpr_mu_unlock(&global_persistent_list_mu);
304 }
305
306 /**
307 * Construct an instance of the Channel class.
308 *
309 * By default, the underlying grpc_channel is "persistent". That is, given
310 * the same set of parameters passed to the constructor, the same underlying
311 * grpc_channel will be returned.
312 *
313 * If the $args array contains a "credentials" key mapping to a
314 * ChannelCredentials object, a secure channel will be created with those
315 * credentials.
316 *
317 * If the $args array contains a "force_new" key mapping to a boolean value
318 * of "true", a new and separate underlying grpc_channel will be created
319 * and returned. This will not affect existing channels.
320 *
321 * @param string $target The hostname to associate with this channel
322 * @param array $args_array The arguments to pass to the Channel
323 */
PHP_METHOD(Channel,__construct)324 PHP_METHOD(Channel, __construct) {
325 wrapped_grpc_channel *channel =
326 PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
327 zval *creds_obj = NULL;
328 char *target;
329 php_grpc_int target_length;
330 zval *args_array = NULL;
331 grpc_channel_args args;
332 HashTable *array_hash;
333 wrapped_grpc_channel_credentials *creds = NULL;
334 php_grpc_zend_resource *rsrc;
335 bool force_new = false;
336 zval *force_new_obj = NULL;
337 int target_upper_bound = -1;
338
339 /* "sa" == 1 string, 1 array */
340 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", &target,
341 &target_length, &args_array) == FAILURE) {
342 zend_throw_exception(spl_ce_InvalidArgumentException,
343 "Channel expects a string and an array", 1 TSRMLS_CC);
344 return;
345 }
346 array_hash = Z_ARRVAL_P(args_array);
347 if (php_grpc_zend_hash_find(array_hash, "credentials", sizeof("credentials"),
348 (void **)&creds_obj) == SUCCESS) {
349 if (Z_TYPE_P(creds_obj) == IS_NULL) {
350 creds = NULL;
351 } else if (PHP_GRPC_GET_CLASS_ENTRY(creds_obj) !=
352 grpc_ce_channel_credentials) {
353 zend_throw_exception(spl_ce_InvalidArgumentException,
354 "credentials must be a ChannelCredentials object",
355 1 TSRMLS_CC);
356 return;
357 } else {
358 creds = PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel_credentials,
359 creds_obj);
360 }
361 }
362 if (php_grpc_zend_hash_find(array_hash, "force_new", sizeof("force_new"),
363 (void **)&force_new_obj) == SUCCESS) {
364 if (PHP_GRPC_BVAL_IS_TRUE(force_new_obj)) {
365 force_new = true;
366 }
367 }
368
369 if (php_grpc_zend_hash_find(array_hash, "grpc_target_persist_bound",
370 sizeof("grpc_target_persist_bound"),
371 (void **)&force_new_obj) == SUCCESS) {
372 if (Z_TYPE_P(force_new_obj) != IS_LONG) {
373 zend_throw_exception(spl_ce_InvalidArgumentException,
374 "plist_bound must be a number",
375 1 TSRMLS_CC);
376 }
377 target_upper_bound = (int)Z_LVAL_P(force_new_obj);
378 }
379
380 // parse the rest of the channel args array
381 if (php_grpc_read_args_array(args_array, &args TSRMLS_CC) == FAILURE) {
382 efree(args.args);
383 return;
384 }
385
386 // Construct a hashkey for the persistent channel
387 // Currently, the hashkey contains 3 parts:
388 // 1. hostname
389 // 2. hash value of the channel args (args_array excluding "credentials",
390 // "force_new" and "grpc_target_persist_bound")
391 // 3. (optional) hash value of the ChannelCredentials object
392
393 char sha1str[41] = { 0 };
394 unsigned char digest[20] = { 0 };
395 PHP_SHA1_CTX context;
396 PHP_SHA1Init(&context);
397 for (int i = 0; i < args.num_args; i++) {
398 PHP_GRPC_SHA1Update(&context, args.args[i].key, strlen(args.args[i].key) + 1);
399 switch (args.args[i].type) {
400 case GRPC_ARG_INTEGER:
401 PHP_GRPC_SHA1Update(&context, &args.args[i].value.integer, 4);
402 break;
403 case GRPC_ARG_STRING:
404 PHP_GRPC_SHA1Update(&context, args.args[i].value.string, strlen(args.args[i].value.string) + 1);
405 break;
406 default:
407 zend_throw_exception(spl_ce_InvalidArgumentException,
408 "args values must be int or string", 1 TSRMLS_CC);
409 return;
410 }
411 };
412 PHP_SHA1Final(digest, &context);
413 make_sha1_digest(sha1str, digest);
414
415 php_grpc_int key_len = target_length + strlen(sha1str);
416 if (creds != NULL && creds->hashstr != NULL) {
417 key_len += strlen(creds->hashstr);
418 }
419 char *key = malloc(key_len + 1);
420 strcpy(key, target);
421 strcat(key, sha1str);
422 if (creds != NULL && creds->hashstr != NULL) {
423 strcat(key, creds->hashstr);
424 }
425 channel->wrapper = malloc(sizeof(grpc_channel_wrapper));
426 channel->wrapper->ref_count = 0;
427 channel->wrapper->key = key;
428 channel->wrapper->target = strdup(target);
429 channel->wrapper->args_hashstr = strdup(sha1str);
430 channel->wrapper->creds_hashstr = NULL;
431 channel->wrapper->creds = creds;
432 channel->wrapper->args = args;
433 if (creds != NULL && creds->hashstr != NULL) {
434 php_grpc_int creds_hashstr_len = strlen(creds->hashstr);
435 char *channel_creds_hashstr = malloc(creds_hashstr_len + 1);
436 strcpy(channel_creds_hashstr, creds->hashstr);
437 channel->wrapper->creds_hashstr = channel_creds_hashstr;
438 }
439
440 gpr_mu_init(&channel->wrapper->mu);
441 if (force_new || (creds != NULL && creds->has_call_creds)) {
442 // If the ChannelCredentials object was composed with a CallCredentials
443 // object, there is no way we can tell them apart. Do NOT persist
444 // them. They should be individually destroyed.
445 create_channel(channel, target, args, creds);
446 } else if (!(PHP_GRPC_PERSISTENT_LIST_FIND(&grpc_persistent_list, key,
447 key_len, rsrc))) {
448 create_and_add_channel_to_persistent_list(
449 channel, target, args, creds, key, key_len, target_upper_bound TSRMLS_CC);
450 } else {
451 // Found a previously stored channel in the persistent list
452 channel_persistent_le_t *le = (channel_persistent_le_t *)rsrc->ptr;
453 if (strcmp(target, le->channel->target) != 0 ||
454 strcmp(sha1str, le->channel->args_hashstr) != 0 ||
455 (creds != NULL && creds->hashstr != NULL &&
456 strcmp(creds->hashstr, le->channel->creds_hashstr) != 0)) {
457 // somehow hash collision
458 create_and_add_channel_to_persistent_list(
459 channel, target, args, creds, key, key_len, target_upper_bound TSRMLS_CC);
460 } else {
461 efree(args.args);
462 free_grpc_channel_wrapper(channel->wrapper, false);
463 gpr_mu_destroy(&channel->wrapper->mu);
464 free(channel->wrapper);
465 channel->wrapper = NULL;
466 channel->wrapper = le->channel;
467 // One more Grpc\Channel object refer to it.
468 php_grpc_channel_ref(channel->wrapper);
469 update_and_get_target_upper_bound(target, target_upper_bound);
470 }
471 }
472 }
473
474 /**
475 * Get the endpoint this call/stream is connected to
476 * @return string The URI of the endpoint
477 */
PHP_METHOD(Channel,getTarget)478 PHP_METHOD(Channel, getTarget) {
479 wrapped_grpc_channel *channel =
480 PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
481 if (channel->wrapper == NULL) {
482 zend_throw_exception(spl_ce_RuntimeException,
483 "getTarget error."
484 "Channel is already closed.", 1 TSRMLS_CC);
485 return;
486 }
487 gpr_mu_lock(&channel->wrapper->mu);
488 char *target = grpc_channel_get_target(channel->wrapper->wrapped);
489 gpr_mu_unlock(&channel->wrapper->mu);
490 PHP_GRPC_RETVAL_STRING(target, 1);
491 gpr_free(target);
492 }
493
494 /**
495 * Get the connectivity state of the channel
496 * @param bool $try_to_connect Try to connect on the channel (optional)
497 * @return long The grpc connectivity state
498 */
PHP_METHOD(Channel,getConnectivityState)499 PHP_METHOD(Channel, getConnectivityState) {
500 wrapped_grpc_channel *channel =
501 PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
502 if (channel->wrapper == NULL) {
503 zend_throw_exception(spl_ce_RuntimeException,
504 "getConnectivityState error."
505 "Channel is already closed.", 1 TSRMLS_CC);
506 return;
507 }
508 gpr_mu_lock(&channel->wrapper->mu);
509 bool try_to_connect = false;
510 /* "|b" == 1 optional bool */
511 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &try_to_connect)
512 == FAILURE) {
513 zend_throw_exception(spl_ce_InvalidArgumentException,
514 "getConnectivityState expects a bool", 1 TSRMLS_CC);
515 gpr_mu_unlock(&channel->wrapper->mu);
516 return;
517 }
518 int state = grpc_channel_check_connectivity_state(channel->wrapper->wrapped,
519 (int)try_to_connect);
520 gpr_mu_unlock(&channel->wrapper->mu);
521 RETURN_LONG(state);
522 }
523
524 /**
525 * Watch the connectivity state of the channel until it changed
526 * @param long $last_state The previous connectivity state of the channel
527 * @param Timeval $deadline_obj The deadline this function should wait until
528 * @return bool If the connectivity state changes from last_state
529 * before deadline
530 */
PHP_METHOD(Channel,watchConnectivityState)531 PHP_METHOD(Channel, watchConnectivityState) {
532 wrapped_grpc_channel *channel =
533 PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
534 if (channel->wrapper == NULL) {
535 zend_throw_exception(spl_ce_RuntimeException,
536 "watchConnectivityState error"
537 "Channel is already closed.", 1 TSRMLS_CC);
538 return;
539 }
540 gpr_mu_lock(&channel->wrapper->mu);
541 php_grpc_long last_state;
542 zval *deadline_obj;
543
544 /* "lO" == 1 long 1 object */
545 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lO",
546 &last_state, &deadline_obj,
547 grpc_ce_timeval) == FAILURE) {
548 zend_throw_exception(spl_ce_InvalidArgumentException,
549 "watchConnectivityState expects 1 long 1 timeval",
550 1 TSRMLS_CC);
551 gpr_mu_unlock(&channel->wrapper->mu);
552 return;
553 }
554
555 wrapped_grpc_timeval *deadline =
556 PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_timeval, deadline_obj);
557 grpc_channel_watch_connectivity_state(channel->wrapper->wrapped,
558 (grpc_connectivity_state)last_state,
559 deadline->wrapped, completion_queue,
560 NULL);
561 grpc_event event =
562 grpc_completion_queue_pluck(completion_queue, NULL,
563 gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
564 gpr_mu_unlock(&channel->wrapper->mu);
565 RETURN_BOOL(event.success);
566 }
567
568 /**
569 * Close the channel
570 * @return void
571 */
PHP_METHOD(Channel,close)572 PHP_METHOD(Channel, close) {
573 wrapped_grpc_channel *channel =
574 PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
575 if (channel->wrapper != NULL) {
576 php_grpc_channel_unref(channel->wrapper);
577 channel->wrapper = NULL;
578 }
579 }
580
581 // Delete an entry from the persistent list
582 // Note: this does not destroy or close the underlying grpc_channel
php_grpc_delete_persistent_list_entry(char * key,php_grpc_int key_len TSRMLS_DC)583 void php_grpc_delete_persistent_list_entry(char *key, php_grpc_int key_len
584 TSRMLS_DC) {
585 php_grpc_zend_resource *rsrc;
586 gpr_mu_lock(&global_persistent_list_mu);
587 if (PHP_GRPC_PERSISTENT_LIST_FIND(&grpc_persistent_list, key,
588 key_len, rsrc)) {
589 php_grpc_zend_hash_del(&grpc_persistent_list, key, key_len+1);
590 }
591 gpr_mu_unlock(&global_persistent_list_mu);
592 }
593
594 // A destructor associated with each list entry from the persistent list
php_grpc_channel_plink_dtor(php_grpc_zend_resource * rsrc TSRMLS_DC)595 static void php_grpc_channel_plink_dtor(php_grpc_zend_resource *rsrc
596 TSRMLS_DC) {
597 channel_persistent_le_t *le = (channel_persistent_le_t *)rsrc->ptr;
598 if (le == NULL) {
599 return;
600 }
601 if (le->channel != NULL) {
602 php_grpc_channel_unref(le->channel);
603 le->channel = NULL;
604 }
605 free(le);
606 le = NULL;
607 }
608
609 // A destructor associated with each list entry from the target_bound map
php_grpc_target_bound_dtor(php_grpc_zend_resource * rsrc TSRMLS_DC)610 static void php_grpc_target_bound_dtor(php_grpc_zend_resource *rsrc
611 TSRMLS_DC) {
612 target_bound_le_t *le = (target_bound_le_t *) rsrc->ptr;
613 if (le == NULL) {
614 return;
615 }
616 free(le);
617 le = NULL;
618 }
619
620 #ifdef GRPC_PHP_DEBUG
621
622 /**
623 * Clean all channels in the persistent. Test only.
624 * @return void
625 */
PHP_METHOD(Channel,cleanPersistentList)626 PHP_METHOD(Channel, cleanPersistentList) {
627 zend_hash_clean(&grpc_persistent_list);
628 zend_hash_clean(&grpc_target_upper_bound_map);
629 }
630
grpc_connectivity_state_name(grpc_connectivity_state state)631 char *grpc_connectivity_state_name(grpc_connectivity_state state) {
632 switch (state) {
633 case GRPC_CHANNEL_IDLE:
634 return "IDLE";
635 case GRPC_CHANNEL_CONNECTING:
636 return "CONNECTING";
637 case GRPC_CHANNEL_READY:
638 return "READY";
639 case GRPC_CHANNEL_TRANSIENT_FAILURE:
640 return "TRANSIENT_FAILURE";
641 case GRPC_CHANNEL_SHUTDOWN:
642 return "SHUTDOWN";
643 }
644 return "UNKNOWN";
645 }
646
647 /**
648 * Return the info about the current channel. Test only.
649 * @return array
650 */
PHP_METHOD(Channel,getChannelInfo)651 PHP_METHOD(Channel, getChannelInfo) {
652 wrapped_grpc_channel *channel =
653 PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
654 array_init(return_value);
655 // Info about the target
656 PHP_GRPC_ADD_STRING_TO_ARRAY(return_value, "target",
657 sizeof("target"), channel->wrapper->target, true);
658 // Info about the upper bound for the target
659 target_bound_le_t* target_bound_status =
660 update_and_get_target_upper_bound(channel->wrapper->target, -1);
661 PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "target_upper_bound",
662 sizeof("target_upper_bound"), target_bound_status->upper_bound);
663 PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "target_current_size",
664 sizeof("target_current_size"), target_bound_status->current_count);
665 // Info about key
666 PHP_GRPC_ADD_STRING_TO_ARRAY(return_value, "key",
667 sizeof("key"), channel->wrapper->key, true);
668 // Info about persistent channel ref_count
669 PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "ref_count",
670 sizeof("ref_count"), channel->wrapper->ref_count);
671 // Info about connectivity status
672 int state =
673 grpc_channel_check_connectivity_state(channel->wrapper->wrapped, (int)0);
674 // It should be set to 'true' in PHP 5.6.33
675 PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "connectivity_status",
676 sizeof("connectivity_status"), state);
677 PHP_GRPC_ADD_STRING_TO_ARRAY(return_value, "ob",
678 sizeof("ob"),
679 grpc_connectivity_state_name(state), true);
680 // Info about the channel is closed or not
681 PHP_GRPC_ADD_BOOL_TO_ARRAY(return_value, "is_valid",
682 sizeof("is_valid"), (channel->wrapper == NULL));
683 }
684
685 /**
686 * Return an array of all channels in the persistent list. Test only.
687 * @return array
688 */
PHP_METHOD(Channel,getPersistentList)689 PHP_METHOD(Channel, getPersistentList) {
690 array_init(return_value);
691 zval *data;
692 PHP_GRPC_HASH_FOREACH_VAL_START(&grpc_persistent_list, data)
693 php_grpc_zend_resource *rsrc =
694 (php_grpc_zend_resource*) PHP_GRPC_HASH_VALPTR_TO_VAL(data)
695 if (rsrc == NULL) {
696 break;
697 }
698 channel_persistent_le_t* le = rsrc->ptr;
699 zval* ret_arr;
700 PHP_GRPC_MAKE_STD_ZVAL(ret_arr);
701 array_init(ret_arr);
702 // Info about the target
703 PHP_GRPC_ADD_STRING_TO_ARRAY(ret_arr, "target",
704 sizeof("target"), le->channel->target, true);
705 // Info about the upper bound for the target
706 target_bound_le_t* target_bound_status =
707 update_and_get_target_upper_bound(le->channel->target, -1);
708 PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "target_upper_bound",
709 sizeof("target_upper_bound"), target_bound_status->upper_bound);
710 PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "target_current_size",
711 sizeof("target_current_size"), target_bound_status->current_count);
712 // Info about key
713 PHP_GRPC_ADD_STRING_TO_ARRAY(ret_arr, "key",
714 sizeof("key"), le->channel->key, true);
715 // Info about persistent channel ref_count
716 PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "ref_count",
717 sizeof("ref_count"), le->channel->ref_count);
718 // Info about connectivity status
719 int state =
720 grpc_channel_check_connectivity_state(le->channel->wrapped, (int)0);
721 // It should be set to 'true' in PHP 5.6.33
722 PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "connectivity_status",
723 sizeof("connectivity_status"), state);
724 PHP_GRPC_ADD_STRING_TO_ARRAY(ret_arr, "ob",
725 sizeof("ob"),
726 grpc_connectivity_state_name(state), true);
727 add_assoc_zval(return_value, le->channel->key, ret_arr);
728 PHP_GRPC_FREE_STD_ZVAL(ret_arr);
729 PHP_GRPC_HASH_FOREACH_END()
730 }
731 #endif
732
733
734 ZEND_BEGIN_ARG_INFO_EX(arginfo_construct, 0, 0, 2)
735 ZEND_ARG_INFO(0, target)
736 ZEND_ARG_INFO(0, args)
737 ZEND_END_ARG_INFO()
738
739 ZEND_BEGIN_ARG_INFO_EX(arginfo_getTarget, 0, 0, 0)
740 ZEND_END_ARG_INFO()
741
742 ZEND_BEGIN_ARG_INFO_EX(arginfo_getConnectivityState, 0, 0, 0)
743 ZEND_ARG_INFO(0, try_to_connect)
744 ZEND_END_ARG_INFO()
745
746 ZEND_BEGIN_ARG_INFO_EX(arginfo_watchConnectivityState, 0, 0, 2)
747 ZEND_ARG_INFO(0, last_state)
748 ZEND_ARG_INFO(0, deadline)
749 ZEND_END_ARG_INFO()
750
751 ZEND_BEGIN_ARG_INFO_EX(arginfo_close, 0, 0, 0)
752 ZEND_END_ARG_INFO()
753
754 #ifdef GRPC_PHP_DEBUG
755 ZEND_BEGIN_ARG_INFO_EX(arginfo_getChannelInfo, 0, 0, 0)
756 ZEND_END_ARG_INFO()
757
758 ZEND_BEGIN_ARG_INFO_EX(arginfo_cleanPersistentList, 0, 0, 0)
759 ZEND_END_ARG_INFO()
760
761 ZEND_BEGIN_ARG_INFO_EX(arginfo_getPersistentList, 0, 0, 0)
762 ZEND_END_ARG_INFO()
763 #endif
764
765
766 static zend_function_entry channel_methods[] = {
767 PHP_ME(Channel, __construct, arginfo_construct,
768 ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
769 PHP_ME(Channel, getTarget, arginfo_getTarget,
770 ZEND_ACC_PUBLIC)
771 PHP_ME(Channel, getConnectivityState, arginfo_getConnectivityState,
772 ZEND_ACC_PUBLIC)
773 PHP_ME(Channel, watchConnectivityState, arginfo_watchConnectivityState,
774 ZEND_ACC_PUBLIC)
775 PHP_ME(Channel, close, arginfo_close,
776 ZEND_ACC_PUBLIC)
777 #ifdef GRPC_PHP_DEBUG
778 PHP_ME(Channel, getChannelInfo, arginfo_getChannelInfo,
779 ZEND_ACC_PUBLIC)
780 PHP_ME(Channel, cleanPersistentList, arginfo_cleanPersistentList,
781 ZEND_ACC_PUBLIC)
782 PHP_ME(Channel, getPersistentList, arginfo_getPersistentList,
783 ZEND_ACC_PUBLIC)
784 #endif
785 PHP_FE_END
786 };
787
GRPC_STARTUP_FUNCTION(channel)788 GRPC_STARTUP_FUNCTION(channel) {
789 zend_class_entry ce;
790 INIT_CLASS_ENTRY(ce, "Grpc\\Channel", channel_methods);
791 ce.create_object = create_wrapped_grpc_channel;
792 grpc_ce_channel = zend_register_internal_class(&ce TSRMLS_CC);
793 gpr_mu_init(&global_persistent_list_mu);
794 le_plink = zend_register_list_destructors_ex(
795 NULL, php_grpc_channel_plink_dtor, "Persistent Channel", module_number);
796 zend_hash_init_ex(&grpc_persistent_list, 20, NULL,
797 EG(persistent_list).pDestructor, 1, 0);
798 // Register the target->upper_bound map.
799 le_bound = zend_register_list_destructors_ex(
800 NULL, php_grpc_target_bound_dtor, "Target Bound", module_number);
801 zend_hash_init_ex(&grpc_target_upper_bound_map, 20, NULL,
802 EG(persistent_list).pDestructor, 1, 0);
803
804 PHP_GRPC_INIT_HANDLER(wrapped_grpc_channel, channel_ce_handlers);
805 return SUCCESS;
806 }
807