xref: /aosp_15_r20/external/grpc-grpc/src/php/ext/grpc/channel.c (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
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     grpc_channel_credentials* insecure_creds = grpc_insecure_credentials_create();
245     channel->wrapper->wrapped = grpc_channel_create(target, insecure_creds, &args);
246     grpc_channel_credentials_release(insecure_creds);
247   } else {
248     channel->wrapper->wrapped =
249         grpc_channel_create(target, creds->wrapped, &args);
250   }
251   // There is an Grpc\Channel object refer to it.
252   php_grpc_channel_ref(channel->wrapper);
253   efree(args.args);
254 }
255 
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)256 void create_and_add_channel_to_persistent_list(
257     wrapped_grpc_channel *channel,
258     char *target,
259     grpc_channel_args args,
260     wrapped_grpc_channel_credentials *creds,
261     char *key,
262     php_grpc_int key_len,
263     int target_upper_bound TSRMLS_DC) {
264   target_bound_le_t* target_bound_status =
265     update_and_get_target_upper_bound(target, target_upper_bound);
266   // Check the upper bound status before inserting to the persistent map.
267   if (target_bound_status->current_count >=
268       target_bound_status->upper_bound) {
269     if (!php_grpc_persistent_list_delete_unused_channel(
270           target, target_bound_status TSRMLS_CC)) {
271       // If no channel can be deleted from the persistent map,
272       // do not persist this one.
273       create_channel(channel, target, args, creds);
274       gpr_log(GPR_INFO, "[Warning] The number of channel for the"
275                  " target %s is maxed out bounded.\n", target);
276       gpr_log(GPR_INFO, "[Warning] Target upper bound: %d. Current size: %d.\n",
277                  target_bound_status->upper_bound,
278                  target_bound_status->current_count);
279       gpr_log(GPR_INFO, "[Warning] Target %s will not be persisted.\n", target);
280       return;
281     }
282   }
283   // There is space in the persistent map.
284   php_grpc_zend_resource new_rsrc;
285   channel_persistent_le_t *le;
286   // this links each persistent list entry to a destructor
287   new_rsrc.type = le_plink;
288   le = malloc(sizeof(channel_persistent_le_t));
289 
290   create_channel(channel, target, args, creds);
291   target_bound_status->current_count += 1;
292 
293   le->channel = channel->wrapper;
294   new_rsrc.ptr = le;
295   gpr_mu_lock(&global_persistent_list_mu);
296   PHP_GRPC_PERSISTENT_LIST_UPDATE(&grpc_persistent_list, key, key_len,
297                                   (void *)&new_rsrc);
298   // Persistent map refer to it.
299   php_grpc_channel_ref(channel->wrapper);
300   gpr_mu_unlock(&global_persistent_list_mu);
301 }
302 
303 /**
304  * Construct an instance of the Channel class.
305  *
306  * By default, the underlying grpc_channel is "persistent". That is, given
307  * the same set of parameters passed to the constructor, the same underlying
308  * grpc_channel will be returned.
309  *
310  * If the $args array contains a "credentials" key mapping to a
311  * ChannelCredentials object, a secure channel will be created with those
312  * credentials.
313  *
314  * If the $args array contains a "force_new" key mapping to a boolean value
315  * of "true", a new and separate underlying grpc_channel will be created
316  * and returned. This will not affect existing channels.
317  *
318  * @param string $target The hostname to associate with this channel
319  * @param array $args_array The arguments to pass to the Channel
320  */
PHP_METHOD(Channel,__construct)321 PHP_METHOD(Channel, __construct) {
322   wrapped_grpc_channel *channel =
323     PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
324   zval *creds_obj = NULL;
325   char *target;
326   php_grpc_int target_length;
327   zval *args_array = NULL;
328   grpc_channel_args args;
329   HashTable *array_hash;
330   wrapped_grpc_channel_credentials *creds = NULL;
331   php_grpc_zend_resource *rsrc;
332   bool force_new = false;
333   zval *force_new_obj = NULL;
334   int target_upper_bound = -1;
335 
336   /* "sa" == 1 string, 1 array */
337   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", &target,
338                             &target_length, &args_array) == FAILURE) {
339     zend_throw_exception(spl_ce_InvalidArgumentException,
340                          "Channel expects a string and an array", 1 TSRMLS_CC);
341     return;
342   }
343   array_hash = Z_ARRVAL_P(args_array);
344   if (php_grpc_zend_hash_find(array_hash, "credentials", sizeof("credentials"),
345                               (void **)&creds_obj) == SUCCESS) {
346     if (Z_TYPE_P(creds_obj) == IS_NULL) {
347       creds = NULL;
348     } else if (PHP_GRPC_GET_CLASS_ENTRY(creds_obj) !=
349                grpc_ce_channel_credentials) {
350       zend_throw_exception(spl_ce_InvalidArgumentException,
351                            "credentials must be a ChannelCredentials object",
352                            1 TSRMLS_CC);
353       return;
354     } else {
355       creds = PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel_credentials,
356                                           creds_obj);
357     }
358   }
359   if (php_grpc_zend_hash_find(array_hash, "force_new", sizeof("force_new"),
360                               (void **)&force_new_obj) == SUCCESS) {
361     if (PHP_GRPC_BVAL_IS_TRUE(force_new_obj)) {
362       force_new = true;
363     }
364   }
365 
366   if (php_grpc_zend_hash_find(array_hash, "grpc_target_persist_bound",
367                               sizeof("grpc_target_persist_bound"),
368                               (void **)&force_new_obj) == SUCCESS) {
369     if (Z_TYPE_P(force_new_obj) != IS_LONG) {
370       zend_throw_exception(spl_ce_InvalidArgumentException,
371                            "plist_bound must be a number",
372                            1 TSRMLS_CC);
373     }
374     target_upper_bound = (int)Z_LVAL_P(force_new_obj);
375   }
376 
377   // parse the rest of the channel args array
378   if (php_grpc_read_args_array(args_array, &args TSRMLS_CC) == FAILURE) {
379     efree(args.args);
380     return;
381   }
382 
383   // Construct a hashkey for the persistent channel
384   // Currently, the hashkey contains 3 parts:
385   // 1. hostname
386   // 2. hash value of the channel args (args_array excluding "credentials",
387   //    "force_new" and "grpc_target_persist_bound")
388   // 3. (optional) hash value of the ChannelCredentials object
389 
390   char sha1str[41] = { 0 };
391   unsigned char digest[20] = { 0 };
392   PHP_SHA1_CTX context;
393   PHP_SHA1Init(&context);
394   for (int i = 0; i < args.num_args; i++) {
395     PHP_GRPC_SHA1Update(&context, args.args[i].key, strlen(args.args[i].key) + 1);
396     switch (args.args[i].type) {
397     case GRPC_ARG_INTEGER:
398       PHP_GRPC_SHA1Update(&context, &args.args[i].value.integer, 4);
399       break;
400     case GRPC_ARG_STRING:
401       PHP_GRPC_SHA1Update(&context, args.args[i].value.string, strlen(args.args[i].value.string) + 1);
402       break;
403     default:
404       zend_throw_exception(spl_ce_InvalidArgumentException,
405                            "args values must be int or string", 1 TSRMLS_CC);
406       return;
407     }
408   };
409   PHP_SHA1Final(digest, &context);
410   make_sha1_digest(sha1str, digest);
411 
412   php_grpc_int key_len = target_length + strlen(sha1str);
413   if (creds != NULL && creds->hashstr != NULL) {
414     key_len += strlen(creds->hashstr);
415   }
416   char *key = malloc(key_len + 1);
417   strcpy(key, target);
418   strcat(key, sha1str);
419   if (creds != NULL && creds->hashstr != NULL) {
420     strcat(key, creds->hashstr);
421   }
422   channel->wrapper = malloc(sizeof(grpc_channel_wrapper));
423   channel->wrapper->ref_count = 0;
424   channel->wrapper->key = key;
425   channel->wrapper->target = strdup(target);
426   channel->wrapper->args_hashstr = strdup(sha1str);
427   channel->wrapper->creds_hashstr = NULL;
428   channel->wrapper->creds = creds;
429   channel->wrapper->args = args;
430   if (creds != NULL && creds->hashstr != NULL) {
431     php_grpc_int creds_hashstr_len = strlen(creds->hashstr);
432     char *channel_creds_hashstr = malloc(creds_hashstr_len + 1);
433     strcpy(channel_creds_hashstr, creds->hashstr);
434     channel->wrapper->creds_hashstr = channel_creds_hashstr;
435   }
436 
437   gpr_mu_init(&channel->wrapper->mu);
438   if (force_new || (creds != NULL && creds->has_call_creds)) {
439     // If the ChannelCredentials object was composed with a CallCredentials
440     // object, there is no way we can tell them apart. Do NOT persist
441     // them. They should be individually destroyed.
442     create_channel(channel, target, args, creds);
443   } else if (!(PHP_GRPC_PERSISTENT_LIST_FIND(&grpc_persistent_list, key,
444                                              key_len, rsrc))) {
445     create_and_add_channel_to_persistent_list(
446         channel, target, args, creds, key, key_len, target_upper_bound TSRMLS_CC);
447   } else {
448     // Found a previously stored channel in the persistent list
449     channel_persistent_le_t *le = (channel_persistent_le_t *)rsrc->ptr;
450     if (strcmp(target, le->channel->target) != 0 ||
451         strcmp(sha1str, le->channel->args_hashstr) != 0 ||
452         (creds != NULL && creds->hashstr != NULL &&
453          strcmp(creds->hashstr, le->channel->creds_hashstr) != 0)) {
454       // somehow hash collision
455       create_and_add_channel_to_persistent_list(
456           channel, target, args, creds, key, key_len, target_upper_bound TSRMLS_CC);
457     } else {
458       efree(args.args);
459       free_grpc_channel_wrapper(channel->wrapper, false);
460       gpr_mu_destroy(&channel->wrapper->mu);
461       free(channel->wrapper);
462       channel->wrapper = NULL;
463       channel->wrapper = le->channel;
464       // One more Grpc\Channel object refer to it.
465       php_grpc_channel_ref(channel->wrapper);
466       update_and_get_target_upper_bound(target, target_upper_bound);
467     }
468   }
469 }
470 
471 /**
472  * Get the endpoint this call/stream is connected to
473  * @return string The URI of the endpoint
474  */
PHP_METHOD(Channel,getTarget)475 PHP_METHOD(Channel, getTarget) {
476   wrapped_grpc_channel *channel =
477     PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
478   if (channel->wrapper == NULL) {
479     zend_throw_exception(spl_ce_RuntimeException,
480                          "getTarget error."
481                          "Channel is already closed.", 1 TSRMLS_CC);
482     return;
483   }
484   gpr_mu_lock(&channel->wrapper->mu);
485   char *target = grpc_channel_get_target(channel->wrapper->wrapped);
486   gpr_mu_unlock(&channel->wrapper->mu);
487   PHP_GRPC_RETVAL_STRING(target, 1);
488   gpr_free(target);
489 }
490 
491 /**
492  * Get the connectivity state of the channel
493  * @param bool $try_to_connect Try to connect on the channel (optional)
494  * @return long The grpc connectivity state
495  */
PHP_METHOD(Channel,getConnectivityState)496 PHP_METHOD(Channel, getConnectivityState) {
497   wrapped_grpc_channel *channel =
498     PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
499   if (channel->wrapper == NULL) {
500     zend_throw_exception(spl_ce_RuntimeException,
501                          "getConnectivityState error."
502                          "Channel is already closed.", 1 TSRMLS_CC);
503     return;
504   }
505   gpr_mu_lock(&channel->wrapper->mu);
506   bool try_to_connect = false;
507   /* "|b" == 1 optional bool */
508   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &try_to_connect)
509       == FAILURE) {
510     zend_throw_exception(spl_ce_InvalidArgumentException,
511                          "getConnectivityState expects a bool", 1 TSRMLS_CC);
512     gpr_mu_unlock(&channel->wrapper->mu);
513     return;
514   }
515   int state = grpc_channel_check_connectivity_state(channel->wrapper->wrapped,
516                                                     (int)try_to_connect);
517   gpr_mu_unlock(&channel->wrapper->mu);
518   RETURN_LONG(state);
519 }
520 
521 /**
522  * Watch the connectivity state of the channel until it changed
523  * @param long $last_state The previous connectivity state of the channel
524  * @param Timeval $deadline_obj The deadline this function should wait until
525  * @return bool If the connectivity state changes from last_state
526  *              before deadline
527  */
PHP_METHOD(Channel,watchConnectivityState)528 PHP_METHOD(Channel, watchConnectivityState) {
529   wrapped_grpc_channel *channel =
530     PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
531   if (channel->wrapper == NULL) {
532     zend_throw_exception(spl_ce_RuntimeException,
533                          "watchConnectivityState error"
534                          "Channel is already closed.", 1 TSRMLS_CC);
535     return;
536   }
537   gpr_mu_lock(&channel->wrapper->mu);
538   php_grpc_long last_state;
539   zval *deadline_obj;
540 
541   /* "lO" == 1 long 1 object */
542   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lO",
543                             &last_state, &deadline_obj,
544                             grpc_ce_timeval) == FAILURE) {
545     zend_throw_exception(spl_ce_InvalidArgumentException,
546                          "watchConnectivityState expects 1 long 1 timeval",
547                          1 TSRMLS_CC);
548     gpr_mu_unlock(&channel->wrapper->mu);
549     return;
550   }
551 
552   wrapped_grpc_timeval *deadline =
553     PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_timeval, deadline_obj);
554   grpc_channel_watch_connectivity_state(channel->wrapper->wrapped,
555                                         (grpc_connectivity_state)last_state,
556                                         deadline->wrapped, completion_queue,
557                                         NULL);
558   grpc_event event =
559       grpc_completion_queue_pluck(completion_queue, NULL,
560                                   gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
561   gpr_mu_unlock(&channel->wrapper->mu);
562   RETURN_BOOL(event.success);
563 }
564 
565 /**
566  * Close the channel
567  * @return void
568  */
PHP_METHOD(Channel,close)569 PHP_METHOD(Channel, close) {
570   wrapped_grpc_channel *channel =
571     PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
572   if (channel->wrapper != NULL) {
573     php_grpc_channel_unref(channel->wrapper);
574     channel->wrapper = NULL;
575   }
576 }
577 
578 // Delete an entry from the persistent list
579 // 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)580 void php_grpc_delete_persistent_list_entry(char *key, php_grpc_int key_len
581                                            TSRMLS_DC) {
582   php_grpc_zend_resource *rsrc;
583   gpr_mu_lock(&global_persistent_list_mu);
584   if (PHP_GRPC_PERSISTENT_LIST_FIND(&grpc_persistent_list, key,
585                                     key_len, rsrc)) {
586     php_grpc_zend_hash_del(&grpc_persistent_list, key, key_len+1);
587   }
588   gpr_mu_unlock(&global_persistent_list_mu);
589 }
590 
591 // A destructor associated with each list entry from the persistent list
php_grpc_channel_plink_dtor(php_grpc_zend_resource * rsrc TSRMLS_DC)592 static void php_grpc_channel_plink_dtor(php_grpc_zend_resource *rsrc
593                                         TSRMLS_DC) {
594   channel_persistent_le_t *le = (channel_persistent_le_t *)rsrc->ptr;
595   if (le == NULL) {
596     return;
597   }
598   if (le->channel != NULL) {
599     php_grpc_channel_unref(le->channel);
600     le->channel = NULL;
601   }
602   free(le);
603   le = NULL;
604 }
605 
606 // A destructor associated with each list entry from the target_bound map
php_grpc_target_bound_dtor(php_grpc_zend_resource * rsrc TSRMLS_DC)607 static void php_grpc_target_bound_dtor(php_grpc_zend_resource *rsrc
608                                         TSRMLS_DC) {
609   target_bound_le_t *le = (target_bound_le_t *) rsrc->ptr;
610   if (le == NULL) {
611     return;
612   }
613   free(le);
614   le = NULL;
615 }
616 
617 #ifdef GRPC_PHP_DEBUG
618 
619 /**
620 * Clean all channels in the persistent. Test only.
621 * @return void
622 */
PHP_METHOD(Channel,cleanPersistentList)623 PHP_METHOD(Channel, cleanPersistentList) {
624   zend_hash_clean(&grpc_persistent_list);
625   zend_hash_clean(&grpc_target_upper_bound_map);
626 }
627 
grpc_connectivity_state_name(grpc_connectivity_state state)628 char *grpc_connectivity_state_name(grpc_connectivity_state state) {
629  switch (state) {
630    case GRPC_CHANNEL_IDLE:
631      return "IDLE";
632    case GRPC_CHANNEL_CONNECTING:
633      return "CONNECTING";
634    case GRPC_CHANNEL_READY:
635      return "READY";
636    case GRPC_CHANNEL_TRANSIENT_FAILURE:
637      return "TRANSIENT_FAILURE";
638    case GRPC_CHANNEL_SHUTDOWN:
639      return "SHUTDOWN";
640  }
641  return "UNKNOWN";
642 }
643 
644 /**
645 * Return the info about the current channel. Test only.
646 * @return array
647 */
PHP_METHOD(Channel,getChannelInfo)648 PHP_METHOD(Channel, getChannelInfo) {
649   wrapped_grpc_channel *channel =
650     PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
651   array_init(return_value);
652    // Info about the target
653   PHP_GRPC_ADD_STRING_TO_ARRAY(return_value, "target",
654               sizeof("target"), channel->wrapper->target, true);
655   // Info about the upper bound for the target
656   target_bound_le_t* target_bound_status =
657     update_and_get_target_upper_bound(channel->wrapper->target, -1);
658   PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "target_upper_bound",
659     sizeof("target_upper_bound"), target_bound_status->upper_bound);
660   PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "target_current_size",
661     sizeof("target_current_size"), target_bound_status->current_count);
662   // Info about key
663   PHP_GRPC_ADD_STRING_TO_ARRAY(return_value, "key",
664               sizeof("key"), channel->wrapper->key, true);
665   // Info about persistent channel ref_count
666   PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "ref_count",
667               sizeof("ref_count"), channel->wrapper->ref_count);
668   // Info about connectivity status
669   int state =
670       grpc_channel_check_connectivity_state(channel->wrapper->wrapped, (int)0);
671   // It should be set to 'true' in PHP 5.6.33
672   PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "connectivity_status",
673               sizeof("connectivity_status"), state);
674   PHP_GRPC_ADD_STRING_TO_ARRAY(return_value, "ob",
675               sizeof("ob"),
676               grpc_connectivity_state_name(state), true);
677   // Info about the channel is closed or not
678   PHP_GRPC_ADD_BOOL_TO_ARRAY(return_value, "is_valid",
679               sizeof("is_valid"), (channel->wrapper == NULL));
680 }
681 
682 /**
683 * Return an array of all channels in the persistent list. Test only.
684 * @return array
685 */
PHP_METHOD(Channel,getPersistentList)686 PHP_METHOD(Channel, getPersistentList) {
687   array_init(return_value);
688   zval *data;
689   PHP_GRPC_HASH_FOREACH_VAL_START(&grpc_persistent_list, data)
690     php_grpc_zend_resource *rsrc  =
691                 (php_grpc_zend_resource*) PHP_GRPC_HASH_VALPTR_TO_VAL(data)
692     if (rsrc == NULL) {
693       break;
694     }
695     channel_persistent_le_t* le = rsrc->ptr;
696     zval* ret_arr;
697     PHP_GRPC_MAKE_STD_ZVAL(ret_arr);
698     array_init(ret_arr);
699     // Info about the target
700     PHP_GRPC_ADD_STRING_TO_ARRAY(ret_arr, "target",
701                 sizeof("target"), le->channel->target, true);
702     // Info about the upper bound for the target
703     target_bound_le_t* target_bound_status =
704       update_and_get_target_upper_bound(le->channel->target, -1);
705     PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "target_upper_bound",
706       sizeof("target_upper_bound"), target_bound_status->upper_bound);
707     PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "target_current_size",
708       sizeof("target_current_size"), target_bound_status->current_count);
709     // Info about key
710     PHP_GRPC_ADD_STRING_TO_ARRAY(ret_arr, "key",
711                 sizeof("key"), le->channel->key, true);
712     // Info about persistent channel ref_count
713     PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "ref_count",
714                 sizeof("ref_count"), le->channel->ref_count);
715     // Info about connectivity status
716     int state =
717         grpc_channel_check_connectivity_state(le->channel->wrapped, (int)0);
718     // It should be set to 'true' in PHP 5.6.33
719     PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "connectivity_status",
720                 sizeof("connectivity_status"), state);
721     PHP_GRPC_ADD_STRING_TO_ARRAY(ret_arr, "ob",
722                 sizeof("ob"),
723                 grpc_connectivity_state_name(state), true);
724     add_assoc_zval(return_value, le->channel->key, ret_arr);
725     PHP_GRPC_FREE_STD_ZVAL(ret_arr);
726   PHP_GRPC_HASH_FOREACH_END()
727 }
728 #endif
729 
730 
731 ZEND_BEGIN_ARG_INFO_EX(arginfo_construct, 0, 0, 2)
732   ZEND_ARG_INFO(0, target)
733   ZEND_ARG_INFO(0, args)
734 ZEND_END_ARG_INFO()
735 
736 ZEND_BEGIN_ARG_INFO_EX(arginfo_getTarget, 0, 0, 0)
737 ZEND_END_ARG_INFO()
738 
739 ZEND_BEGIN_ARG_INFO_EX(arginfo_getConnectivityState, 0, 0, 0)
740   ZEND_ARG_INFO(0, try_to_connect)
741 ZEND_END_ARG_INFO()
742 
743 ZEND_BEGIN_ARG_INFO_EX(arginfo_watchConnectivityState, 0, 0, 2)
744   ZEND_ARG_INFO(0, last_state)
745   ZEND_ARG_INFO(0, deadline)
746 ZEND_END_ARG_INFO()
747 
748 ZEND_BEGIN_ARG_INFO_EX(arginfo_close, 0, 0, 0)
749 ZEND_END_ARG_INFO()
750 
751 #ifdef GRPC_PHP_DEBUG
752 ZEND_BEGIN_ARG_INFO_EX(arginfo_getChannelInfo, 0, 0, 0)
753 ZEND_END_ARG_INFO()
754 
755 ZEND_BEGIN_ARG_INFO_EX(arginfo_cleanPersistentList, 0, 0, 0)
756 ZEND_END_ARG_INFO()
757 
758 ZEND_BEGIN_ARG_INFO_EX(arginfo_getPersistentList, 0, 0, 0)
759 ZEND_END_ARG_INFO()
760 #endif
761 
762 
763 static zend_function_entry channel_methods[] = {
764   PHP_ME(Channel, __construct, arginfo_construct,
765          ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
766   PHP_ME(Channel, getTarget, arginfo_getTarget,
767          ZEND_ACC_PUBLIC)
768   PHP_ME(Channel, getConnectivityState, arginfo_getConnectivityState,
769          ZEND_ACC_PUBLIC)
770   PHP_ME(Channel, watchConnectivityState, arginfo_watchConnectivityState,
771          ZEND_ACC_PUBLIC)
772   PHP_ME(Channel, close, arginfo_close,
773          ZEND_ACC_PUBLIC)
774   #ifdef GRPC_PHP_DEBUG
775   PHP_ME(Channel, getChannelInfo, arginfo_getChannelInfo,
776          ZEND_ACC_PUBLIC)
777   PHP_ME(Channel, cleanPersistentList, arginfo_cleanPersistentList,
778          ZEND_ACC_PUBLIC)
779   PHP_ME(Channel, getPersistentList, arginfo_getPersistentList,
780          ZEND_ACC_PUBLIC)
781   #endif
782   PHP_FE_END
783 };
784 
GRPC_STARTUP_FUNCTION(channel)785 GRPC_STARTUP_FUNCTION(channel) {
786   zend_class_entry ce;
787   INIT_CLASS_ENTRY(ce, "Grpc\\Channel", channel_methods);
788   ce.create_object = create_wrapped_grpc_channel;
789   grpc_ce_channel = zend_register_internal_class(&ce TSRMLS_CC);
790   gpr_mu_init(&global_persistent_list_mu);
791   le_plink = zend_register_list_destructors_ex(
792       NULL, php_grpc_channel_plink_dtor, "Persistent Channel", module_number);
793   ZEND_HASH_INIT(&grpc_persistent_list, 20, EG(persistent_list).pDestructor, 1);
794   // Register the target->upper_bound map.
795   le_bound = zend_register_list_destructors_ex(
796       NULL, php_grpc_target_bound_dtor, "Target Bound", module_number);
797   ZEND_HASH_INIT(&grpc_target_upper_bound_map, 20, EG(persistent_list).pDestructor, 1);
798 
799   PHP_GRPC_INIT_HANDLER(wrapped_grpc_channel, channel_ce_handlers);
800   return SUCCESS;
801 }
802