{"action":"created","comment":{"url":"https://api.github.com/repos/googleapis/gax-java/pulls/comments/165380480","pull_request_review_id":93309362,"id":165380480,"diff_hunk":"@@ -0,0 +1,254 @@\n+package com.google.api.gax.rpc;\n+\n+import com.google.api.core.InternalApi;\n+import com.google.api.gax.retrying.NonCancellableFuture;\n+import com.google.api.gax.retrying.RetryingFuture;\n+import com.google.api.gax.retrying.StreamResumptionStrategy;\n+import com.google.common.base.Preconditions;\n+import java.util.concurrent.Callable;\n+import java.util.concurrent.CancellationException;\n+import javax.annotation.concurrent.GuardedBy;\n+import org.threeten.bp.Duration;\n+\n+@InternalApi\n+public class ServerStreamingAttemptCallable<RequestT, ResponseT> implements Callable<Void> {\n+  private final Object lock = new Object();\n+  private final ServerStreamingCallable<RequestT, ResponseT> innerCallable;\n+\n+  private final StreamResumptionStrategy<RequestT, ResponseT> resumptionStrategy;\n+  private final RequestT initialRequest;\n+  private ApiCallContext context;\n+\n+  private final ResponseObserver<ResponseT> outerObserver;\n+\n+  // Start state\n+  private boolean autoFlowControl = true;\n+  private boolean isStarted;\n+\n+  // Outer state\n+  @GuardedBy(\"lock\")\n+  private Throwable cancellationCause;\n+\n+  @GuardedBy(\"lock\")\n+  private int pendingRequests;\n+\n+  RetryingFuture<Void> externalFuture;\n+\n+  // Internal retry state\n+  @GuardedBy(\"lock\")\n+  private StreamController innerController;\n+  private boolean seenSuccessSinceLastError;\n+\n+  public ServerStreamingAttemptCallable(\n+      ServerStreamingCallable<RequestT, ResponseT> innerCallable,\n+      StreamResumptionStrategy<RequestT, ResponseT> resumptionStrategy,\n+      RequestT initialRequest,\n+      ApiCallContext context,\n+      ResponseObserver<ResponseT> outerObserver) {\n+    this.innerCallable = innerCallable;\n+    this.resumptionStrategy = resumptionStrategy;\n+    this.initialRequest = initialRequest;\n+    this.context = context;\n+    this.outerObserver = outerObserver;\n+  }\n+\n+  public void setExternalFuture(RetryingFuture<Void> externalFuture) {\n+    this.externalFuture = externalFuture;\n+  }\n+\n+  /**\n+   * Starts the initial call. The call is attempted on the caller's thread. Further call attempts\n+   * will be made by the executor.\n+   */\n+  public void start() {\n+    Preconditions.checkState(!isStarted, \"Already started\");\n+\n+    // Initialize the outer observer\n+    outerObserver.onStart(\n+        new StreamController() {\n+          @Override\n+          public void disableAutoInboundFlowControl() {\n+            Preconditions.checkState(\n+                !isStarted, \"Can't disable auto flow control once the stream is started\");\n+            autoFlowControl = false;\n+          }\n+\n+          @Override\n+          public void request(int count) {\n+            onRequest(count);\n+          }\n+\n+          @Override\n+          public void cancel() {\n+            onCancel();\n+          }\n+        });\n+    isStarted = true;\n+    if (autoFlowControl) {\n+      pendingRequests = Integer.MAX_VALUE;\n+    }\n+\n+    // Propagate the totalTimeout as the overall stream deadline.\n+    Duration totalTimeout = externalFuture.getAttemptSettings().getGlobalSettings().getTotalTimeout();\n+    if (totalTimeout != null) {\n+      context = context.withTimeout(totalTimeout);\n+    }\n+\n+    // Call the inner callable\n+    call();\n+  }\n+\n+  @Override\n+  public Void call() {\n+    Preconditions.checkState(isStarted, \"Must be started first\");\n+\n+    RequestT request = resumptionStrategy.getResumeRequest(initialRequest);\n+    Preconditions.checkState(request != null, \"ResumptionStrategy returned a null request.\");\n+\n+    final NonCancellableFuture<Void> attemptFuture = new NonCancellableFuture<>();\n+    seenSuccessSinceLastError = false;\n+\n+    // TODO: watchdog\n+    innerCallable.call(\n+        request,\n+        new StateCheckingResponseObserver<ResponseT>() {\n+          @Override\n+          public void onStartImpl(StreamController controller) {\n+            onAttemptStart(controller);\n+          }\n+\n+          @Override\n+          public void onResponseImpl(ResponseT response) {\n+            seenSuccessSinceLastError = true;\n+            resumptionStrategy.onProgress(response);\n+            outerObserver.onResponse(response);\n+          }\n+\n+          @Override\n+          public void onErrorImpl(Throwable t) {\n+            if (cancellationCause != null) {\n+              attemptFuture.setExceptionPrivately(cancellationCause);\n+            } else {\n+              attemptFuture.setExceptionPrivately(\n+                  new WrappedApiException(resumptionStrategy.canResume(), seenSuccessSinceLastError, t)\n+              );\n+            }\n+          }\n+\n+          @Override\n+          public void onCompleteImpl() {\n+            attemptFuture.setPrivately(null);\n+          }\n+        },\n+        context);\n+\n+    externalFuture.setAttemptFuture(attemptFuture);\n+\n+    return null;\n+  }\n+\n+  /**\n+   * Called by the inner {@link ServerStreamingCallable} when the call is about to start. This will\n+   * transfer unfinished state from the previous attempt.\n+   *\n+   * @see ResponseObserver#onStart(StreamController)\n+   */\n+  private void onAttemptStart(StreamController controller) {\n+    if (!autoFlowControl) {\n+      controller.disableAutoInboundFlowControl();\n+    }\n+\n+    Throwable localCancellationCause;\n+    int numToRequest = 0;\n+\n+    synchronized (lock) {\n+      innerController = controller;\n+\n+      localCancellationCause = this.cancellationCause;\n+\n+      if (!autoFlowControl) {\n+        numToRequest = pendingRequests;\n+      }\n+    }\n+\n+    if (localCancellationCause != null) {\n+      controller.cancel();\n+    } else if (numToRequest > 0) {\n+      controller.request(numToRequest);\n+    }\n+  }\n+\n+  /**\n+   * Called when the outer {@link ResponseObserver} is ready for more data.\n+   *\n+   * @see StreamController#request(int)\n+   */\n+  private void onRequest(int count) {\n+    Preconditions.checkState(!autoFlowControl, \"Automatic flow control is enabled\");\n+    Preconditions.checkArgument(count > 0, \"Count must be > 0\");\n+\n+    final StreamController localInnerController;\n+\n+    synchronized (lock) {\n+      int maxInc = Integer.MAX_VALUE - pendingRequests;\n+      count = Math.min(maxInc, count);\n+\n+      pendingRequests += count;\n+      localInnerController = this.innerController;\n+    }\n+\n+    // Note: there is a race condition here where the count might go to the previous attempt's\n+    // StreamController after it failed. But it doesn't matter, because the controller will just\n+    // ignore it and the current controller will pick it up onStart.\n+    if (localInnerController != null) {\n+      localInnerController.request(count);\n+    }\n+  }\n+\n+  /**\n+   * Called when the outer {@link ResponseObserver} wants to prematurely cancel the stream.\n+   *\n+   * @see StreamController#cancel()\n+   */\n+  private void onCancel() {\n+    StreamController localInnerController;\n+\n+    synchronized (lock) {\n+      if (cancellationCause != null) {\n+        return;\n+      }\n+      cancellationCause = new WrappedCancellationException(\"User cancelled stream\");\n+      localInnerController = innerController;\n+    }\n+\n+    if (localInnerController != null) {\n+      localInnerController.cancel();\n+    }\n+  }\n+\n+  public static class WrappedCancellationException extends RuntimeException {","path":"gax/src/main/java/com/google/api/gax/rpc/ServerStreamingAttemptCallable.java","position":229,"original_position":229,"commit_id":"95e0d05757680052f9c34293ed277b10da781f7c","original_commit_id":"95e0d05757680052f9c34293ed277b10da781f7c","user":{"login":"garrettjonesgoogle","id":13341017,"avatar_url":"https://avatars2.githubusercontent.com/u/13341017?v=4","gravatar_id":"","url":"https://api.github.com/users/garrettjonesgoogle","html_url":"https://github.com/garrettjonesgoogle","followers_url":"https://api.github.com/users/garrettjonesgoogle/followers","following_url":"https://api.github.com/users/garrettjonesgoogle/following{/other_user}","gists_url":"https://api.github.com/users/garrettjonesgoogle/gists{/gist_id}","starred_url":"https://api.github.com/users/garrettjonesgoogle/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/garrettjonesgoogle/subscriptions","organizations_url":"https://api.github.com/users/garrettjonesgoogle/orgs","repos_url":"https://api.github.com/users/garrettjonesgoogle/repos","events_url":"https://api.github.com/users/garrettjonesgoogle/events{/privacy}","received_events_url":"https://api.github.com/users/garrettjonesgoogle/received_events","type":"User","site_admin":false},"body":"I think \"StreamingAttemptCancellationException\" or something like that would be better","created_at":"2018-02-01T14:58:32Z","updated_at":"2018-02-01T14:58:46Z","html_url":"https://github.com/googleapis/gax-java/pull/463#discussion_r165380480","pull_request_url":"https://api.github.com/repos/googleapis/gax-java/pulls/463","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/googleapis/gax-java/pulls/comments/165380480"},"html":{"href":"https://github.com/googleapis/gax-java/pull/463#discussion_r165380480"},"pull_request":{"href":"https://api.github.com/repos/googleapis/gax-java/pulls/463"}}},"pull_request":{"url":"https://api.github.com/repos/googleapis/gax-java/pulls/463","id":166125465,"html_url":"https://github.com/googleapis/gax-java/pull/463","diff_url":"https://github.com/googleapis/gax-java/pull/463.diff","patch_url":"https://github.com/googleapis/gax-java/pull/463.patch","issue_url":"https://api.github.com/repos/googleapis/gax-java/issues/463","number":463,"state":"open","locked":false,"title":"wip: new server streaming retry approach","user":{"login":"igorbernstein2","id":23526194,"avatar_url":"https://avatars3.githubusercontent.com/u/23526194?v=4","gravatar_id":"","url":"https://api.github.com/users/igorbernstein2","html_url":"https://github.com/igorbernstein2","followers_url":"https://api.github.com/users/igorbernstein2/followers","following_url":"https://api.github.com/users/igorbernstein2/following{/other_user}","gists_url":"https://api.github.com/users/igorbernstein2/gists{/gist_id}","starred_url":"https://api.github.com/users/igorbernstein2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/igorbernstein2/subscriptions","organizations_url":"https://api.github.com/users/igorbernstein2/orgs","repos_url":"https://api.github.com/users/igorbernstein2/repos","events_url":"https://api.github.com/users/igorbernstein2/events{/privacy}","received_events_url":"https://api.github.com/users/igorbernstein2/received_events","type":"User","site_admin":false},"body":"DON'T MERGE\r\n\r\nThis is merely a proof of concept of a different approach for #449. It tries to build on top of `CallbackChainRetryingFuture`.  I've temporarily stripped out the watchdog and the tests so that we can discuss this approach. Please note that this not written to be production code, it's only meant to guide deiscussion\r\n\r\n## RetryingServerStreamingCallable\r\nThis is the entrypoint, it tries to mirror `RetryingCallable`. It's responsible for wiring all of the machinery together. The main difference to the existing `RetryingCallable` is that it needs to bridge the result of the future back to the response observer. The exception unwrapping bit is discussed later.\r\n\r\n## ServerStreamingAttemptCallable\r\n`ServerStreamingAttemptCallable` is the thing that will be invoked for every rpc. It tries to mirror `AttemptCallable`. However, it is substantially more complicated due to the fact that it needs to track the state of the stream:\r\n- Number of responses that the caller requested. This number must be carried over to the next attempt.\r\n- Delegate to `StreamResumptionStrategy` to compute the next retry request. \r\n- Has the current attempt seen any responses. This will be bubbled up to the `StreamingRetryAlgorithm` to decide if the `TimedAttemptSettings` need to be reset.\r\n\r\nAn unfortunate wart on this class is that it needs to collaborate with class that it doesn't have direct references to. In particular it needs to notify the `StreamingRetryAlgorithm` that successful messages have received, so that `TimedAttemptSettings` can be reset. Also it should be able to transport the original stacktrace of the cancellation cause, this will make it easier for caller to track down why an rpc was cancelled. This class resorts to exception wrapping as a communication channel. It uses `WrappedApiException` to deliver information to the `StreamingRetryAlgorithm`. And it uses `WrappedCancellationException` to transport the original cancellation cause to the `RetryingServerStreamingCallable`.\r\n\r\n## StreamResumptionStrategy\r\nPlays the same role as in #449. It's responsible for tracking progress of the stream and computing the next retry request. The only addition here is the `canResume()` method, which must be called preemptively by `ServerStreamingAttemptCallable` to communicate retry ability to the `StreamingRetryAlgorithm`\r\n\r\n## StreamingRetryAlgorithm\r\nMainly used to unwrap extra information exception  from `ServerStreamingAttemptCallable`\r\n\r\n\r\n","created_at":"2018-01-30T23:20:24Z","updated_at":"2018-02-01T14:58:46Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2aaab6e678f0efce1d1cc15d337edd112701dac0","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"milestone":null,"commits_url":"https://api.github.com/repos/googleapis/gax-java/pulls/463/commits","review_comments_url":"https://api.github.com/repos/googleapis/gax-java/pulls/463/comments","review_comment_url":"https://api.github.com/repos/googleapis/gax-java/pulls/comments{/number}","comments_url":"https://api.github.com/repos/googleapis/gax-java/issues/463/comments","statuses_url":"https://api.github.com/repos/googleapis/gax-java/statuses/95e0d05757680052f9c34293ed277b10da781f7c","head":{"label":"igorbernstein2:ssc-7b-retries-2","ref":"ssc-7b-retries-2","sha":"95e0d05757680052f9c34293ed277b10da781f7c","user":{"login":"igorbernstein2","id":23526194,"avatar_url":"https://avatars3.githubusercontent.com/u/23526194?v=4","gravatar_id":"","url":"https://api.github.com/users/igorbernstein2","html_url":"https://github.com/igorbernstein2","followers_url":"https://api.github.com/users/igorbernstein2/followers","following_url":"https://api.github.com/users/igorbernstein2/following{/other_user}","gists_url":"https://api.github.com/users/igorbernstein2/gists{/gist_id}","starred_url":"https://api.github.com/users/igorbernstein2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/igorbernstein2/subscriptions","organizations_url":"https://api.github.com/users/igorbernstein2/orgs","repos_url":"https://api.github.com/users/igorbernstein2/repos","events_url":"https://api.github.com/users/igorbernstein2/events{/privacy}","received_events_url":"https://api.github.com/users/igorbernstein2/received_events","type":"User","site_admin":false},"repo":{"id":109061626,"name":"gax-java","full_name":"igorbernstein2/gax-java","owner":{"login":"igorbernstein2","id":23526194,"avatar_url":"https://avatars3.githubusercontent.com/u/23526194?v=4","gravatar_id":"","url":"https://api.github.com/users/igorbernstein2","html_url":"https://github.com/igorbernstein2","followers_url":"https://api.github.com/users/igorbernstein2/followers","following_url":"https://api.github.com/users/igorbernstein2/following{/other_user}","gists_url":"https://api.github.com/users/igorbernstein2/gists{/gist_id}","starred_url":"https://api.github.com/users/igorbernstein2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/igorbernstein2/subscriptions","organizations_url":"https://api.github.com/users/igorbernstein2/orgs","repos_url":"https://api.github.com/users/igorbernstein2/repos","events_url":"https://api.github.com/users/igorbernstein2/events{/privacy}","received_events_url":"https://api.github.com/users/igorbernstein2/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/igorbernstein2/gax-java","description":"Google API Extensions for Java","fork":true,"url":"https://api.github.com/repos/igorbernstein2/gax-java","forks_url":"https://api.github.com/repos/igorbernstein2/gax-java/forks","keys_url":"https://api.github.com/repos/igorbernstein2/gax-java/keys{/key_id}","collaborators_url":"https://api.github.com/repos/igorbernstein2/gax-java/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/igorbernstein2/gax-java/teams","hooks_url":"https://api.github.com/repos/igorbernstein2/gax-java/hooks","issue_events_url":"https://api.github.com/repos/igorbernstein2/gax-java/issues/events{/number}","events_url":"https://api.github.com/repos/igorbernstein2/gax-java/events","assignees_url":"https://api.github.com/repos/igorbernstein2/gax-java/assignees{/user}","branches_url":"https://api.github.com/repos/igorbernstein2/gax-java/branches{/branch}","tags_url":"https://api.github.com/repos/igorbernstein2/gax-java/tags","blobs_url":"https://api.github.com/repos/igorbernstein2/gax-java/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/igorbernstein2/gax-java/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/igorbernstein2/gax-java/git/refs{/sha}","trees_url":"https://api.github.com/repos/igorbernstein2/gax-java/git/trees{/sha}","statuses_url":"https://api.github.com/repos/igorbernstein2/gax-java/statuses/{sha}","languages_url":"https://api.github.com/repos/igorbernstein2/gax-java/languages","stargazers_url":"https://api.github.com/repos/igorbernstein2/gax-java/stargazers","contributors_url":"https://api.github.com/repos/igorbernstein2/gax-java/contributors","subscribers_url":"https://api.github.com/repos/igorbernstein2/gax-java/subscribers","subscription_url":"https://api.github.com/repos/igorbernstein2/gax-java/subscription","commits_url":"https://api.github.com/repos/igorbernstein2/gax-java/commits{/sha}","git_commits_url":"https://api.github.com/repos/igorbernstein2/gax-java/git/commits{/sha}","comments_url":"https://api.github.com/repos/igorbernstein2/gax-java/comments{/number}","issue_comment_url":"https://api.github.com/repos/igorbernstein2/gax-java/issues/comments{/number}","contents_url":"https://api.github.com/repos/igorbernstein2/gax-java/contents/{+path}","compare_url":"https://api.github.com/repos/igorbernstein2/gax-java/compare/{base}...{head}","merges_url":"https://api.github.com/repos/igorbernstein2/gax-java/merges","archive_url":"https://api.github.com/repos/igorbernstein2/gax-java/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/igorbernstein2/gax-java/downloads","issues_url":"https://api.github.com/repos/igorbernstein2/gax-java/issues{/number}","pulls_url":"https://api.github.com/repos/igorbernstein2/gax-java/pulls{/number}","milestones_url":"https://api.github.com/repos/igorbernstein2/gax-java/milestones{/number}","notifications_url":"https://api.github.com/repos/igorbernstein2/gax-java/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/igorbernstein2/gax-java/labels{/name}","releases_url":"https://api.github.com/repos/igorbernstein2/gax-java/releases{/id}","deployments_url":"https://api.github.com/repos/igorbernstein2/gax-java/deployments","created_at":"2017-10-31T23:07:12Z","updated_at":"2017-10-31T23:07:15Z","pushed_at":"2018-01-30T23:20:18Z","git_url":"git://github.com/igorbernstein2/gax-java.git","ssh_url":"git@github.com:igorbernstein2/gax-java.git","clone_url":"https://github.com/igorbernstein2/gax-java.git","svn_url":"https://github.com/igorbernstein2/gax-java","homepage":"","size":7966,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"googleapis:master","ref":"master","sha":"e5ef54da0bdbd5313817a40974f775b3494be4ee","user":{"login":"googleapis","id":16785467,"avatar_url":"https://avatars3.githubusercontent.com/u/16785467?v=4","gravatar_id":"","url":"https://api.github.com/users/googleapis","html_url":"https://github.com/googleapis","followers_url":"https://api.github.com/users/googleapis/followers","following_url":"https://api.github.com/users/googleapis/following{/other_user}","gists_url":"https://api.github.com/users/googleapis/gists{/gist_id}","starred_url":"https://api.github.com/users/googleapis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/googleapis/subscriptions","organizations_url":"https://api.github.com/users/googleapis/orgs","repos_url":"https://api.github.com/users/googleapis/repos","events_url":"https://api.github.com/users/googleapis/events{/privacy}","received_events_url":"https://api.github.com/users/googleapis/received_events","type":"Organization","site_admin":false},"repo":{"id":50872805,"name":"gax-java","full_name":"googleapis/gax-java","owner":{"login":"googleapis","id":16785467,"avatar_url":"https://avatars3.githubusercontent.com/u/16785467?v=4","gravatar_id":"","url":"https://api.github.com/users/googleapis","html_url":"https://github.com/googleapis","followers_url":"https://api.github.com/users/googleapis/followers","following_url":"https://api.github.com/users/googleapis/following{/other_user}","gists_url":"https://api.github.com/users/googleapis/gists{/gist_id}","starred_url":"https://api.github.com/users/googleapis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/googleapis/subscriptions","organizations_url":"https://api.github.com/users/googleapis/orgs","repos_url":"https://api.github.com/users/googleapis/repos","events_url":"https://api.github.com/users/googleapis/events{/privacy}","received_events_url":"https://api.github.com/users/googleapis/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/googleapis/gax-java","description":"Google API Extensions for Java","fork":false,"url":"https://api.github.com/repos/googleapis/gax-java","forks_url":"https://api.github.com/repos/googleapis/gax-java/forks","keys_url":"https://api.github.com/repos/googleapis/gax-java/keys{/key_id}","collaborators_url":"https://api.github.com/repos/googleapis/gax-java/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/googleapis/gax-java/teams","hooks_url":"https://api.github.com/repos/googleapis/gax-java/hooks","issue_events_url":"https://api.github.com/repos/googleapis/gax-java/issues/events{/number}","events_url":"https://api.github.com/repos/googleapis/gax-java/events","assignees_url":"https://api.github.com/repos/googleapis/gax-java/assignees{/user}","branches_url":"https://api.github.com/repos/googleapis/gax-java/branches{/branch}","tags_url":"https://api.github.com/repos/googleapis/gax-java/tags","blobs_url":"https://api.github.com/repos/googleapis/gax-java/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/googleapis/gax-java/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/googleapis/gax-java/git/refs{/sha}","trees_url":"https://api.github.com/repos/googleapis/gax-java/git/trees{/sha}","statuses_url":"https://api.github.com/repos/googleapis/gax-java/statuses/{sha}","languages_url":"https://api.github.com/repos/googleapis/gax-java/languages","stargazers_url":"https://api.github.com/repos/googleapis/gax-java/stargazers","contributors_url":"https://api.github.com/repos/googleapis/gax-java/contributors","subscribers_url":"https://api.github.com/repos/googleapis/gax-java/subscribers","subscription_url":"https://api.github.com/repos/googleapis/gax-java/subscription","commits_url":"https://api.github.com/repos/googleapis/gax-java/commits{/sha}","git_commits_url":"https://api.github.com/repos/googleapis/gax-java/git/commits{/sha}","comments_url":"https://api.github.com/repos/googleapis/gax-java/comments{/number}","issue_comment_url":"https://api.github.com/repos/googleapis/gax-java/issues/comments{/number}","contents_url":"https://api.github.com/repos/googleapis/gax-java/contents/{+path}","compare_url":"https://api.github.com/repos/googleapis/gax-java/compare/{base}...{head}","merges_url":"https://api.github.com/repos/googleapis/gax-java/merges","archive_url":"https://api.github.com/repos/googleapis/gax-java/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/googleapis/gax-java/downloads","issues_url":"https://api.github.com/repos/googleapis/gax-java/issues{/number}","pulls_url":"https://api.github.com/repos/googleapis/gax-java/pulls{/number}","milestones_url":"https://api.github.com/repos/googleapis/gax-java/milestones{/number}","notifications_url":"https://api.github.com/repos/googleapis/gax-java/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/googleapis/gax-java/labels{/name}","releases_url":"https://api.github.com/repos/googleapis/gax-java/releases{/id}","deployments_url":"https://api.github.com/repos/googleapis/gax-java/deployments","created_at":"2016-02-01T21:33:22Z","updated_at":"2018-01-26T14:36:45Z","pushed_at":"2018-02-01T04:23:45Z","git_url":"git://github.com/googleapis/gax-java.git","ssh_url":"git@github.com:googleapis/gax-java.git","clone_url":"https://github.com/googleapis/gax-java.git","svn_url":"https://github.com/googleapis/gax-java","homepage":"","size":8534,"stargazers_count":21,"watchers_count":21,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":21,"mirror_url":null,"archived":false,"open_issues_count":19,"license":null,"forks":21,"open_issues":19,"watchers":21,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/googleapis/gax-java/pulls/463"},"html":{"href":"https://github.com/googleapis/gax-java/pull/463"},"issue":{"href":"https://api.github.com/repos/googleapis/gax-java/issues/463"},"comments":{"href":"https://api.github.com/repos/googleapis/gax-java/issues/463/comments"},"review_comments":{"href":"https://api.github.com/repos/googleapis/gax-java/pulls/463/comments"},"review_comment":{"href":"https://api.github.com/repos/googleapis/gax-java/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/googleapis/gax-java/pulls/463/commits"},"statuses":{"href":"https://api.github.com/repos/googleapis/gax-java/statuses/95e0d05757680052f9c34293ed277b10da781f7c"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"garrettjonesgoogle"}}