From cc019af9cc2951bf7e0cdc17fb118e239dc785c3 Mon Sep 17 00:00:00 2001
From: Stanley Cheung <stanleycheung@google.com>
Date: Mon, 15 Jun 2015 11:45:00 -0700
Subject: [PATCH] add PHP timeout interop test

---
 src/php/lib/Grpc/AbstractCall.php        | 14 +++++++++--
 src/php/lib/Grpc/BaseStub.php            | 31 ++++++++++++++++++------
 src/php/tests/interop/interop_client.php | 21 ++++++++++++++++
 src/php/tests/unit_tests/TimevalTest.php | 22 +++++++++++++++++
 4 files changed, 78 insertions(+), 10 deletions(-)

diff --git a/src/php/lib/Grpc/AbstractCall.php b/src/php/lib/Grpc/AbstractCall.php
index 1add972589..5b28417a0d 100644
--- a/src/php/lib/Grpc/AbstractCall.php
+++ b/src/php/lib/Grpc/AbstractCall.php
@@ -43,9 +43,19 @@ abstract class AbstractCall {
    * Create a new Call wrapper object.
    * @param Channel $channel The channel to communicate on
    * @param string $method The method to call on the remote server
+   * @param callback $deserialize A callback function to deserialize
+   * the response
+   * @param (optional) long $timeout Timeout in microseconds
    */
-  public function __construct(Channel $channel, $method, $deserialize) {
-    $this->call = new Call($channel, $method, Timeval::infFuture());
+  public function __construct(Channel $channel, $method, $deserialize, $timeout = false) {
+    if ($timeout) {
+      $now = Timeval::now();
+      $delta = new Timeval($timeout);
+      $deadline = $now->add($delta);
+    } else {
+      $deadline = Timeval::infFuture();
+    }
+    $this->call = new Call($channel, $method, $deadline);
     $this->deserialize = $deserialize;
     $this->metadata = null;
   }
diff --git a/src/php/lib/Grpc/BaseStub.php b/src/php/lib/Grpc/BaseStub.php
index b84b6b86f8..48c00977eb 100755
--- a/src/php/lib/Grpc/BaseStub.php
+++ b/src/php/lib/Grpc/BaseStub.php
@@ -83,6 +83,21 @@ class BaseStub {
     return "https://" . $this->hostname . $service_name;
   }
 
+  /**
+   * extract $timeout from $metadata
+   * @param $metadata The metadata map
+   * @return list($metadata_copy, $timeout)
+   */
+  private function _extract_timeout_from_metadata($metadata) {
+    $timeout = false;
+    $metadata_copy = $metadata;
+    if (isset($metadata['timeout'])) {
+      $timeout = $metadata['timeout'];
+      unset($metadata_copy['timeout']);
+    }
+    return array($metadata_copy, $timeout);
+  }
+
   /* This class is intended to be subclassed by generated code, so all functions
      begin with "_" to avoid name collisions. */
 
@@ -99,8 +114,8 @@ class BaseStub {
                                  $argument,
                                  callable $deserialize,
                                  $metadata = array()) {
-    $call = new UnaryCall($this->channel, $method, $deserialize);
-    $actual_metadata = $metadata;
+    list($actual_metadata, $timeout)  = $this->_extract_timeout_from_metadata($metadata);
+    $call = new UnaryCall($this->channel, $method, $deserialize, $timeout);
     $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
     if (is_callable($this->update_metadata)) {
       $actual_metadata = call_user_func($this->update_metadata,
@@ -126,8 +141,8 @@ class BaseStub {
                                        $arguments,
                                        callable $deserialize,
                                        $metadata = array()) {
-    $call = new ClientStreamingCall($this->channel, $method, $deserialize);
-    $actual_metadata = $metadata;
+    list($actual_metadata, $timeout)  = $this->_extract_timeout_from_metadata($metadata);
+    $call = new ClientStreamingCall($this->channel, $method, $deserialize, $timeout);
     $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
     if (is_callable($this->update_metadata)) {
       $actual_metadata = call_user_func($this->update_metadata,
@@ -152,8 +167,8 @@ class BaseStub {
                                        $argument,
                                        callable $deserialize,
                                        $metadata = array()) {
-    $call = new ServerStreamingCall($this->channel, $method, $deserialize);
-    $actual_metadata = $metadata;
+    list($actual_metadata, $timeout)  = $this->_extract_timeout_from_metadata($metadata);
+    $call = new ServerStreamingCall($this->channel, $method, $deserialize, $timeout);
     $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
     if (is_callable($this->update_metadata)) {
       $actual_metadata = call_user_func($this->update_metadata,
@@ -175,8 +190,8 @@ class BaseStub {
   public function _bidiRequest($method,
                                callable $deserialize,
                                $metadata = array()) {
-    $call = new BidiStreamingCall($this->channel, $method, $deserialize);
-    $actual_metadata = $metadata;
+    list($actual_metadata, $timeout)  = $this->_extract_timeout_from_metadata($metadata);
+    $call = new BidiStreamingCall($this->channel, $method, $deserialize, $timeout);
     $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
     if (is_callable($this->update_metadata)) {
       $actual_metadata = call_user_func($this->update_metadata,
diff --git a/src/php/tests/interop/interop_client.php b/src/php/tests/interop/interop_client.php
index 9aee01cd4d..2041577557 100755
--- a/src/php/tests/interop/interop_client.php
+++ b/src/php/tests/interop/interop_client.php
@@ -270,6 +270,24 @@ function cancelAfterFirstResponse($stub) {
              'Call status was not CANCELLED');
 }
 
+function timeoutOnSleepingServer($stub) {
+  $call = $stub->FullDuplexCall(array('timeout' => 500000));
+  $request = new grpc\testing\StreamingOutputCallRequest();
+  $request->setResponseType(grpc\testing\PayloadType::COMPRESSABLE);
+  $response_parameters = new grpc\testing\ResponseParameters();
+  $response_parameters->setSize(8);
+  $request->addResponseParameters($response_parameters);
+  $payload = new grpc\testing\Payload();
+  $payload->setBody(str_repeat("\0", 9));
+  $request->setPayload($payload);
+
+  $call->write($request);
+  $response = $call->read();
+
+  hardAssert($call->getStatus()->code === Grpc\STATUS_DEADLINE_EXCEEDED,
+             'Call status was not DEADLINE_EXCEEDED');
+}
+
 $args = getopt('', array('server_host:', 'server_port:', 'test_case:',
                          'server_host_override:', 'oauth_scope:',
                          'default_service_account:'));
@@ -341,6 +359,9 @@ switch ($args['test_case']) {
   case 'cancel_after_first_response':
     cancelAfterFirstResponse($stub);
     break;
+  case 'timeout_on_sleeping_server':
+    timeoutOnSleepingServer($stub);
+    break;
   case 'service_account_creds':
     serviceAccountCreds($stub, $args);
     break;
diff --git a/src/php/tests/unit_tests/TimevalTest.php b/src/php/tests/unit_tests/TimevalTest.php
index a8bfcf0ac4..7b4925cad6 100755
--- a/src/php/tests/unit_tests/TimevalTest.php
+++ b/src/php/tests/unit_tests/TimevalTest.php
@@ -61,4 +61,26 @@ class TimevalTest extends PHPUnit_Framework_TestCase{
     $this->assertLessThan(0, Grpc\Timeval::compare($zero, $now));
     $this->assertLessThan(0, Grpc\Timeval::compare($now, $future));
   }
+
+  public function testNowAndAdd() {
+    $now = Grpc\Timeval::now();
+    $delta = new Grpc\Timeval(1000);
+    $deadline = $now->add($delta);
+    $this->assertGreaterThan(0, Grpc\Timeval::compare($deadline, $now));
+  }
+
+  public function testNowAndSubtract() {
+    $now = Grpc\Timeval::now();
+    $delta = new Grpc\Timeval(1000);
+    $deadline = $now->subtract($delta);
+    $this->assertLessThan(0, Grpc\Timeval::compare($deadline, $now));
+  }
+
+  public function testAddAndSubtract() {
+    $now = Grpc\Timeval::now();
+    $delta = new Grpc\Timeval(1000);
+    $deadline = $now->add($delta);
+    $back_to_now = $deadline->subtract($delta);
+    $this->assertSame(0, Grpc\Timeval::compare($back_to_now, $now));
+  }
 }
-- 
GitLab