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()
26  {
27  }
28
29  public function tearDown()
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
184      // channel2 can still be use
185      $state = $this->channel2->getConnectivityState();
186      $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
187
188      $call1 = new Grpc\Call($this->channel1,
189          '/foo',
190          Grpc\Timeval::infFuture());
191      $call2 = new Grpc\Call($this->channel2,
192          '/foo',
193          Grpc\Timeval::infFuture());
194      $call3 = new Grpc\Call($this->channel1,
195          '/foo',
196          Grpc\Timeval::infFuture());
197      $call4 = new Grpc\Call($this->channel2,
198          '/foo',
199          Grpc\Timeval::infFuture());
200      $batch = [
201          Grpc\OP_SEND_INITIAL_METADATA => [],
202      ];
203
204      $result = $call1->startBatch($batch);
205      $this->assertTrue($result->send_metadata);
206      $result = $call2->startBatch($batch);
207      $this->assertTrue($result->send_metadata);
208
209      $this->channel1->close();
210      // After closing channel1, channel2 can still be use
211      $result = $call4->startBatch($batch);
212      $this->assertTrue($result->send_metadata);
213      // channel 1 is closed, it will throw an exception.
214      $result = $call3->startBatch($batch);
215  }
216
217  public function testPersistentChannelTargetDefaultUpperBound()
218  {
219      $this->channel1 = new Grpc\Channel('localhost:10002', []);
220      $channel1_info = $this->channel1->getChannelInfo();
221      $this->assertEquals($channel1_info['target_upper_bound'], 1);
222      $this->assertEquals($channel1_info['target_current_size'], 1);
223  }
224
225  public function testPersistentChannelTargetUpperBoundZero()
226  {
227      $this->channel1 = new Grpc\Channel('localhost:10002', [
228          "grpc_target_persist_bound" => 0,
229      ]);
230      // channel1 will not be persisted.
231      $channel1_info = $this->channel1->getChannelInfo();
232      $this->assertEquals($channel1_info['target_upper_bound'], 0);
233      $this->assertEquals($channel1_info['target_current_size'], 0);
234      $plist_info = $this->channel1->getPersistentList();
235      $this->assertEquals(0, count($plist_info));
236  }
237
238  public function testPersistentChannelTargetUpperBoundNotZero()
239  {
240      $this->channel1 = new Grpc\Channel('localhost:10003', [
241          "grpc_target_persist_bound" => 3,
242      ]);
243      $channel1_info = $this->channel1->getChannelInfo();
244      $this->assertEquals($channel1_info['target_upper_bound'], 3);
245      $this->assertEquals($channel1_info['target_current_size'], 1);
246
247      // The upper bound should not be changed
248      $this->channel2 = new Grpc\Channel('localhost:10003', []);
249      $channel2_info = $this->channel2->getChannelInfo();
250      $this->assertEquals($channel2_info['target_upper_bound'], 3);
251      $this->assertEquals($channel2_info['target_current_size'], 1);
252
253      // The upper bound should not be changed
254      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
255          null);
256      $this->channel3 = new Grpc\Channel('localhost:10003',
257          ['credentials' => $channel_credentials]);
258      $channel3_info = $this->channel3->getChannelInfo();
259      $this->assertEquals($channel3_info['target_upper_bound'], 3);
260      $this->assertEquals($channel3_info['target_current_size'], 2);
261
262      // The upper bound should not be changed
263      $this->channel4 = new Grpc\Channel('localhost:10003', [
264          "grpc_target_persist_bound" => 5,
265      ]);
266      $channel4_info = $this->channel4->getChannelInfo();
267      $this->assertEquals($channel4_info['target_upper_bound'], 5);
268      $this->assertEquals($channel4_info['target_current_size'], 2);
269  }
270
271  public function testPersistentChannelDefaultOutBound1()
272  {
273      $this->channel1 = new Grpc\Channel('localhost:10004', []);
274      // Make channel1 not IDLE.
275      $this->channel1->getConnectivityState(true);
276      $this->waitUntilNotIdle($this->channel1);
277      $channel1_info = $this->channel1->getChannelInfo();
278      $this->assertConnecting($channel1_info['connectivity_status']);
279
280      // Since channel1 is CONNECTING, channel 2 will not be persisted
281      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
282        null);
283      $this->channel2 = new Grpc\Channel('localhost:10004',
284          ['credentials' => $channel_credentials]);
285      $channel2_info = $this->channel2->getChannelInfo();
286      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel2_info['connectivity_status']);
287
288      // By default, target 'localhost:10011' only persist one channel.
289      // Since channel1 is not Idle channel2 will not be persisted.
290      $plist_info = $this->channel1->getPersistentList();
291      $this->assertEquals(1, count($plist_info));
292      $this->assertArrayHasKey($channel1_info['key'], $plist_info);
293      $this->assertArrayNotHasKey($channel2_info['key'], $plist_info);
294  }
295
296  public function testPersistentChannelDefaultOutBound2()
297  {
298      $this->channel1 = new Grpc\Channel('localhost:10005', []);
299      $channel1_info = $this->channel1->getChannelInfo();
300      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel1_info['connectivity_status']);
301
302      // Although channel1 is IDLE, channel1 still has reference to the underline
303      // gRPC channel. channel2 will not be persisted
304      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
305        null);
306      $this->channel2 = new Grpc\Channel('localhost:10005',
307          ['credentials' => $channel_credentials]);
308      $channel2_info = $this->channel2->getChannelInfo();
309      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel2_info['connectivity_status']);
310
311      // By default, target 'localhost:10011' only persist one channel.
312      // Since channel1 Idle, channel2 will be persisted.
313      $plist_info = $this->channel1->getPersistentList();
314      $this->assertEquals(1, count($plist_info));
315      $this->assertArrayHasKey($channel1_info['key'], $plist_info);
316      $this->assertArrayNotHasKey($channel2_info['key'], $plist_info);
317  }
318
319  public function testPersistentChannelDefaultOutBound3()
320  {
321      $this->channel1 = new Grpc\Channel('localhost:10006', []);
322      $channel1_info = $this->channel1->getChannelInfo();
323      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel1_info['connectivity_status']);
324
325      $this->channel1->close();
326      // channel1 is closed, no reference holds to the underline channel.
327      // channel2 can be persisted.
328      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
329        null);
330      $this->channel2 = new Grpc\Channel('localhost:10006',
331        ['credentials' => $channel_credentials]);
332      $channel2_info = $this->channel2->getChannelInfo();
333      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel2_info['connectivity_status']);
334
335      // By default, target 'localhost:10011' only persist one channel.
336      // Since channel1 Idle, channel2 will be persisted.
337      $plist_info = $this->channel2->getPersistentList();
338      $this->assertEquals(1, count($plist_info));
339      $this->assertArrayHasKey($channel2_info['key'], $plist_info);
340      $this->assertArrayNotHasKey($channel1_info['key'], $plist_info);
341  }
342
343  public function testPersistentChannelTwoUpperBound()
344  {
345      $this->channel1 = new Grpc\Channel('localhost:10007', [
346          "grpc_target_persist_bound" => 2,
347      ]);
348      $channel1_info = $this->channel1->getChannelInfo();
349      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel1_info['connectivity_status']);
350
351      // Since channel1 is IDLE, channel 1 will be deleted
352      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
353          null);
354      $this->channel2 = new Grpc\Channel('localhost:10007',
355          ['credentials' => $channel_credentials]);
356      $channel2_info = $this->channel2->getChannelInfo();
357      $this->assertEquals(GRPC\CHANNEL_IDLE, $channel2_info['connectivity_status']);
358
359      $plist_info = $this->channel1->getPersistentList();
360      $this->assertEquals(2, count($plist_info));
361      $this->assertArrayHasKey($channel1_info['key'], $plist_info);
362      $this->assertArrayHasKey($channel2_info['key'], $plist_info);
363  }
364
365  public function testPersistentChannelTwoUpperBoundOutBound1()
366  {
367      $this->channel1 = new Grpc\Channel('localhost:10011', [
368          "grpc_target_persist_bound" => 2,
369      ]);
370      $channel1_info = $this->channel1->getChannelInfo();
371
372      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
373        null);
374      $this->channel2 = new Grpc\Channel('localhost:10011',
375          ['credentials' => $channel_credentials]);
376      $channel2_info = $this->channel2->getChannelInfo();
377
378      // Close channel1, so that new channel can be persisted.
379      $this->channel1->close();
380
381      $channel_credentials = Grpc\ChannelCredentials::createSsl("a", null,
382        null);
383      $this->channel3 = new Grpc\Channel('localhost:10011',
384          ['credentials' => $channel_credentials]);
385      $channel3_info = $this->channel3->getChannelInfo();
386
387      $plist_info = $this->channel1->getPersistentList();
388      $this->assertEquals(2, count($plist_info));
389      $this->assertArrayNotHasKey($channel1_info['key'], $plist_info);
390      $this->assertArrayHasKey($channel2_info['key'], $plist_info);
391      $this->assertArrayHasKey($channel3_info['key'], $plist_info);
392  }
393
394  public function testPersistentChannelTwoUpperBoundOutBound2()
395  {
396      $this->channel1 = new Grpc\Channel('localhost:10012', [
397          "grpc_target_persist_bound" => 2,
398      ]);
399      $channel1_info = $this->channel1->getChannelInfo();
400
401      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
402        null);
403      $this->channel2 = new Grpc\Channel('localhost:10012',
404        ['credentials' => $channel_credentials]);
405      $channel2_info = $this->channel2->getChannelInfo();
406
407      // Close channel2, so that new channel can be persisted.
408      $this->channel2->close();
409
410      $channel_credentials = Grpc\ChannelCredentials::createSsl("a", null,
411        null);
412      $this->channel3 = new Grpc\Channel('localhost:10012',
413        ['credentials' => $channel_credentials]);
414      $channel3_info = $this->channel3->getChannelInfo();
415
416      $plist_info = $this->channel1->getPersistentList();
417      $this->assertEquals(2, count($plist_info));
418      $this->assertArrayHasKey($channel1_info['key'], $plist_info);
419      $this->assertArrayNotHasKey($channel2_info['key'], $plist_info);
420      $this->assertArrayHasKey($channel3_info['key'], $plist_info);
421  }
422
423  public function testPersistentChannelTwoUpperBoundOutBound3()
424  {
425      $this->channel1 = new Grpc\Channel('localhost:10013', [
426          "grpc_target_persist_bound" => 2,
427      ]);
428      $channel1_info = $this->channel1->getChannelInfo();
429
430      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
431          null);
432      $this->channel2 = new Grpc\Channel('localhost:10013',
433          ['credentials' => $channel_credentials]);
434      $this->channel2->getConnectivityState(true);
435      $this->waitUntilNotIdle($this->channel2);
436      $channel2_info = $this->channel2->getChannelInfo();
437      $this->assertConnecting($channel2_info['connectivity_status']);
438
439      // Only one channel will be deleted
440      $this->channel1->close();
441      $this->channel2->close();
442
443      $channel_credentials = Grpc\ChannelCredentials::createSsl("a", null,
444        null);
445      $this->channel3 = new Grpc\Channel('localhost:10013',
446          ['credentials' => $channel_credentials]);
447      $channel3_info = $this->channel3->getChannelInfo();
448
449      // Only the Idle Channel will be deleted
450      $plist_info = $this->channel1->getPersistentList();
451      $this->assertEquals(2, count($plist_info));
452      $this->assertArrayNotHasKey($channel1_info['key'], $plist_info);
453      $this->assertArrayHasKey($channel2_info['key'], $plist_info);
454      $this->assertArrayHasKey($channel3_info['key'], $plist_info);
455  }
456
457  public function testPersistentChannelTwoUpperBoundOutBound4()
458  {
459      $this->channel1 = new Grpc\Channel('localhost:10014', [
460          "grpc_target_persist_bound" => 2,
461      ]);
462      $this->channel1->getConnectivityState(true);
463      $this->waitUntilNotIdle($this->channel1);
464      $channel1_info = $this->channel1->getChannelInfo();
465      $this->assertConnecting($channel1_info['connectivity_status']);
466
467      $channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
468          null);
469      $this->channel2 = new Grpc\Channel('localhost:10014',
470          ['credentials' => $channel_credentials]);
471      $this->channel2->getConnectivityState(true);
472      $this->waitUntilNotIdle($this->channel2);
473      $channel2_info = $this->channel2->getChannelInfo();
474      $this->assertConnecting($channel2_info['connectivity_status']);
475
476      $channel_credentials = Grpc\ChannelCredentials::createSsl("a", null,
477          null);
478      $this->channel3 = new Grpc\Channel('localhost:10014',
479          ['credentials' => $channel_credentials]);
480      $channel3_info = $this->channel3->getChannelInfo();
481
482      // Channel3 will not be persisted
483      $plist_info = $this->channel1->getPersistentList();
484      $this->assertEquals(2, count($plist_info));
485      $this->assertArrayHasKey($channel1_info['key'], $plist_info);
486      $this->assertArrayHasKey($channel2_info['key'], $plist_info);
487      $this->assertArrayNotHasKey($channel3_info['key'], $plist_info);
488  }
489}
490