From 8f05e1ea5b938b61b91e1eebd9bb7764c037abc0 Mon Sep 17 00:00:00 2001 From: Joe Heck Date: Sun, 7 Apr 2024 15:07:18 -0700 Subject: [PATCH] calculating a backoff value for auto-reconnect timing --- Sources/AutomergeRepo/Backoff.swift | 27 +++++++++++++++++++++ Tests/AutomergeRepoTests/BackoffTests.swift | 25 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 Sources/AutomergeRepo/Backoff.swift create mode 100644 Tests/AutomergeRepoTests/BackoffTests.swift diff --git a/Sources/AutomergeRepo/Backoff.swift b/Sources/AutomergeRepo/Backoff.swift new file mode 100644 index 0000000..5598e17 --- /dev/null +++ b/Sources/AutomergeRepo/Backoff.swift @@ -0,0 +1,27 @@ +import Foundation + +public enum Backoff { + // fibonacci numbers for 0...15 + static let fibonacci = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610] + + public static func delay(_ step: UInt, withJitter: Bool) -> Int { + let boundedStep = Int(min(15, step)) + + if withJitter { + // pick a range of +/- values that's one fibonacci step lower + let jitterStep = max(min(15,boundedStep - 1),0) + if jitterStep < 1 { + // picking a random number between -0 and 0 is just silly, and kinda wrong + // so just return the fibonacci number + return Self.fibonacci[boundedStep] + } + let jitterRange = -1*Self.fibonacci[jitterStep]...Self.fibonacci[jitterStep] + let selectedValue = Int.random(in: jitterRange) + // no delay should be less than 0 + let adjustedValue = max(0, Self.fibonacci[boundedStep] + selectedValue) + // max value is 987, min value is 0 + return adjustedValue + } + return Self.fibonacci[boundedStep] + } +} diff --git a/Tests/AutomergeRepoTests/BackoffTests.swift b/Tests/AutomergeRepoTests/BackoffTests.swift new file mode 100644 index 0000000..3bb997b --- /dev/null +++ b/Tests/AutomergeRepoTests/BackoffTests.swift @@ -0,0 +1,25 @@ +import AutomergeRepo +import XCTest + +final class BackoffTests: XCTestCase { + func testSimpleBackoff() throws { + XCTAssertEqual(0, Backoff.delay(0, withJitter: false)) + XCTAssertEqual(1, Backoff.delay(1, withJitter: false)) + XCTAssertEqual(1, Backoff.delay(2, withJitter: false)) + XCTAssertEqual(2, Backoff.delay(3, withJitter: false)) + XCTAssertEqual(3, Backoff.delay(4, withJitter: false)) + XCTAssertEqual(610, Backoff.delay(15, withJitter: false)) + XCTAssertEqual(610, Backoff.delay(16, withJitter: false)) + XCTAssertEqual(610, Backoff.delay(100, withJitter: false)) + } + + func testWithJitterBackoff() throws { + XCTAssertEqual(0, Backoff.delay(0, withJitter: true)) + XCTAssertEqual(1, Backoff.delay(1, withJitter: true)) + for i: UInt in 2...50 { + XCTAssertTrue(Backoff.delay(i, withJitter: true) <= 987) + print(Backoff.delay(i, withJitter: true)) + } + } + +}