{"action":"created","comment":{"url":"https://api.github.com/repos/App-vNext/Polly/pulls/comments/233278651","pull_request_review_id":174655866,"id":233278651,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIzMzI3ODY1MQ==","diff_hunk":"@@ -0,0 +1,118 @@\n+using System;\n+using System.Collections.Generic;\n+\n+namespace Polly.Duration\n+{\n+    /// <summary>\n+    /// Generates sleep durations in an jittered manner, making sure to mitigate any correlations.\n+    /// For example: 1s, 3s, 2s, 4s.\n+    /// For background and formula, see https://www.awsarchitectureblog.com/2015/03/backoff.html.\n+    /// </summary>\n+    public sealed class DecorrelatedJitterBackoff : ISleepDurationStrategy\n+    {\n+        private static readonly Random s_random = DurationUtils.Uniform;\n+        private readonly Random _random;\n+\n+        /// <summary>\n+        /// Whether the first retry will be immediate or not.\n+        /// </summary>\n+        public bool FastFirst { get; }\n+\n+        /// <summary>\n+        /// The minimum duration value to use for each retry.\n+        /// </summary>\n+        public TimeSpan MinDelay { get; }\n+\n+        /// <summary>\n+        /// The maximum duration value to use for each retry.\n+        /// </summary>\n+        public TimeSpan MaxDelay { get; }\n+\n+        /// <summary>\n+        /// Creates a new instance of the class.\n+        /// </summary>\n+        /// <param name=\"minDelay\">The minimum duration value to use for each retry.</param>\n+        /// <param name=\"maxDelay\">The maximum duration value to use for each retry.</param>\n+        /// <param name=\"fastFirst\">Whether the first retry will be immediate or not.</param>\n+        /// <param name=\"random\">An optional <see cref=\"Random\"/> instance to use.\n+        /// If not specified, will use a shared (singleton) instance with a random seed.</param>\n+        public DecorrelatedJitterBackoff(TimeSpan minDelay, TimeSpan maxDelay, bool fastFirst = false, Random random = null)\n+        {\n+            if (minDelay < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(minDelay));\n+            if (maxDelay < minDelay) throw new ArgumentOutOfRangeException(nameof(maxDelay));\n+\n+            _random = random ?? s_random;\n+\n+            MinDelay = minDelay;\n+            MaxDelay = maxDelay;\n+            FastFirst = fastFirst;\n+        }\n+\n+        /// <summary>\n+        /// Generate the sequence of <see cref=\"TimeSpan\"/> values to use as sleep-durations.\n+        /// </summary>\n+        /// <param name=\"retryCount\">The maximum number of retries to use, in addition to the original call.</param>\n+        public IReadOnlyList<TimeSpan> Discrete(int retryCount)\n+        {\n+            if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount));\n+\n+            TimeSpan[] delays = new TimeSpan[retryCount];\n+            if (delays.Length == 0)\n+                return delays;\n+\n+            int i = 0;\n+            if (FastFirst)\n+                delays[i++] = TimeSpan.Zero;\n+\n+            double range = MaxDelay.TotalMilliseconds - MinDelay.Milliseconds;\n+\n+            for (; i < delays.Length; i++)\n+            {\n+                double ms = range * _random.NextDouble(); // Range\n+                ms += MinDelay.TotalMilliseconds; // Floor\n+                ms = Math.Min(ms, MaxDelay.TotalMilliseconds); // Ceiling\n+\n+                delays[i] = TimeSpan.FromMilliseconds(ms);\n+            }\n+\n+            return delays;\n+        }\n+\n+        /// <summary>\n+        /// Generate a continuous sequence of <see cref=\"TimeSpan\"/> values to use as sleep-durations.\n+        /// </summary>\n+        /// <param name=\"retryCount\">The maximum number of retries to use, in addition to the original call.</param>\n+        public IEnumerable<TimeSpan> Continuous(int retryCount)\n+        {\n+            if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount));\n+            if (retryCount == 0)\n+                yield break;\n+\n+            int i = 0;\n+            if (FastFirst)\n+            {\n+                i++;\n+                yield return TimeSpan.Zero;\n+            }\n+\n+            double range = MaxDelay.TotalMilliseconds - MinDelay.Milliseconds;\n+            double max = MinDelay.TotalMilliseconds;\n+\n+            for (; i < retryCount; i++)\n+            {\n+                double ms = range * _random.NextDouble(); // Range\n+                ms += MinDelay.TotalMilliseconds; // Floor\n+                ms = Math.Min(ms, MaxDelay.TotalMilliseconds); // Ceiling\n+\n+                max = Math.Max(ms, max); // Extra\n+\n+                yield return TimeSpan.FromMilliseconds(ms);\n+            }\n+\n+            while (true)","path":"src/Polly.Shared/Duration/DecorrelatedJitterBackoff.cs","position":112,"original_position":112,"commit_id":"d51ac0eede0e6086027a578f178395270012dfe3","original_commit_id":"d51ac0eede0e6086027a578f178395270012dfe3","user":{"login":"grant-d","id":10214147,"node_id":"MDQ6VXNlcjEwMjE0MTQ3","avatar_url":"https://avatars1.githubusercontent.com/u/10214147?v=4","gravatar_id":"","url":"https://api.github.com/users/grant-d","html_url":"https://github.com/grant-d","followers_url":"https://api.github.com/users/grant-d/followers","following_url":"https://api.github.com/users/grant-d/following{/other_user}","gists_url":"https://api.github.com/users/grant-d/gists{/gist_id}","starred_url":"https://api.github.com/users/grant-d/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/grant-d/subscriptions","organizations_url":"https://api.github.com/users/grant-d/orgs","repos_url":"https://api.github.com/users/grant-d/repos","events_url":"https://api.github.com/users/grant-d/events{/privacy}","received_events_url":"https://api.github.com/users/grant-d/received_events","type":"User","site_admin":false},"body":"...then returns the `max` value thereafter","created_at":"2018-11-14T00:47:13Z","updated_at":"2018-11-14T00:47:13Z","html_url":"https://github.com/App-vNext/Polly/pull/536#discussion_r233278651","pull_request_url":"https://api.github.com/repos/App-vNext/Polly/pulls/536","author_association":"NONE","_links":{"self":{"href":"https://api.github.com/repos/App-vNext/Polly/pulls/comments/233278651"},"html":{"href":"https://github.com/App-vNext/Polly/pull/536#discussion_r233278651"},"pull_request":{"href":"https://api.github.com/repos/App-vNext/Polly/pulls/536"}}},"pull_request":{"url":"https://api.github.com/repos/App-vNext/Polly/pulls/536","id":230674214,"node_id":"MDExOlB1bGxSZXF1ZXN0MjMwNjc0MjE0","html_url":"https://github.com/App-vNext/Polly/pull/536","diff_url":"https://github.com/App-vNext/Polly/pull/536.diff","patch_url":"https://github.com/App-vNext/Polly/pull/536.patch","issue_url":"https://api.github.com/repos/App-vNext/Polly/issues/536","number":536,"state":"open","locked":false,"title":"Add ISleepDurationStrategy providers, including DecorrelatedJitter","user":{"login":"grant-d","id":10214147,"node_id":"MDQ6VXNlcjEwMjE0MTQ3","avatar_url":"https://avatars1.githubusercontent.com/u/10214147?v=4","gravatar_id":"","url":"https://api.github.com/users/grant-d","html_url":"https://github.com/grant-d","followers_url":"https://api.github.com/users/grant-d/followers","following_url":"https://api.github.com/users/grant-d/following{/other_user}","gists_url":"https://api.github.com/users/grant-d/gists{/gist_id}","starred_url":"https://api.github.com/users/grant-d/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/grant-d/subscriptions","organizations_url":"https://api.github.com/users/grant-d/orgs","repos_url":"https://api.github.com/users/grant-d/repos","events_url":"https://api.github.com/users/grant-d/events{/privacy}","received_events_url":"https://api.github.com/users/grant-d/received_events","type":"User","site_admin":false},"body":"### ISleepDurationStrategy providers\r\n\r\nPer the design in  #526, this PR includes changes that make `DecorrelatedJitterBackoff`, `ConstantBackoff`, `LinearBackoff` and `ExponentialBackoff` first-class citizens in Polly.\r\n\r\n### Details\r\n\r\nDetails in the linked issue. I do not expect this PR to be merged immediately - it needs some discussion:\r\n* Any other backoff strategies we need\r\n* Is the interface sufficient\r\n* I have added some overloads to `PolicyBuilder` but we may need more/less or elsewhere\r\n* What about the `*Forever` overloads\r\n* I need to noodle on how we handle the shared `Random` instance (per separate discussion)\r\n\r\n### Confirm the following\r\n\r\n- [x]  I have merged the latest changes from the dev vX.Y branch\r\n- [x]  I have successfully run a local build\r\n- [x]  I have included unit tests for the issue/feature\r\n- [x]  I have targeted the PR against the latest dev vX.Y branch\r\n","created_at":"2018-11-14T00:44:07Z","updated_at":"2018-11-14T00:47:13Z","closed_at":null,"merged_at":null,"merge_commit_sha":"77ceac3f9fb200124121eda5fe1ce85b38c4e490","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/App-vNext/Polly/pulls/536/commits","review_comments_url":"https://api.github.com/repos/App-vNext/Polly/pulls/536/comments","review_comment_url":"https://api.github.com/repos/App-vNext/Polly/pulls/comments{/number}","comments_url":"https://api.github.com/repos/App-vNext/Polly/issues/536/comments","statuses_url":"https://api.github.com/repos/App-vNext/Polly/statuses/d51ac0eede0e6086027a578f178395270012dfe3","head":{"label":"grant-d:gd-duration","ref":"gd-duration","sha":"d51ac0eede0e6086027a578f178395270012dfe3","user":{"login":"grant-d","id":10214147,"node_id":"MDQ6VXNlcjEwMjE0MTQ3","avatar_url":"https://avatars1.githubusercontent.com/u/10214147?v=4","gravatar_id":"","url":"https://api.github.com/users/grant-d","html_url":"https://github.com/grant-d","followers_url":"https://api.github.com/users/grant-d/followers","following_url":"https://api.github.com/users/grant-d/following{/other_user}","gists_url":"https://api.github.com/users/grant-d/gists{/gist_id}","starred_url":"https://api.github.com/users/grant-d/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/grant-d/subscriptions","organizations_url":"https://api.github.com/users/grant-d/orgs","repos_url":"https://api.github.com/users/grant-d/repos","events_url":"https://api.github.com/users/grant-d/events{/privacy}","received_events_url":"https://api.github.com/users/grant-d/received_events","type":"User","site_admin":false},"repo":{"id":157128676,"node_id":"MDEwOlJlcG9zaXRvcnkxNTcxMjg2NzY=","name":"Polly","full_name":"grant-d/Polly","private":false,"owner":{"login":"grant-d","id":10214147,"node_id":"MDQ6VXNlcjEwMjE0MTQ3","avatar_url":"https://avatars1.githubusercontent.com/u/10214147?v=4","gravatar_id":"","url":"https://api.github.com/users/grant-d","html_url":"https://github.com/grant-d","followers_url":"https://api.github.com/users/grant-d/followers","following_url":"https://api.github.com/users/grant-d/following{/other_user}","gists_url":"https://api.github.com/users/grant-d/gists{/gist_id}","starred_url":"https://api.github.com/users/grant-d/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/grant-d/subscriptions","organizations_url":"https://api.github.com/users/grant-d/orgs","repos_url":"https://api.github.com/users/grant-d/repos","events_url":"https://api.github.com/users/grant-d/events{/privacy}","received_events_url":"https://api.github.com/users/grant-d/received_events","type":"User","site_admin":false},"html_url":"https://github.com/grant-d/Polly","description":"Polly is a .NET resilience and transient-fault-handling library that allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner. From version 6.0.1, Polly targets .NET Standard 1.1 and 2.0+.","fork":true,"url":"https://api.github.com/repos/grant-d/Polly","forks_url":"https://api.github.com/repos/grant-d/Polly/forks","keys_url":"https://api.github.com/repos/grant-d/Polly/keys{/key_id}","collaborators_url":"https://api.github.com/repos/grant-d/Polly/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/grant-d/Polly/teams","hooks_url":"https://api.github.com/repos/grant-d/Polly/hooks","issue_events_url":"https://api.github.com/repos/grant-d/Polly/issues/events{/number}","events_url":"https://api.github.com/repos/grant-d/Polly/events","assignees_url":"https://api.github.com/repos/grant-d/Polly/assignees{/user}","branches_url":"https://api.github.com/repos/grant-d/Polly/branches{/branch}","tags_url":"https://api.github.com/repos/grant-d/Polly/tags","blobs_url":"https://api.github.com/repos/grant-d/Polly/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/grant-d/Polly/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/grant-d/Polly/git/refs{/sha}","trees_url":"https://api.github.com/repos/grant-d/Polly/git/trees{/sha}","statuses_url":"https://api.github.com/repos/grant-d/Polly/statuses/{sha}","languages_url":"https://api.github.com/repos/grant-d/Polly/languages","stargazers_url":"https://api.github.com/repos/grant-d/Polly/stargazers","contributors_url":"https://api.github.com/repos/grant-d/Polly/contributors","subscribers_url":"https://api.github.com/repos/grant-d/Polly/subscribers","subscription_url":"https://api.github.com/repos/grant-d/Polly/subscription","commits_url":"https://api.github.com/repos/grant-d/Polly/commits{/sha}","git_commits_url":"https://api.github.com/repos/grant-d/Polly/git/commits{/sha}","comments_url":"https://api.github.com/repos/grant-d/Polly/comments{/number}","issue_comment_url":"https://api.github.com/repos/grant-d/Polly/issues/comments{/number}","contents_url":"https://api.github.com/repos/grant-d/Polly/contents/{+path}","compare_url":"https://api.github.com/repos/grant-d/Polly/compare/{base}...{head}","merges_url":"https://api.github.com/repos/grant-d/Polly/merges","archive_url":"https://api.github.com/repos/grant-d/Polly/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/grant-d/Polly/downloads","issues_url":"https://api.github.com/repos/grant-d/Polly/issues{/number}","pulls_url":"https://api.github.com/repos/grant-d/Polly/pulls{/number}","milestones_url":"https://api.github.com/repos/grant-d/Polly/milestones{/number}","notifications_url":"https://api.github.com/repos/grant-d/Polly/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/grant-d/Polly/labels{/name}","releases_url":"https://api.github.com/repos/grant-d/Polly/releases{/id}","deployments_url":"https://api.github.com/repos/grant-d/Polly/deployments","created_at":"2018-11-11T22:45:19Z","updated_at":"2018-11-11T22:45:21Z","pushed_at":"2018-11-14T00:33:57Z","git_url":"git://github.com/grant-d/Polly.git","ssh_url":"git@github.com:grant-d/Polly.git","clone_url":"https://github.com/grant-d/Polly.git","svn_url":"https://github.com/grant-d/Polly","homepage":"http://www.thepollyproject.org","size":2570,"stargazers_count":0,"watchers_count":0,"language":"C#","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":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"App-vNext:master","ref":"master","sha":"cf63bdbe59eb187c748ffc3cc6f32088d68d5759","user":{"login":"App-vNext","id":9165070,"node_id":"MDEyOk9yZ2FuaXphdGlvbjkxNjUwNzA=","avatar_url":"https://avatars2.githubusercontent.com/u/9165070?v=4","gravatar_id":"","url":"https://api.github.com/users/App-vNext","html_url":"https://github.com/App-vNext","followers_url":"https://api.github.com/users/App-vNext/followers","following_url":"https://api.github.com/users/App-vNext/following{/other_user}","gists_url":"https://api.github.com/users/App-vNext/gists{/gist_id}","starred_url":"https://api.github.com/users/App-vNext/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/App-vNext/subscriptions","organizations_url":"https://api.github.com/users/App-vNext/orgs","repos_url":"https://api.github.com/users/App-vNext/repos","events_url":"https://api.github.com/users/App-vNext/events{/privacy}","received_events_url":"https://api.github.com/users/App-vNext/received_events","type":"Organization","site_admin":false},"repo":{"id":9864166,"node_id":"MDEwOlJlcG9zaXRvcnk5ODY0MTY2","name":"Polly","full_name":"App-vNext/Polly","private":false,"owner":{"login":"App-vNext","id":9165070,"node_id":"MDEyOk9yZ2FuaXphdGlvbjkxNjUwNzA=","avatar_url":"https://avatars2.githubusercontent.com/u/9165070?v=4","gravatar_id":"","url":"https://api.github.com/users/App-vNext","html_url":"https://github.com/App-vNext","followers_url":"https://api.github.com/users/App-vNext/followers","following_url":"https://api.github.com/users/App-vNext/following{/other_user}","gists_url":"https://api.github.com/users/App-vNext/gists{/gist_id}","starred_url":"https://api.github.com/users/App-vNext/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/App-vNext/subscriptions","organizations_url":"https://api.github.com/users/App-vNext/orgs","repos_url":"https://api.github.com/users/App-vNext/repos","events_url":"https://api.github.com/users/App-vNext/events{/privacy}","received_events_url":"https://api.github.com/users/App-vNext/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/App-vNext/Polly","description":"Polly is a .NET resilience and transient-fault-handling library that allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner. From version 6.0.1, Polly targets .NET Standard 1.1 and 2.0+.","fork":false,"url":"https://api.github.com/repos/App-vNext/Polly","forks_url":"https://api.github.com/repos/App-vNext/Polly/forks","keys_url":"https://api.github.com/repos/App-vNext/Polly/keys{/key_id}","collaborators_url":"https://api.github.com/repos/App-vNext/Polly/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/App-vNext/Polly/teams","hooks_url":"https://api.github.com/repos/App-vNext/Polly/hooks","issue_events_url":"https://api.github.com/repos/App-vNext/Polly/issues/events{/number}","events_url":"https://api.github.com/repos/App-vNext/Polly/events","assignees_url":"https://api.github.com/repos/App-vNext/Polly/assignees{/user}","branches_url":"https://api.github.com/repos/App-vNext/Polly/branches{/branch}","tags_url":"https://api.github.com/repos/App-vNext/Polly/tags","blobs_url":"https://api.github.com/repos/App-vNext/Polly/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/App-vNext/Polly/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/App-vNext/Polly/git/refs{/sha}","trees_url":"https://api.github.com/repos/App-vNext/Polly/git/trees{/sha}","statuses_url":"https://api.github.com/repos/App-vNext/Polly/statuses/{sha}","languages_url":"https://api.github.com/repos/App-vNext/Polly/languages","stargazers_url":"https://api.github.com/repos/App-vNext/Polly/stargazers","contributors_url":"https://api.github.com/repos/App-vNext/Polly/contributors","subscribers_url":"https://api.github.com/repos/App-vNext/Polly/subscribers","subscription_url":"https://api.github.com/repos/App-vNext/Polly/subscription","commits_url":"https://api.github.com/repos/App-vNext/Polly/commits{/sha}","git_commits_url":"https://api.github.com/repos/App-vNext/Polly/git/commits{/sha}","comments_url":"https://api.github.com/repos/App-vNext/Polly/comments{/number}","issue_comment_url":"https://api.github.com/repos/App-vNext/Polly/issues/comments{/number}","contents_url":"https://api.github.com/repos/App-vNext/Polly/contents/{+path}","compare_url":"https://api.github.com/repos/App-vNext/Polly/compare/{base}...{head}","merges_url":"https://api.github.com/repos/App-vNext/Polly/merges","archive_url":"https://api.github.com/repos/App-vNext/Polly/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/App-vNext/Polly/downloads","issues_url":"https://api.github.com/repos/App-vNext/Polly/issues{/number}","pulls_url":"https://api.github.com/repos/App-vNext/Polly/pulls{/number}","milestones_url":"https://api.github.com/repos/App-vNext/Polly/milestones{/number}","notifications_url":"https://api.github.com/repos/App-vNext/Polly/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/App-vNext/Polly/labels{/name}","releases_url":"https://api.github.com/repos/App-vNext/Polly/releases{/id}","deployments_url":"https://api.github.com/repos/App-vNext/Polly/deployments","created_at":"2013-05-05T04:40:04Z","updated_at":"2018-11-13T17:59:41Z","pushed_at":"2018-11-14T00:44:08Z","git_url":"git://github.com/App-vNext/Polly.git","ssh_url":"git@github.com:App-vNext/Polly.git","clone_url":"https://github.com/App-vNext/Polly.git","svn_url":"https://github.com/App-vNext/Polly","homepage":"http://www.thepollyproject.org","size":2590,"stargazers_count":5005,"watchers_count":5005,"language":"C#","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":507,"mirror_url":null,"archived":false,"open_issues_count":26,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":507,"open_issues":26,"watchers":5005,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/App-vNext/Polly/pulls/536"},"html":{"href":"https://github.com/App-vNext/Polly/pull/536"},"issue":{"href":"https://api.github.com/repos/App-vNext/Polly/issues/536"},"comments":{"href":"https://api.github.com/repos/App-vNext/Polly/issues/536/comments"},"review_comments":{"href":"https://api.github.com/repos/App-vNext/Polly/pulls/536/comments"},"review_comment":{"href":"https://api.github.com/repos/App-vNext/Polly/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/App-vNext/Polly/pulls/536/commits"},"statuses":{"href":"https://api.github.com/repos/App-vNext/Polly/statuses/d51ac0eede0e6086027a578f178395270012dfe3"}},"author_association":"NONE"}}
{"actor":{"display_login":"grant-d"}}