1<?php
2/*
3 *
4 * Copyright 2015 gRPC authors.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19
20/**
21 * @group persistent_list_bound_tests
22 */
23class PersistentListTest extends \PHPUnit\Framework\TestCase
24{
25  public function setUp(): void
26  {
27  }
28
29  public function tearDown(): void
30  {
31    $channel_clean_persistent =
32        new Grpc\Channel('localhost:50010', []);
33    $plist = $channel_clean_persistent->getPersistentList();
34    $channel_clean_persistent->cleanPersistentList();
35  }
36
37  public function waitUntilNotIdle($channel) {
38      for ($i = 0; $i < 10; $i++) {
39          $now = Grpc\Timeval::now();
40          $deadline = $now->add(new Grpc\Timeval(1000));
41          if ($channel->watchConnectivityState(GRPC\CHANNEL_IDLE,
42              $deadline)) {
43              return true;
44          }
45      }
46      $this->assertTrue(false);
47  }
48
49  public function assertConnecting($state) {
50      $this->assertTrue($state == GRPC\CHANNEL_CONNECTING ||
51      $state == GRPC\CHANNEL_TRANSIENT_FAILURE);
52  }
53
54  public function testInitHelper()
55  {
56      // PersistentList is not empty at the beginning of the tests
57      // because phpunit will cache the channels created by other test
58      // files.
59  }
60
61
62  public function testChannelNotPersist()
63  {
64      $this->channel1 = new Grpc\Channel('localhost:1', ['force_new' => true]);
65      $channel1_info = $this->channel1->getChannelInfo();
66      $plist_info = $this->channel1->getPersistentList();
67      $this->assertEquals($channel1_info['target'], 'localhost:1');
68      $this->assertEquals($channel1_info['ref_count'], 1);
69      $this->assertEquals($channel1_info['connectivity_status'],
70          GRPC\CHANNEL_IDLE);
71      $this->assertEquals(count($plist_info), 0);
72      $this->channel1->close();
73  }
74
75  public function testPersistentChannelCreateOneChannel()
76  {
77      $this->channel1 = new Grpc\Channel('localhost:1', []);
78      $channel1_info = $this->channel1->getChannelInfo();
79      $plist_info = $this->channel1->getPersistentList();
80      $this->assertEquals($channel1_info['target'], 'localhost:1');
81      $this->assertEquals($channel1_info['ref_count'], 2);
82      $this->assertEquals($channel1_info['connectivity_status'],
83                          GRPC\CHANNEL_IDLE);
84      $this->assertArrayHasKey($channel1_info['key'], $plist_info);
85      $this->assertEquals(count($plist_info), 1);
86      $this->channel1->close();
87  }
88
89  public function testPersistentChannelCreateMultipleChannels()
90  {
91      $this->channel1 = new Grpc\Channel('localhost:1', []);
92      $plist_info = $this->channel1->getPersistentList();
93      $this->assertEquals(count($plist_info), 1);
94
95      $this->channel2 = new Grpc\Channel('localhost:2', []);
96      $plist_info = $this->channel1->getPersistentList();
97      $this->assertEquals(count($plist_info), 2);
98
99      $this->channel3 = new Grpc\Channel('localhost:3', []);
100      $plist_info = $this->channel1->getPersistentList();
101      $this->assertEquals(count($plist_info), 3);
102  }
103
104  public function testPersistentChannelStatusChange()
105  {
106      $this->channel1 = new Grpc\Channel('localhost:4', []);
107      $channel1_info = $this->channel1->getChannelInfo();
108      $this->assertEquals($channel1_info['connectivity_status'],
109                          GRPC\CHANNEL_IDLE);
110
111      $this->channel1->getConnectivityState(true);
112      $this->waitUntilNotIdle($this->channel1);
113      $channel1_info = $this->channel1->getChannelInfo();
114      $this->assertConnecting($channel1_info['connectivity_status']);
115      $this->channel1->close();
116  }
117
118  public function testPersistentChannelCloseChannel()
119  {
120      $this->channel1 = new Grpc\Channel('localhost:1', []);
121      $this->channel2 = new Grpc\Channel('localhost:1', []);
122
123      $channel1_info = $this->channel1->getChannelInfo();
124      $this->assertEquals($channel1_info['ref_count'], 3);
125      $plist_info = $this->channel1->getPersistentList();
126      $this->assertEquals($plist_info[$channel1_info['key']]['ref_count'], 3);
127
128      $this->channel1->close();
129      $plist_info = $this->channel1->getPersistentList();
130      $this->assertEquals($plist_info[$channel1_info['key']]['ref_count'], 2);
131
132      $this->channel2->close();
133      $plist_info = $this->channel1->getPersistentList();
134      $this->assertEquals($plist_info[$channel1_info['key']]['ref_count'], 1);
135  }
136
137  public function testPersistentChannelSameTarget()
138  {
139      $this->channel1 = new Grpc\Channel('localhost:1', []);
140      $this->channel2 = new Grpc\Channel('localhost:1', []);
141      $plist = $this->channel2->getPersistentList();
142      $channel1_info = $this->channel1->getChannelInfo();
143      $channel2_info = $this->channel2->getChannelInfo();
144      // $channel1 and $channel2 shares the same channel, thus only 1
145      // channel should be in the persistent list.
146      $this->assertEquals($channel1_info['key'], $channel2_info['key']);
147      $this->assertArrayHasKey($channel1_info['key'], $plist);
148      $this->assertEquals(count($plist), 1);
149      $this->channel1->close();
150      $this->channel2->close();
151  }
152
153  public function testPersistentChannelDifferentTarget()
154  {
155      $this->channel1 = new Grpc\Channel('localhost:1', []);
156      $channel1_info = $this->channel1->getChannelInfo();
157      $this->channel2 = new Grpc\Channel('localhost:2', []);
158      $channel2_info = $this->channel1->getChannelInfo();
159      $plist_info = $this->channel1->getPersistentList();
160      $this->assertArrayHasKey($channel1_info['key'], $plist_info);
161      $this->assertArrayHasKey($channel2_info['key'], $plist_info);
162      $this->assertEquals($plist_info[$channel1_info['key']]['ref_count'], 2);
163      $this->assertEquals($plist_info[$channel2_info['key']]['ref_count'], 2);
164      $plist_info = $this->channel1->getPersistentList();
165      $this->assertEquals(count($plist_info), 2);
166      $this->channel1->close();
167      $this->channel2->close();
168  }
169
170  /**
171   * @expectedException RuntimeException
172   * @expectedExceptionMessage startBatch Error. Channel is closed
173   */
174  public function testPersistentChannelSharedChannelClose()
175  {
176      // same underlying channel
177      $this->channel1 = new Grpc\Channel('localhost:10001', [
178          "grpc_target_persist_bound" => 2,
179      ]);
180      $this->channel2 = new Grpc\Channel('localhost:10001', []);
181      $this->server = new Grpc\Server([]);
182      $this->port = $this->server->addHttp2Port('localhost:10001');
183      $this->server->start();
184
185      // channel2 can still be use
186      $state = $this->channel2->getConnectivityState();
187      $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
188
189      $call1 = new Grpc\Call($this->channel1,
190          '/foo',
191          Grpc\Timeval::infFuture());
192      $call2 = new Grpc\Call($this->channel2,
193          '/foo',
194          Grpc\Timeval::infFuture());
195      $call3 = new Grpc\Call($this->channel1,
196          '/foo',
197          Grpc\Timeval::infFuture());
198      $call4 = new Grpc\Call($this->channel2,
199          '/foo',
200          Grpc\Timeval::infFuture());
201      $batch = [
202          Grpc\OP_SEND_INITIAL_METADATA => [],
203      ];
204
205      $result = $call1->startBatch($batch);
206      $this->assertTrue($result->send_metadata);
207      $result = $call2->startBatch($batch);
208      $this->assertTrue($result->send_metadata);
209
210      $this->channel1->close();
211      // After closing channel1, channel2 can still be use
212      $result = $call4->startBatch($batch);
213      $this->assertTrue($result->send_metadata);
214      // channel 1 is closed, it will throw an exception.
215      $result = $call3->startBatch($batch);
216  }
217
218  public function testPersistentChannelTargetDefaultUpperBound()
219  {
220      $this->channel1 = new Grpc\Channel('localhost:10002', []);
221      $channel1_info = $this->channel1->getChannelInfo();
222      $this->assertEquals($channel1_info['target_upper_bound'], 1);
223      $this->assertEquals($channel1_info['target_current_size'], 1);
224  }
225
226  public function testPersistentChannelTargetUpperBoundZero()
227  {
228      $this->channel1 = new Grpc\Channel('localhost:10002', [
229          "grpc_target_persist_bound" => 0,
230      ]);
231      // channel1 will not be persisted.
232      $channel1_info = $this->channel1->getChannelInfo();
233      $this->assertEquals($channel1_info['target_upper_bound'], 0);
234      $this->assertEquals($channel1_info['target_current_size'], 0);
235      $plist_info = $this->channel1->getPersistentList();
236      $this->assertEquals(0, count($plist_info));
237  }
238
239  public function testPersistentChannelTargetUpperBoundNotZero()
240  {
241      $this->channel1 = new Grpc\Channel('localhost:10003', [
242          "grpc_target_persist_bound" => 3,
243      ]);
244      $channel1_info = $this->channel1->getChannelInfo();
245      $this->assertEquals($channel1_info['target_upper_bound'], 3);
246      $this->assertEquals($channel1_info['target_current_size'], 1);
247
248      // The upper bound should not be changed
249      $this->channel2 = new Grpc\Channel('localhost:10003', []);
250      $channel2_info = $this->channel2->getChannelInfo();
251      $this->assertEquals($channel2_info['target_upper_bound'], 3);
252      $this->assertEquals($channel2_info['target_current_size'], 1);
253
254      // The upper bound should not be changed
255      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
256          null);
257      $this->channel3 = new Grpc\Channel('localhost:10003',
258          ['credentials' => $channel_credentials]);
259      $channel3_info = $this->channel3->getChannelInfo();
260      $this->assertEquals($channel3_info['target_upper_bound'], 3);
261      $this->assertEquals($channel3_info['target_current_size'], 2);
262
263      // The upper bound should not be changed
264      $this->channel4 = new Grpc\Channel('localhost:10003', [
265          "grpc_target_persist_bound" => 5,
266      ]);
267      $channel4_info = $this->channel4->getChannelInfo();
268      $this->assertEquals($channel4_info['target_upper_bound'], 5);
269      $this->assertEquals($channel4_info['target_current_size'], 2);
270  }
271
272  public function testPersistentChannelDefaultOutBound1()
273  {
274      $this->channel1 = new Grpc\Channel('localhost:10004', []);
275      // Make channel1 not IDLE.
276      $this->channel1->getConnectivityState(true);
277      $this->waitUntilNotIdle($this->channel1);
278      $channel1_info = $this->channel1->getChannelInfo();
279      $this->assertConnecting($channel1_info['connectivity_status']);
280
281      // Since channel1 is CONNECTING, channel 2 will not be persisted
282      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
283        null);
284      $this->channel2 = new Grpc\Channel('localhost:10004',
285          ['credentials' => $channel_credentials]);
286      $channel2_info = $this->channel2->getChannelInfo();
287      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel2_info['connectivity_status']);
288
289      // By default, target 'localhost:10011' only persist one channel.
290      // Since channel1 is not Idle channel2 will not be persisted.
291      $plist_info = $this->channel1->getPersistentList();
292      $this->assertEquals(1, count($plist_info));
293      $this->assertArrayHasKey($channel1_info['key'], $plist_info);
294      $this->assertArrayNotHasKey($channel2_info['key'], $plist_info);
295  }
296
297  public function testPersistentChannelDefaultOutBound2()
298  {
299      $this->channel1 = new Grpc\Channel('localhost:10005', []);
300      $channel1_info = $this->channel1->getChannelInfo();
301      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel1_info['connectivity_status']);
302
303      // Although channel1 is IDLE, channel1 still has reference to the underline
304      // gRPC channel. channel2 will not be persisted
305      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
306        null);
307      $this->channel2 = new Grpc\Channel('localhost:10005',
308          ['credentials' => $channel_credentials]);
309      $channel2_info = $this->channel2->getChannelInfo();
310      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel2_info['connectivity_status']);
311
312      // By default, target 'localhost:10011' only persist one channel.
313      // Since channel1 Idle, channel2 will be persisted.
314      $plist_info = $this->channel1->getPersistentList();
315      $this->assertEquals(1, count($plist_info));
316      $this->assertArrayHasKey($channel1_info['key'], $plist_info);
317      $this->assertArrayNotHasKey($channel2_info['key'], $plist_info);
318  }
319
320  public function testPersistentChannelDefaultOutBound3()
321  {
322      $this->channel1 = new Grpc\Channel('localhost:10006', []);
323      $channel1_info = $this->channel1->getChannelInfo();
324      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel1_info['connectivity_status']);
325
326      $this->channel1->close();
327      // channel1 is closed, no reference holds to the underline channel.
328      // channel2 can be persisted.
329      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
330        null);
331      $this->channel2 = new Grpc\Channel('localhost:10006',
332        ['credentials' => $channel_credentials]);
333      $channel2_info = $this->channel2->getChannelInfo();
334      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel2_info['connectivity_status']);
335
336      // By default, target 'localhost:10011' only persist one channel.
337      // Since channel1 Idle, channel2 will be persisted.
338      $plist_info = $this->channel2->getPersistentList();
339      $this->assertEquals(1, count($plist_info));
340      $this->assertArrayHasKey($channel2_info['key'], $plist_info);
341      $this->assertArrayNotHasKey($channel1_info['key'], $plist_info);
342  }
343
344  public function testPersistentChannelTwoUpperBound()
345  {
346      $this->channel1 = new Grpc\Channel('localhost:10007', [
347          "grpc_target_persist_bound" => 2,
348      ]);
349      $channel1_info = $this->channel1->getChannelInfo();
350      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel1_info['connectivity_status']);
351
352      // Since channel1 is IDLE, channel 1 will be deleted
353      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
354          null);
355      $this->channel2 = new Grpc\Channel('localhost:10007',
356          ['credentials' => $channel_credentials]);
357      $channel2_info = $this->channel2->getChannelInfo();
358      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel2_info['connectivity_status']);
359
360      $plist_info = $this->channel1->getPersistentList();
361      $this->assertEquals(2, count($plist_info));
362      $this->assertArrayHasKey($channel1_info['key'], $plist_info);
363      $this->assertArrayHasKey($channel2_info['key'], $plist_info);
364  }
365
366  public function testPersistentChannelTwoUpperBoundOutBound1()
367  {
368      $this->channel1 = new Grpc\Channel('localhost:10011', [
369          "grpc_target_persist_bound" => 2,
370      ]);
371      $channel1_info = $this->channel1->getChannelInfo();
372
373      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
374        null);
375      $this->channel2 = new Grpc\Channel('localhost:10011',
376          ['credentials' => $channel_credentials]);
377      $channel2_info = $this->channel2->getChannelInfo();
378
379      // Close channel1, so that new channel can be persisted.
380      $this->channel1->close();
381
382      $channel_credentials = Grpc\ChannelCredentials::createSsl("a", null,
383        null);
384      $this->channel3 = new Grpc\Channel('localhost:10011',
385          ['credentials' => $channel_credentials]);
386      $channel3_info = $this->channel3->getChannelInfo();
387
388      $plist_info = $this->channel1->getPersistentList();
389      $this->assertEquals(2, count($plist_info));
390      $this->assertArrayNotHasKey($channel1_info['key'], $plist_info);
391      $this->assertArrayHasKey($channel2_info['key'], $plist_info);
392      $this->assertArrayHasKey($channel3_info['key'], $plist_info);
393  }
394
395  public function testPersistentChannelTwoUpperBoundOutBound2()
396  {
397      $this->channel1 = new Grpc\Channel('localhost:10012', [
398          "grpc_target_persist_bound" => 2,
399      ]);
400      $channel1_info = $this->channel1->getChannelInfo();
401
402      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
403        null);
404      $this->channel2 = new Grpc\Channel('localhost:10012',
405        ['credentials' => $channel_credentials]);
406      $channel2_info = $this->channel2->getChannelInfo();
407
408      // Close channel2, so that new channel can be persisted.
409      $this->channel2->close();
410
411      $channel_credentials = Grpc\ChannelCredentials::createSsl("a", null,
412        null);
413      $this->channel3 = new Grpc\Channel('localhost:10012',
414        ['credentials' => $channel_credentials]);
415      $channel3_info = $this->channel3->getChannelInfo();
416
417      $plist_info = $this->channel1->getPersistentList();
418      $this->assertEquals(2, count($plist_info));
419      $this->assertArrayHasKey($channel1_info['key'], $plist_info);
420      $this->assertArrayNotHasKey($channel2_info['key'], $plist_info);
421      $this->assertArrayHasKey($channel3_info['key'], $plist_info);
422  }
423
424  public function testPersistentChannelTwoUpperBoundOutBound3()
425  {
426      $this->channel1 = new Grpc\Channel('localhost:10013', [
427          "grpc_target_persist_bound" => 2,
428      ]);
429      $channel1_info = $this->channel1->getChannelInfo();
430
431      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
432          null);
433      $this->channel2 = new Grpc\Channel('localhost:10013',
434          ['credentials' => $channel_credentials]);
435      $this->channel2->getConnectivityState(true);
436      $this->waitUntilNotIdle($this->channel2);
437      $channel2_info = $this->channel2->getChannelInfo();
438      $this->assertConnecting($channel2_info['connectivity_status']);
439
440      // Only one channel will be deleted
441      $this->channel1->close();
442      $this->channel2->close();
443
444      $channel_credentials = Grpc\ChannelCredentials::createSsl("a", null,
445        null);
446      $this->channel3 = new Grpc\Channel('localhost:10013',
447          ['credentials' => $channel_credentials]);
448      $channel3_info = $this->channel3->getChannelInfo();
449
450      // Only the Idle Channel will be deleted
451      $plist_info = $this->channel1->getPersistentList();
452      $this->assertEquals(2, count($plist_info));
453      $this->assertArrayNotHasKey($channel1_info['key'], $plist_info);
454      $this->assertArrayHasKey($channel2_info['key'], $plist_info);
455      $this->assertArrayHasKey($channel3_info['key'], $plist_info);
456  }
457
458  public function testPersistentChannelTwoUpperBoundOutBound4()
459  {
460      $this->channel1 = new Grpc\Channel('localhost:10014', [
461          "grpc_target_persist_bound" => 2,
462      ]);
463      $this->channel1->getConnectivityState(true);
464      $this->waitUntilNotIdle($this->channel1);
465      $channel1_info = $this->channel1->getChannelInfo();
466      $this->assertConnecting($channel1_info['connectivity_status']);
467
468      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
469          null);
470      $this->channel2 = new Grpc\Channel('localhost:10014',
471          ['credentials' => $channel_credentials]);
472      $this->channel2->getConnectivityState(true);
473      $this->waitUntilNotIdle($this->channel2);
474      $channel2_info = $this->channel2->getChannelInfo();
475      $this->assertConnecting($channel2_info['connectivity_status']);
476
477      $channel_credentials = Grpc\ChannelCredentials::createSsl("a", null,
478          null);
479      $this->channel3 = new Grpc\Channel('localhost:10014',
480          ['credentials' => $channel_credentials]);
481      $channel3_info = $this->channel3->getChannelInfo();
482
483      // Channel3 will not be persisted
484      $plist_info = $this->channel1->getPersistentList();
485      $this->assertEquals(2, count($plist_info));
486      $this->assertArrayHasKey($channel1_info['key'], $plist_info);
487      $this->assertArrayHasKey($channel2_info['key'], $plist_info);
488      $this->assertArrayNotHasKey($channel3_info['key'], $plist_info);
489  }
490}
491