add PHP timeout interop test
diff --git a/src/php/lib/Grpc/AbstractCall.php b/src/php/lib/Grpc/AbstractCall.php
index 1add972..5b28417 100644
--- a/src/php/lib/Grpc/AbstractCall.php
+++ b/src/php/lib/Grpc/AbstractCall.php
@@ -43,9 +43,19 @@
    * 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 b84b6b8..48c0097 100755
--- a/src/php/lib/Grpc/BaseStub.php
+++ b/src/php/lib/Grpc/BaseStub.php
@@ -83,6 +83,21 @@
     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 @@
                                  $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 @@
                                        $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 @@
                                        $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 @@
   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 9aee01c..2041577 100755
--- a/src/php/tests/interop/interop_client.php
+++ b/src/php/tests/interop/interop_client.php
@@ -270,6 +270,24 @@
              '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 @@
   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 a8bfcf0..7b4925c 100755
--- a/src/php/tests/unit_tests/TimevalTest.php
+++ b/src/php/tests/unit_tests/TimevalTest.php
@@ -61,4 +61,26 @@
     $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));
+  }
 }