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