{"action":"created","comment":{"url":"https://api.github.com/repos/akka/akka/pulls/comments/43052063","id":43052063,"diff_hunk":"@@ -0,0 +1,206 @@\n+/**\n+ * Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>\n+ */\n+\n+package akka.contrib.pattern\n+\n+import akka.actor._\n+import akka.actor.OneForOneStrategy\n+import akka.actor.SupervisorStrategy._\n+import scala.concurrent.duration._\n+\n+object TransparentExponentialBackoffSupervisor {\n+  private case object RestartChild\n+  private case class ResetRestartCount(lastNumRestarts: Int)\n+\n+  /**\n+   * Props for creating a [[TransparentExponentialBackoffSupervisor]] with a decider.\n+   *\n+   * @param childProps the [[akka.actor.Props]] of the child to be supervised.\n+   * @param minBackoff the min time before the child is restarted.\n+   * @param maxBackoff the max time (upperbound) for a child restart.\n+   * @param randomFactor a random delay factor to add on top of the calculated exponential\n+   *   back off.\n+   *   The calculation is equivalent to:\n+   *   {{{\n+   *     final_delay = min(\n+   *       maxBackoff,\n+   *       (random_delay_factor * calculated_backoff) + calculated_backoff)\n+   *   }}}\n+   * @param decider an [[akka.actor.SupervisorStrategy.Decider]] to specify how the supervisor\n+   *   should behave for different exceptions. If no cases are matched, the default decider of\n+   *   [[akka.actor.Actor]] is used. When the [[akka.actor.SupervisorStrategy.Restart]] directive\n+   *   is returned by the decider, this supervisor will apply an exponential back off restart.\n+   */\n+  def propsWithDecider(\n+    childProps: Props,\n+    minBackoff: FiniteDuration,\n+    maxBackoff: FiniteDuration,\n+    randomFactor: Double)(decider: Decider): Props = {\n+    Props(\n+      classOf[TransparentExponentialBackoffSupervisor],\n+      childProps,\n+      Some(decider),\n+      minBackoff,\n+      maxBackoff,\n+      randomFactor)\n+  }\n+\n+  /**\n+   * Props for creating a [[TransparentExponentialBackoffSupervisor]] using the\n+   * default [[akka.actor.Actor]] decider.\n+   *\n+   * @param childProps the [[akka.actor.Props]] of the child to be supervised.\n+   * @param minBackoff the min time before the child is restarted.\n+   * @param maxBackoff the max time (upperbound) for a child restart.\n+   * @param randomFactor a random delay factor to add on top of the calculated exponential\n+   *   back off.\n+   *   The calculation is equivalent to:\n+   *   {{{\n+   *     final_delay = min(\n+   *       maxBackoff,\n+   *       (random_delay_factor * calculated_backoff) + calculated_backoff)\n+   *   }}}\n+   */\n+  def props(\n+    childProps: Props,\n+    minBackoff: FiniteDuration,\n+    maxBackoff: FiniteDuration,\n+    randomFactor: Double): Props = {\n+    Props(\n+      classOf[TransparentExponentialBackoffSupervisor],\n+      childProps,\n+      None,\n+      minBackoff,\n+      maxBackoff,\n+      randomFactor)\n+  }\n+}\n+\n+/**\n+ * A supervising actor that restarts a child actor with an exponential back off.\n+ *\n+ * This explicit supervisor behaves similarly to the normal implicit supervision where\n+ * if an actor throws an exception, the decider on the supervisor will decide when to\n+ * `Stop`, `Restart`, `Escalate`, `Resume` the child actor.\n+ *\n+ * When the `Restart` directive is specified, the supervisor will delay the restart\n+ * using an exponential back off strategy (bounded by minBackoff and maxBackoff).\n+ *\n+ * This supervisor is intended to be transparent to both the child actor and external actors.\n+ * Where external actors can send messages to the supervisor as if it was the child and the\n+ * messages will be forwarded. And when the child is `Terminated`, the supervisor is also\n+ * `Terminated`.\n+ * Transparent to the child means that the child does not have to be aware that it is being\n+ * supervised specifically by the [[TransparentExponentialBackoffSupervisor]]. Just like it does\n+ * not need to know when it is being supervised by the usual implicit supervisors.\n+ * The only caveat is that the `ActorRef` of the child is not stable, so any user storing the\n+ * `sender()` `ActorRef` from the child response may eventually not be able to communicate with\n+ * the stored `ActorRef`. In general all messages to the child should be directed through the\n+ * [[TransparentExponentialBackoffSupervisor]].\n+ *\n+ * An example of where this supervisor might be used is when you may have an actor that is\n+ * responsible for continuously polling on a server for some resource that sometimes may be down.\n+ * Instead of hammering the server continuously when the resource is unavailable, the actor will\n+ * be restarted with an exponentially increasing back off until the resource is available again.\n+ */\n+class TransparentExponentialBackoffSupervisor(\n+  props: Props,\n+  decider: Option[Decider],\n+  minBackoff: FiniteDuration,\n+  maxBackoff: FiniteDuration,\n+  randomFactor: Double)\n+  extends Actor\n+  with Stash\n+  with ActorLogging {\n+\n+  import TransparentExponentialBackoffSupervisor._\n+  import context._\n+\n+  override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = -1) {\n+    case ex ⇒\n+      val defaultDirective: Directive =\n+        super.supervisorStrategy.decider.applyOrElse(ex, (_: Any) ⇒ Escalate)\n+      val maybeDirective: Option[Directive] = decider\n+        .map(_.applyOrElse(ex, (_: Any) ⇒ defaultDirective))\n+\n+      // Get the directive from the specified decider or fallback to\n+      // the default decider.\n+      // Whatever the final Directive is, we will translate all Restarts\n+      // to our own Restarts, which involves stopping the child.\n+      maybeDirective.getOrElse(defaultDirective) match {\n+        case Restart ⇒\n+          self ! RestartChild\n+          Stop\n+        case other ⇒ other\n+      }\n+  }\n+\n+  // Initialize by starting up and watching the child\n+  self ! RestartChild\n+\n+  def receive = {\n+    case RestartChild ⇒\n+      val childRef = actorOf(props)\n+      watch(childRef)\n+      unstashAll()\n+      become(watching(childRef, 0))\n+    case _ ⇒ stash()\n+  }\n+\n+  // Steady state\n+  def watching(childRef: ActorRef, numRestarts: Int): Receive = {\n+    case RestartChild ⇒\n+      log.debug(\"Waiting on child termination to restart\")\n+      become(waitingToRestart(childRef, numRestarts))\n+    case ResetRestartCount(last) ⇒\n+      if (last == numRestarts) {\n+        log.debug(s\"Last restart count [$last] matches current count; resetting\")\n+        become(watching(childRef, 0))\n+      } else {\n+        log.debug(s\"Last restart count [$last] does not match the current count [$numRestarts]\")\n+      }\n+    case Terminated(`childRef`) ⇒\n+      log.debug(s\"Terminating, because child [$childRef] terminated itself\")\n+      stop(self)\n+    case Terminated(other) ⇒\n+      log.debug(s\"Ignoring unexpected Terminated message from: $other\")\n+    case msg ⇒\n+      childRef.forward(msg)\n+  }\n+\n+  def calculateDelay(numRestarts: Int): FiniteDuration = {\n+    // Source:\n+    // https://github.com/akka/akka/blob/v2.4.0/akka-actor/src/main/scala/akka/pattern/BackoffSupervisor.scala","path":"akka-contrib/src/main/scala/akka/contrib/pattern/TransparentExponentialBackoffSupervisor.scala","position":174,"original_position":174,"commit_id":"eb5504eb30c97f92ab233152cd964d69dc04c812","original_commit_id":"eb5504eb30c97f92ab233152cd964d69dc04c812","user":{"login":"ktoso","id":120979,"avatar_url":"https://avatars.githubusercontent.com/u/120979?v=3","gravatar_id":"","url":"https://api.github.com/users/ktoso","html_url":"https://github.com/ktoso","followers_url":"https://api.github.com/users/ktoso/followers","following_url":"https://api.github.com/users/ktoso/following{/other_user}","gists_url":"https://api.github.com/users/ktoso/gists{/gist_id}","starred_url":"https://api.github.com/users/ktoso/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ktoso/subscriptions","organizations_url":"https://api.github.com/users/ktoso/orgs","repos_url":"https://api.github.com/users/ktoso/repos","events_url":"https://api.github.com/users/ktoso/events{/privacy}","received_events_url":"https://api.github.com/users/ktoso/received_events","type":"User","site_admin":false},"body":"Yup, exactly","created_at":"2015-10-26T20:59:34Z","updated_at":"2015-10-26T20:59:34Z","html_url":"https://github.com/akka/akka/pull/18776#discussion_r43052063","pull_request_url":"https://api.github.com/repos/akka/akka/pulls/18776","_links":{"self":{"href":"https://api.github.com/repos/akka/akka/pulls/comments/43052063"},"html":{"href":"https://github.com/akka/akka/pull/18776#discussion_r43052063"},"pull_request":{"href":"https://api.github.com/repos/akka/akka/pulls/18776"}}},"pull_request":{"url":"https://api.github.com/repos/akka/akka/pulls/18776","id":48690996,"html_url":"https://github.com/akka/akka/pull/18776","diff_url":"https://github.com/akka/akka/pull/18776.diff","patch_url":"https://github.com/akka/akka/pull/18776.patch","issue_url":"https://api.github.com/repos/akka/akka/issues/18776","number":18776,"state":"open","locked":false,"title":"Add TransparentExponentialBackoffSupervisor","user":{"login":"henrymai","id":136857,"avatar_url":"https://avatars.githubusercontent.com/u/136857?v=3","gravatar_id":"","url":"https://api.github.com/users/henrymai","html_url":"https://github.com/henrymai","followers_url":"https://api.github.com/users/henrymai/followers","following_url":"https://api.github.com/users/henrymai/following{/other_user}","gists_url":"https://api.github.com/users/henrymai/gists{/gist_id}","starred_url":"https://api.github.com/users/henrymai/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/henrymai/subscriptions","organizations_url":"https://api.github.com/users/henrymai/orgs","repos_url":"https://api.github.com/users/henrymai/repos","events_url":"https://api.github.com/users/henrymai/events{/privacy}","received_events_url":"https://api.github.com/users/henrymai/received_events","type":"User","site_admin":false},"body":"See this link for details:\r\nhttps://github.com/akka/akka/issues/18766","created_at":"2015-10-25T23:23:22Z","updated_at":"2015-10-26T20:59:34Z","closed_at":null,"merged_at":null,"merge_commit_sha":"07d2987b6a733c6e08d9d2fa81e795ae7d439f36","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/akka/akka/pulls/18776/commits","review_comments_url":"https://api.github.com/repos/akka/akka/pulls/18776/comments","review_comment_url":"https://api.github.com/repos/akka/akka/pulls/comments{/number}","comments_url":"https://api.github.com/repos/akka/akka/issues/18776/comments","statuses_url":"https://api.github.com/repos/akka/akka/statuses/eb5504eb30c97f92ab233152cd964d69dc04c812","head":{"label":"henrymai:master","ref":"master","sha":"eb5504eb30c97f92ab233152cd964d69dc04c812","user":{"login":"henrymai","id":136857,"avatar_url":"https://avatars.githubusercontent.com/u/136857?v=3","gravatar_id":"","url":"https://api.github.com/users/henrymai","html_url":"https://github.com/henrymai","followers_url":"https://api.github.com/users/henrymai/followers","following_url":"https://api.github.com/users/henrymai/following{/other_user}","gists_url":"https://api.github.com/users/henrymai/gists{/gist_id}","starred_url":"https://api.github.com/users/henrymai/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/henrymai/subscriptions","organizations_url":"https://api.github.com/users/henrymai/orgs","repos_url":"https://api.github.com/users/henrymai/repos","events_url":"https://api.github.com/users/henrymai/events{/privacy}","received_events_url":"https://api.github.com/users/henrymai/received_events","type":"User","site_admin":false},"repo":{"id":44931432,"name":"akka","full_name":"henrymai/akka","owner":{"login":"henrymai","id":136857,"avatar_url":"https://avatars.githubusercontent.com/u/136857?v=3","gravatar_id":"","url":"https://api.github.com/users/henrymai","html_url":"https://github.com/henrymai","followers_url":"https://api.github.com/users/henrymai/followers","following_url":"https://api.github.com/users/henrymai/following{/other_user}","gists_url":"https://api.github.com/users/henrymai/gists{/gist_id}","starred_url":"https://api.github.com/users/henrymai/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/henrymai/subscriptions","organizations_url":"https://api.github.com/users/henrymai/orgs","repos_url":"https://api.github.com/users/henrymai/repos","events_url":"https://api.github.com/users/henrymai/events{/privacy}","received_events_url":"https://api.github.com/users/henrymai/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/henrymai/akka","description":"Akka Project","fork":true,"url":"https://api.github.com/repos/henrymai/akka","forks_url":"https://api.github.com/repos/henrymai/akka/forks","keys_url":"https://api.github.com/repos/henrymai/akka/keys{/key_id}","collaborators_url":"https://api.github.com/repos/henrymai/akka/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/henrymai/akka/teams","hooks_url":"https://api.github.com/repos/henrymai/akka/hooks","issue_events_url":"https://api.github.com/repos/henrymai/akka/issues/events{/number}","events_url":"https://api.github.com/repos/henrymai/akka/events","assignees_url":"https://api.github.com/repos/henrymai/akka/assignees{/user}","branches_url":"https://api.github.com/repos/henrymai/akka/branches{/branch}","tags_url":"https://api.github.com/repos/henrymai/akka/tags","blobs_url":"https://api.github.com/repos/henrymai/akka/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/henrymai/akka/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/henrymai/akka/git/refs{/sha}","trees_url":"https://api.github.com/repos/henrymai/akka/git/trees{/sha}","statuses_url":"https://api.github.com/repos/henrymai/akka/statuses/{sha}","languages_url":"https://api.github.com/repos/henrymai/akka/languages","stargazers_url":"https://api.github.com/repos/henrymai/akka/stargazers","contributors_url":"https://api.github.com/repos/henrymai/akka/contributors","subscribers_url":"https://api.github.com/repos/henrymai/akka/subscribers","subscription_url":"https://api.github.com/repos/henrymai/akka/subscription","commits_url":"https://api.github.com/repos/henrymai/akka/commits{/sha}","git_commits_url":"https://api.github.com/repos/henrymai/akka/git/commits{/sha}","comments_url":"https://api.github.com/repos/henrymai/akka/comments{/number}","issue_comment_url":"https://api.github.com/repos/henrymai/akka/issues/comments{/number}","contents_url":"https://api.github.com/repos/henrymai/akka/contents/{+path}","compare_url":"https://api.github.com/repos/henrymai/akka/compare/{base}...{head}","merges_url":"https://api.github.com/repos/henrymai/akka/merges","archive_url":"https://api.github.com/repos/henrymai/akka/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/henrymai/akka/downloads","issues_url":"https://api.github.com/repos/henrymai/akka/issues{/number}","pulls_url":"https://api.github.com/repos/henrymai/akka/pulls{/number}","milestones_url":"https://api.github.com/repos/henrymai/akka/milestones{/number}","notifications_url":"https://api.github.com/repos/henrymai/akka/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/henrymai/akka/labels{/name}","releases_url":"https://api.github.com/repos/henrymai/akka/releases{/id}","created_at":"2015-10-25T21:48:05Z","updated_at":"2015-10-25T21:48:20Z","pushed_at":"2015-10-25T23:22:25Z","git_url":"git://github.com/henrymai/akka.git","ssh_url":"git@github.com:henrymai/akka.git","clone_url":"https://github.com/henrymai/akka.git","svn_url":"https://github.com/henrymai/akka","homepage":"http://akka.io","size":169243,"stargazers_count":0,"watchers_count":0,"language":"Scala","has_issues":false,"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":"master"}},"base":{"label":"akka:master","ref":"master","sha":"97fc49344666e3c52a3e444c6f50d18aa1eee083","user":{"login":"akka","id":1496237,"avatar_url":"https://avatars.githubusercontent.com/u/1496237?v=3","gravatar_id":"","url":"https://api.github.com/users/akka","html_url":"https://github.com/akka","followers_url":"https://api.github.com/users/akka/followers","following_url":"https://api.github.com/users/akka/following{/other_user}","gists_url":"https://api.github.com/users/akka/gists{/gist_id}","starred_url":"https://api.github.com/users/akka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akka/subscriptions","organizations_url":"https://api.github.com/users/akka/orgs","repos_url":"https://api.github.com/users/akka/repos","events_url":"https://api.github.com/users/akka/events{/privacy}","received_events_url":"https://api.github.com/users/akka/received_events","type":"Organization","site_admin":false},"repo":{"id":130013,"name":"akka","full_name":"akka/akka","owner":{"login":"akka","id":1496237,"avatar_url":"https://avatars.githubusercontent.com/u/1496237?v=3","gravatar_id":"","url":"https://api.github.com/users/akka","html_url":"https://github.com/akka","followers_url":"https://api.github.com/users/akka/followers","following_url":"https://api.github.com/users/akka/following{/other_user}","gists_url":"https://api.github.com/users/akka/gists{/gist_id}","starred_url":"https://api.github.com/users/akka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akka/subscriptions","organizations_url":"https://api.github.com/users/akka/orgs","repos_url":"https://api.github.com/users/akka/repos","events_url":"https://api.github.com/users/akka/events{/privacy}","received_events_url":"https://api.github.com/users/akka/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/akka/akka","description":"Akka Project","fork":false,"url":"https://api.github.com/repos/akka/akka","forks_url":"https://api.github.com/repos/akka/akka/forks","keys_url":"https://api.github.com/repos/akka/akka/keys{/key_id}","collaborators_url":"https://api.github.com/repos/akka/akka/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/akka/akka/teams","hooks_url":"https://api.github.com/repos/akka/akka/hooks","issue_events_url":"https://api.github.com/repos/akka/akka/issues/events{/number}","events_url":"https://api.github.com/repos/akka/akka/events","assignees_url":"https://api.github.com/repos/akka/akka/assignees{/user}","branches_url":"https://api.github.com/repos/akka/akka/branches{/branch}","tags_url":"https://api.github.com/repos/akka/akka/tags","blobs_url":"https://api.github.com/repos/akka/akka/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/akka/akka/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/akka/akka/git/refs{/sha}","trees_url":"https://api.github.com/repos/akka/akka/git/trees{/sha}","statuses_url":"https://api.github.com/repos/akka/akka/statuses/{sha}","languages_url":"https://api.github.com/repos/akka/akka/languages","stargazers_url":"https://api.github.com/repos/akka/akka/stargazers","contributors_url":"https://api.github.com/repos/akka/akka/contributors","subscribers_url":"https://api.github.com/repos/akka/akka/subscribers","subscription_url":"https://api.github.com/repos/akka/akka/subscription","commits_url":"https://api.github.com/repos/akka/akka/commits{/sha}","git_commits_url":"https://api.github.com/repos/akka/akka/git/commits{/sha}","comments_url":"https://api.github.com/repos/akka/akka/comments{/number}","issue_comment_url":"https://api.github.com/repos/akka/akka/issues/comments{/number}","contents_url":"https://api.github.com/repos/akka/akka/contents/{+path}","compare_url":"https://api.github.com/repos/akka/akka/compare/{base}...{head}","merges_url":"https://api.github.com/repos/akka/akka/merges","archive_url":"https://api.github.com/repos/akka/akka/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/akka/akka/downloads","issues_url":"https://api.github.com/repos/akka/akka/issues{/number}","pulls_url":"https://api.github.com/repos/akka/akka/pulls{/number}","milestones_url":"https://api.github.com/repos/akka/akka/milestones{/number}","notifications_url":"https://api.github.com/repos/akka/akka/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/akka/akka/labels{/name}","releases_url":"https://api.github.com/repos/akka/akka/releases{/id}","created_at":"2009-02-16T12:51:54Z","updated_at":"2015-10-26T15:16:12Z","pushed_at":"2015-10-26T20:57:42Z","git_url":"git://github.com/akka/akka.git","ssh_url":"git@github.com:akka/akka.git","clone_url":"https://github.com/akka/akka.git","svn_url":"https://github.com/akka/akka","homepage":"http://akka.io","size":277374,"stargazers_count":4608,"watchers_count":4608,"language":"Scala","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1376,"mirror_url":null,"open_issues_count":757,"forks":1376,"open_issues":757,"watchers":4608,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/akka/akka/pulls/18776"},"html":{"href":"https://github.com/akka/akka/pull/18776"},"issue":{"href":"https://api.github.com/repos/akka/akka/issues/18776"},"comments":{"href":"https://api.github.com/repos/akka/akka/issues/18776/comments"},"review_comments":{"href":"https://api.github.com/repos/akka/akka/pulls/18776/comments"},"review_comment":{"href":"https://api.github.com/repos/akka/akka/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/akka/akka/pulls/18776/commits"},"statuses":{"href":"https://api.github.com/repos/akka/akka/statuses/eb5504eb30c97f92ab233152cd964d69dc04c812"}}}}