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