{"action":"created","comment":{"url":"https://api.github.com/repos/apache/kafka/pulls/comments/129140556","pull_request_review_id":51883471,"id":129140556,"diff_hunk":"@@ -0,0 +1,287 @@\n+/*\n+ * Licensed to the Apache Software Foundation (ASF) under one or more\n+ * contributor license agreements. See the NOTICE file distributed with\n+ * this work for additional information regarding copyright ownership.\n+ * The ASF licenses this file to You under the Apache License, Version 2.0\n+ * (the \"License\"); you may not use this file except in compliance with\n+ * the License. You may obtain a copy of the License at\n+ *\n+ *    http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+\n+package kafka.controller\n+\n+import java.util.concurrent.locks.{ReentrantLock, ReentrantReadWriteLock}\n+import java.util.concurrent.{ArrayBlockingQueue, ConcurrentHashMap, CountDownLatch, TimeUnit}\n+\n+import kafka.utils.CoreUtils.{inLock, inReadLock, inWriteLock}\n+import kafka.utils.Logging\n+import org.apache.kafka.common.utils.Time\n+import org.apache.zookeeper.AsyncCallback.{ACLCallback, Children2Callback, DataCallback, StatCallback, StringCallback, VoidCallback}\n+import org.apache.zookeeper.Watcher.Event.{EventType, KeeperState}\n+import org.apache.zookeeper.ZooKeeper.States\n+import org.apache.zookeeper.data.{ACL, Stat}\n+import org.apache.zookeeper.{CreateMode, WatchedEvent, Watcher, ZooKeeper}\n+\n+/**\n+  * ZookeeperClient is a zookeeper client that encourages pipelined requests to zookeeper.\n+  *\n+  * @param connectString comma separated host:port pairs, each corresponding to a zk server\n+  * @param sessionTimeoutMs session timeout in milliseconds\n+  * @param connectionTimeoutMs connection timeout in milliseconds\n+  * @param stateChangeHandler state change handler callbacks called by the underlying zookeeper client's EventThread.\n+  * @param time\n+  */\n+class ZookeeperClient(connectString: String, sessionTimeoutMs: Int, connectionTimeoutMs: Int, stateChangeHandler: StateChangeHandler, time: Time) extends Logging {\n+  private val initializationLock = new ReentrantReadWriteLock()\n+  private val isConnectedOrExpiredLock = new ReentrantLock()\n+  private val isConnectedOrExpiredCondition = isConnectedOrExpiredLock.newCondition()\n+  private val zNodeChangeHandlers = new ConcurrentHashMap[String, ZNodeChangeHandler]()\n+  private val zNodeChildChangeHandlers = new ConcurrentHashMap[String, ZNodeChildChangeHandler]()\n+\n+  info(s\"Initializing a new session to $connectString.\")\n+  @volatile private var zooKeeper = new ZooKeeper(connectString, sessionTimeoutMs, ZookeeperClientWatcher)\n+  waitUntilConnected(connectionTimeoutMs, TimeUnit.MILLISECONDS)\n+\n+  /**\n+    * Take an AsyncRequest and wait for its AsyncResponse.\n+    * @param request a single AsyncRequest to wait on.\n+    * @return the request's AsyncReponse.\n+    */\n+  def handle(request: AsyncRequest): AsyncResponse = {\n+    handle(Seq(request)).head\n+  }\n+\n+  /**\n+    * Pipeline a sequence of AsyncRequests and wait for all of their AsyncResponses.\n+    * @param requests a sequence of AsyncRequests to wait on.\n+    * @return the AsyncResponses.\n+    */\n+  def handle(requests: Seq[AsyncRequest]): Seq[AsyncResponse] = inReadLock(initializationLock) {\n+    import scala.collection.JavaConverters._\n+    val countDownLatch = new CountDownLatch(requests.size)\n+    val responseQueue = new ArrayBlockingQueue[AsyncResponse](requests.size)\n+    requests.foreach {\n+      case CreateRequest(path, data, acl, createMode, ctx) => zooKeeper.create(path, data, acl.asJava, createMode, new StringCallback {\n+        override def processResult(rc: Int, path: String, ctx: Any, name: String) = {\n+          responseQueue.add(CreateResponse(rc, path, ctx, name))\n+          countDownLatch.countDown()\n+        }}, ctx)\n+      case DeleteRequest(path, version, ctx) => zooKeeper.delete(path, version, new VoidCallback {\n+        override def processResult(rc: Int, path: String, ctx: Any) = {\n+          responseQueue.add(DeleteResponse(rc, path, ctx))\n+          countDownLatch.countDown()\n+        }}, ctx)\n+      case ExistsRequest(path, ctx) => zooKeeper.exists(path, zNodeChangeHandlers.containsKey(path), new StatCallback {\n+        override def processResult(rc: Int, path: String, ctx: Any, stat: Stat) = {\n+          responseQueue.add(ExistsResponse(rc, path, ctx, stat))\n+          countDownLatch.countDown()\n+        }}, ctx)\n+      case GetDataRequest(path, ctx) => zooKeeper.getData(path, zNodeChangeHandlers.containsKey(path), new DataCallback {\n+        override def processResult(rc: Int, path: String, ctx: Any, data: Array[Byte], stat: Stat) = {\n+          responseQueue.add(GetDataResponse(rc, path, ctx, data, stat))\n+          countDownLatch.countDown()\n+        }}, ctx)\n+      case SetDataRequest(path, data, version, ctx) => zooKeeper.setData(path, data, version, new StatCallback {\n+        override def processResult(rc: Int, path: String, ctx: Any, stat: Stat) = {\n+          responseQueue.add(SetDataResponse(rc, path, ctx, stat))\n+          countDownLatch.countDown()\n+        }}, ctx)\n+      case GetACLRequest(path, ctx) => zooKeeper.getACL(path, null, new ACLCallback {\n+        override def processResult(rc: Int, path: String, ctx: Any, acl: java.util.List[ACL], stat: Stat): Unit = {\n+          responseQueue.add(GetACLResponse(rc, path, ctx, Option(acl).map(_.asScala).orNull, stat))\n+          countDownLatch.countDown()\n+        }}, ctx)\n+      case SetACLRequest(path, acl, version, ctx) => zooKeeper.setACL(path, acl.asJava, version, new StatCallback {\n+        override def processResult(rc: Int, path: String, ctx: Any, stat: Stat) = {\n+          responseQueue.add(SetACLResponse(rc, path, ctx, stat))\n+          countDownLatch.countDown()\n+        }}, ctx)\n+      case GetChildrenRequest(path, ctx) => zooKeeper.getChildren(path, zNodeChildChangeHandlers.containsKey(path), new Children2Callback {\n+        override def processResult(rc: Int, path: String, ctx: Any, children: java.util.List[String], stat: Stat) = {\n+          responseQueue.add(GetChildrenResponse(rc, path, ctx, Option(children).map(_.asScala).orNull, stat))\n+          countDownLatch.countDown()\n+        }}, ctx)\n+    }\n+    countDownLatch.await()\n+    responseQueue.asScala.toSeq\n+  }\n+\n+  /**\n+    * Wait indefinitely until the underlying zookeeper client to reaches the CONNECTED state.\n+    * @throws ZookeeperClientAuthFailedException if the authentication failed either before or while waiting for connection.\n+    * @throws ZookeeperClientExpiredException if the session expired either before or while waiting for connection.\n+    */\n+  def waitUntilConnected(): Unit = inLock(isConnectedOrExpiredLock) {\n+    waitUntilConnected(Long.MaxValue, TimeUnit.MILLISECONDS)\n+  }\n+\n+  private def waitUntilConnected(timeout: Long, timeUnit: TimeUnit): Unit = {\n+    info(\"Waiting until connected.\")\n+    var nanos = timeUnit.toNanos(timeout)\n+    inLock(isConnectedOrExpiredLock) {\n+      var state = zooKeeper.getState\n+      while (!state.isConnected && state.isAlive) {\n+        if (nanos <= 0) {\n+          throw new ZookeeperClientTimeoutException(s\"Timed out waiting for connection while in state: $state\")\n+        }\n+        nanos = isConnectedOrExpiredCondition.awaitNanos(nanos)\n+        state = zooKeeper.getState\n+      }\n+      if (state == States.AUTH_FAILED) {\n+        throw new ZookeeperClientAuthFailedException(\"Auth failed either before or while waiting for connection\")\n+      } else if (state == States.CLOSED) {\n+        throw new ZookeeperClientExpiredException(\"Session expired either before or while waiting for connection\")\n+      }\n+    }\n+    info(\"Connected.\")\n+  }\n+\n+  def registerZNodeChangeHandler(zNodeChangeHandler: ZNodeChangeHandler): ExistsResponse = {\n+    registerZNodeChangeHandlers(Seq(zNodeChangeHandler)).head\n+  }\n+\n+  def registerZNodeChangeHandlers(handlers: Seq[ZNodeChangeHandler]): Seq[ExistsResponse] = {\n+    handlers.foreach(handler => zNodeChangeHandlers.put(handler.path, handler))\n+    val asyncRequests = handlers.map(handler => ExistsRequest(handler.path, null))\n+    handle(asyncRequests).asInstanceOf[Seq[ExistsResponse]]\n+  }\n+\n+  def unregisterZNodeChangeHandler(path: String): Unit = {\n+    zNodeChangeHandlers.remove(path)\n+  }\n+\n+  def registerZNodeChildChangeHandler(zNodeChildChangeHandler: ZNodeChildChangeHandler): GetChildrenResponse = {\n+    registerZNodeChildChangeHandlers(Seq(zNodeChildChangeHandler)).head\n+  }\n+\n+  def registerZNodeChildChangeHandlers(handlers: Seq[ZNodeChildChangeHandler]): Seq[GetChildrenResponse] = {\n+    handlers.foreach(handler => zNodeChildChangeHandlers.put(handler.path, handler))\n+    val asyncRequests = handlers.map(handler => GetChildrenRequest(handler.path, null))\n+    handle(asyncRequests).asInstanceOf[Seq[GetChildrenResponse]]\n+  }\n+\n+  def unregisterZNodeChildChangeHandler(path: String): Unit = {\n+    zNodeChildChangeHandlers.remove(path)\n+  }\n+\n+  def close(): Unit = inWriteLock(initializationLock) {\n+    info(\"Closing.\")\n+    zNodeChangeHandlers.clear()\n+    zNodeChildChangeHandlers.clear()\n+    zooKeeper.close()\n+    info(\"Closed.\")\n+  }\n+\n+  private def initialize(): Unit = {\n+    if (!zooKeeper.getState.isAlive) {\n+      info(s\"Initializing a new session to $connectString.\")\n+      var now = time.milliseconds()","path":"core/src/main/scala/kafka/controller/ZookeeperClient.scala","position":185,"original_position":185,"commit_id":"f2a9d1687471f5a19a39161ec7a57c00e2ea3ea3","original_commit_id":"f2a9d1687471f5a19a39161ec7a57c00e2ea3ea3","user":{"login":"onurkaraman","id":529976,"avatar_url":"https://avatars1.githubusercontent.com/u/529976?v=4","gravatar_id":"","url":"https://api.github.com/users/onurkaraman","html_url":"https://github.com/onurkaraman","followers_url":"https://api.github.com/users/onurkaraman/followers","following_url":"https://api.github.com/users/onurkaraman/following{/other_user}","gists_url":"https://api.github.com/users/onurkaraman/gists{/gist_id}","starred_url":"https://api.github.com/users/onurkaraman/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/onurkaraman/subscriptions","organizations_url":"https://api.github.com/users/onurkaraman/orgs","repos_url":"https://api.github.com/users/onurkaraman/repos","events_url":"https://api.github.com/users/onurkaraman/events{/privacy}","received_events_url":"https://api.github.com/users/onurkaraman/received_events","type":"User","site_admin":false},"body":"I see. Yeah that's a good point. Basically the underlying condition variable's await still uses real system time and not our mock time, so our injected time only actually helps in a subset of timing scenarios.","created_at":"2017-07-24T20:10:42Z","updated_at":"2017-07-24T20:10:43Z","html_url":"https://github.com/apache/kafka/pull/3427#discussion_r129140556","pull_request_url":"https://api.github.com/repos/apache/kafka/pulls/3427","_links":{"self":{"href":"https://api.github.com/repos/apache/kafka/pulls/comments/129140556"},"html":{"href":"https://github.com/apache/kafka/pull/3427#discussion_r129140556"},"pull_request":{"href":"https://api.github.com/repos/apache/kafka/pulls/3427"}}},"pull_request":{"url":"https://api.github.com/repos/apache/kafka/pulls/3427","id":127324926,"html_url":"https://github.com/apache/kafka/pull/3427","diff_url":"https://github.com/apache/kafka/pull/3427.diff","patch_url":"https://github.com/apache/kafka/pull/3427.patch","issue_url":"https://api.github.com/repos/apache/kafka/issues/3427","number":3427,"state":"open","locked":false,"title":"KAFKA-5501 [WIP]: use async zookeeper apis everywhere","user":{"login":"onurkaraman","id":529976,"avatar_url":"https://avatars1.githubusercontent.com/u/529976?v=4","gravatar_id":"","url":"https://api.github.com/users/onurkaraman","html_url":"https://github.com/onurkaraman","followers_url":"https://api.github.com/users/onurkaraman/followers","following_url":"https://api.github.com/users/onurkaraman/following{/other_user}","gists_url":"https://api.github.com/users/onurkaraman/gists{/gist_id}","starred_url":"https://api.github.com/users/onurkaraman/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/onurkaraman/subscriptions","organizations_url":"https://api.github.com/users/onurkaraman/orgs","repos_url":"https://api.github.com/users/onurkaraman/repos","events_url":"https://api.github.com/users/onurkaraman/events{/privacy}","received_events_url":"https://api.github.com/users/onurkaraman/received_events","type":"User","site_admin":false},"body":"Synchronous zookeeper writes means that we wait an entire round trip before doing the next write. With respect to the controller, these synchronous writes are happening at a per-partition granularity in several places, so partition-heavy clusters suffer from the controller doing many sequential round trips to zookeeper.\r\n- PartitionStateMachine.electLeaderForPartition updates leaderAndIsr in zookeeper on transition to OnlinePartition. This gets triggered per-partition sequentially with synchronous writes during controlled shutdown of the shutting down broker's replicas for which it is the leader.\r\n- ReplicaStateMachine updates leaderAndIsr in zookeeper on transition to OfflineReplica when calling KafkaController.removeReplicaFromIsr. This gets triggered per-partition sequentially with synchronous writes for failed or controlled shutdown brokers.","created_at":"2017-06-24T09:50:46Z","updated_at":"2017-07-24T20:10:43Z","closed_at":null,"merged_at":null,"merge_commit_sha":"4c60e64ff297281cdeff4554904f207b7adc49b2","assignee":null,"assignees":[],"requested_reviewers":[],"milestone":null,"commits_url":"https://api.github.com/repos/apache/kafka/pulls/3427/commits","review_comments_url":"https://api.github.com/repos/apache/kafka/pulls/3427/comments","review_comment_url":"https://api.github.com/repos/apache/kafka/pulls/comments{/number}","comments_url":"https://api.github.com/repos/apache/kafka/issues/3427/comments","statuses_url":"https://api.github.com/repos/apache/kafka/statuses/f2a9d1687471f5a19a39161ec7a57c00e2ea3ea3","head":{"label":"onurkaraman:KAFKA-5501","ref":"KAFKA-5501","sha":"f2a9d1687471f5a19a39161ec7a57c00e2ea3ea3","user":{"login":"onurkaraman","id":529976,"avatar_url":"https://avatars1.githubusercontent.com/u/529976?v=4","gravatar_id":"","url":"https://api.github.com/users/onurkaraman","html_url":"https://github.com/onurkaraman","followers_url":"https://api.github.com/users/onurkaraman/followers","following_url":"https://api.github.com/users/onurkaraman/following{/other_user}","gists_url":"https://api.github.com/users/onurkaraman/gists{/gist_id}","starred_url":"https://api.github.com/users/onurkaraman/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/onurkaraman/subscriptions","organizations_url":"https://api.github.com/users/onurkaraman/orgs","repos_url":"https://api.github.com/users/onurkaraman/repos","events_url":"https://api.github.com/users/onurkaraman/events{/privacy}","received_events_url":"https://api.github.com/users/onurkaraman/received_events","type":"User","site_admin":false},"repo":{"id":39984426,"name":"kafka","full_name":"onurkaraman/kafka","owner":{"login":"onurkaraman","id":529976,"avatar_url":"https://avatars1.githubusercontent.com/u/529976?v=4","gravatar_id":"","url":"https://api.github.com/users/onurkaraman","html_url":"https://github.com/onurkaraman","followers_url":"https://api.github.com/users/onurkaraman/followers","following_url":"https://api.github.com/users/onurkaraman/following{/other_user}","gists_url":"https://api.github.com/users/onurkaraman/gists{/gist_id}","starred_url":"https://api.github.com/users/onurkaraman/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/onurkaraman/subscriptions","organizations_url":"https://api.github.com/users/onurkaraman/orgs","repos_url":"https://api.github.com/users/onurkaraman/repos","events_url":"https://api.github.com/users/onurkaraman/events{/privacy}","received_events_url":"https://api.github.com/users/onurkaraman/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/onurkaraman/kafka","description":"Mirror of Apache Kafka","fork":true,"url":"https://api.github.com/repos/onurkaraman/kafka","forks_url":"https://api.github.com/repos/onurkaraman/kafka/forks","keys_url":"https://api.github.com/repos/onurkaraman/kafka/keys{/key_id}","collaborators_url":"https://api.github.com/repos/onurkaraman/kafka/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/onurkaraman/kafka/teams","hooks_url":"https://api.github.com/repos/onurkaraman/kafka/hooks","issue_events_url":"https://api.github.com/repos/onurkaraman/kafka/issues/events{/number}","events_url":"https://api.github.com/repos/onurkaraman/kafka/events","assignees_url":"https://api.github.com/repos/onurkaraman/kafka/assignees{/user}","branches_url":"https://api.github.com/repos/onurkaraman/kafka/branches{/branch}","tags_url":"https://api.github.com/repos/onurkaraman/kafka/tags","blobs_url":"https://api.github.com/repos/onurkaraman/kafka/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/onurkaraman/kafka/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/onurkaraman/kafka/git/refs{/sha}","trees_url":"https://api.github.com/repos/onurkaraman/kafka/git/trees{/sha}","statuses_url":"https://api.github.com/repos/onurkaraman/kafka/statuses/{sha}","languages_url":"https://api.github.com/repos/onurkaraman/kafka/languages","stargazers_url":"https://api.github.com/repos/onurkaraman/kafka/stargazers","contributors_url":"https://api.github.com/repos/onurkaraman/kafka/contributors","subscribers_url":"https://api.github.com/repos/onurkaraman/kafka/subscribers","subscription_url":"https://api.github.com/repos/onurkaraman/kafka/subscription","commits_url":"https://api.github.com/repos/onurkaraman/kafka/commits{/sha}","git_commits_url":"https://api.github.com/repos/onurkaraman/kafka/git/commits{/sha}","comments_url":"https://api.github.com/repos/onurkaraman/kafka/comments{/number}","issue_comment_url":"https://api.github.com/repos/onurkaraman/kafka/issues/comments{/number}","contents_url":"https://api.github.com/repos/onurkaraman/kafka/contents/{+path}","compare_url":"https://api.github.com/repos/onurkaraman/kafka/compare/{base}...{head}","merges_url":"https://api.github.com/repos/onurkaraman/kafka/merges","archive_url":"https://api.github.com/repos/onurkaraman/kafka/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/onurkaraman/kafka/downloads","issues_url":"https://api.github.com/repos/onurkaraman/kafka/issues{/number}","pulls_url":"https://api.github.com/repos/onurkaraman/kafka/pulls{/number}","milestones_url":"https://api.github.com/repos/onurkaraman/kafka/milestones{/number}","notifications_url":"https://api.github.com/repos/onurkaraman/kafka/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/onurkaraman/kafka/labels{/name}","releases_url":"https://api.github.com/repos/onurkaraman/kafka/releases{/id}","deployments_url":"https://api.github.com/repos/onurkaraman/kafka/deployments","created_at":"2015-07-31T03:20:01Z","updated_at":"2016-01-23T00:19:29Z","pushed_at":"2017-07-22T19:15:16Z","git_url":"git://github.com/onurkaraman/kafka.git","ssh_url":"git@github.com:onurkaraman/kafka.git","clone_url":"https://github.com/onurkaraman/kafka.git","svn_url":"https://github.com/onurkaraman/kafka","homepage":null,"size":51055,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"trunk"}},"base":{"label":"apache:trunk","ref":"trunk","sha":"d0ce0a95de8c5451d73dcc87f3f9b6c03f5f85de","user":{"login":"apache","id":47359,"avatar_url":"https://avatars0.githubusercontent.com/u/47359?v=4","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"repo":{"id":2211243,"name":"kafka","full_name":"apache/kafka","owner":{"login":"apache","id":47359,"avatar_url":"https://avatars0.githubusercontent.com/u/47359?v=4","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/apache/kafka","description":"Mirror of Apache Kafka","fork":false,"url":"https://api.github.com/repos/apache/kafka","forks_url":"https://api.github.com/repos/apache/kafka/forks","keys_url":"https://api.github.com/repos/apache/kafka/keys{/key_id}","collaborators_url":"https://api.github.com/repos/apache/kafka/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/apache/kafka/teams","hooks_url":"https://api.github.com/repos/apache/kafka/hooks","issue_events_url":"https://api.github.com/repos/apache/kafka/issues/events{/number}","events_url":"https://api.github.com/repos/apache/kafka/events","assignees_url":"https://api.github.com/repos/apache/kafka/assignees{/user}","branches_url":"https://api.github.com/repos/apache/kafka/branches{/branch}","tags_url":"https://api.github.com/repos/apache/kafka/tags","blobs_url":"https://api.github.com/repos/apache/kafka/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/apache/kafka/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/apache/kafka/git/refs{/sha}","trees_url":"https://api.github.com/repos/apache/kafka/git/trees{/sha}","statuses_url":"https://api.github.com/repos/apache/kafka/statuses/{sha}","languages_url":"https://api.github.com/repos/apache/kafka/languages","stargazers_url":"https://api.github.com/repos/apache/kafka/stargazers","contributors_url":"https://api.github.com/repos/apache/kafka/contributors","subscribers_url":"https://api.github.com/repos/apache/kafka/subscribers","subscription_url":"https://api.github.com/repos/apache/kafka/subscription","commits_url":"https://api.github.com/repos/apache/kafka/commits{/sha}","git_commits_url":"https://api.github.com/repos/apache/kafka/git/commits{/sha}","comments_url":"https://api.github.com/repos/apache/kafka/comments{/number}","issue_comment_url":"https://api.github.com/repos/apache/kafka/issues/comments{/number}","contents_url":"https://api.github.com/repos/apache/kafka/contents/{+path}","compare_url":"https://api.github.com/repos/apache/kafka/compare/{base}...{head}","merges_url":"https://api.github.com/repos/apache/kafka/merges","archive_url":"https://api.github.com/repos/apache/kafka/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/apache/kafka/downloads","issues_url":"https://api.github.com/repos/apache/kafka/issues{/number}","pulls_url":"https://api.github.com/repos/apache/kafka/pulls{/number}","milestones_url":"https://api.github.com/repos/apache/kafka/milestones{/number}","notifications_url":"https://api.github.com/repos/apache/kafka/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/apache/kafka/labels{/name}","releases_url":"https://api.github.com/repos/apache/kafka/releases{/id}","deployments_url":"https://api.github.com/repos/apache/kafka/deployments","created_at":"2011-08-15T18:06:16Z","updated_at":"2017-07-24T13:19:06Z","pushed_at":"2017-07-24T19:06:40Z","git_url":"git://github.com/apache/kafka.git","ssh_url":"git@github.com:apache/kafka.git","clone_url":"https://github.com/apache/kafka.git","svn_url":"https://github.com/apache/kafka","homepage":null,"size":52822,"stargazers_count":5660,"watchers_count":5660,"language":"Java","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":3520,"mirror_url":"git://git.apache.org/kafka.git","open_issues_count":405,"forks":3520,"open_issues":405,"watchers":5660,"default_branch":"trunk"}},"_links":{"self":{"href":"https://api.github.com/repos/apache/kafka/pulls/3427"},"html":{"href":"https://github.com/apache/kafka/pull/3427"},"issue":{"href":"https://api.github.com/repos/apache/kafka/issues/3427"},"comments":{"href":"https://api.github.com/repos/apache/kafka/issues/3427/comments"},"review_comments":{"href":"https://api.github.com/repos/apache/kafka/pulls/3427/comments"},"review_comment":{"href":"https://api.github.com/repos/apache/kafka/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/apache/kafka/pulls/3427/commits"},"statuses":{"href":"https://api.github.com/repos/apache/kafka/statuses/f2a9d1687471f5a19a39161ec7a57c00e2ea3ea3"}}}}
{"actor":{"display_login":"onurkaraman"}}