• 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 #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