backoff.ex (2466B)
1 defmodule DBConnection.Backoff do 2 @moduledoc false 3 @compile :nowarn_deprecated_function 4 5 alias DBConnection.Backoff 6 7 @default_type :rand_exp 8 @min 1_000 9 @max 30_000 10 11 defstruct [:type, :min, :max, :state] 12 13 def new(opts) do 14 case Keyword.get(opts, :backoff_type, @default_type) do 15 :stop -> 16 nil 17 18 type -> 19 {min, max} = min_max(opts) 20 new(type, min, max) 21 end 22 end 23 24 def backoff(%Backoff{type: :rand, min: min, max: max} = s) do 25 {rand(min, max), s} 26 end 27 28 def backoff(%Backoff{type: :exp, min: min, state: nil} = s) do 29 {min, %Backoff{s | state: min}} 30 end 31 32 def backoff(%Backoff{type: :exp, max: max, state: prev} = s) do 33 require Bitwise 34 next = min(Bitwise.<<<(prev, 1), max) 35 {next, %Backoff{s | state: next}} 36 end 37 38 def backoff(%Backoff{type: :rand_exp, max: max, state: state} = s) do 39 {prev, lower} = state 40 next_min = min(prev, lower) 41 next_max = min(prev * 3, max) 42 next = rand(next_min, next_max) 43 {next, %Backoff{s | state: {next, lower}}} 44 end 45 46 def reset(%Backoff{type: :rand} = s), do: s 47 def reset(%Backoff{type: :exp} = s), do: %Backoff{s | state: nil} 48 49 def reset(%Backoff{type: :rand_exp, min: min, state: {_, lower}} = s) do 50 %Backoff{s | state: {min, lower}} 51 end 52 53 ## Internal 54 55 defp min_max(opts) do 56 case {opts[:backoff_min], opts[:backoff_max]} do 57 {nil, nil} -> {@min, @max} 58 {nil, max} -> {min(@min, max), max} 59 {min, nil} -> {min, max(min, @max)} 60 {min, max} -> {min, max} 61 end 62 end 63 64 defp new(_, min, _) when not (is_integer(min) and min >= 0) do 65 raise ArgumentError, "minimum #{inspect(min)} not 0 or a positive integer" 66 end 67 68 defp new(_, _, max) when not (is_integer(max) and max >= 0) do 69 raise ArgumentError, "maximum #{inspect(max)} not 0 or a positive integer" 70 end 71 72 defp new(_, min, max) when min > max do 73 raise ArgumentError, "minimum #{min} is greater than maximum #{max}" 74 end 75 76 defp new(:rand, min, max) do 77 %Backoff{type: :rand, min: min, max: max, state: nil} 78 end 79 80 defp new(:exp, min, max) do 81 %Backoff{type: :exp, min: min, max: max, state: nil} 82 end 83 84 defp new(:rand_exp, min, max) do 85 lower = max(min, div(max, 3)) 86 %Backoff{type: :rand_exp, min: min, max: max, state: {min, lower}} 87 end 88 89 defp new(type, _, _) do 90 raise ArgumentError, "unknown type #{inspect(type)}" 91 end 92 93 defp rand(min, max) do 94 :rand.uniform(max - min + 1) + min - 1 95 end 96 end