• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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