Page MenuHomePhorge

No OneTemporary

Size
6 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/flake_id/builder.ex b/lib/flake_id/builder.ex
index 1306634..e363044 100644
--- a/lib/flake_id/builder.ex
+++ b/lib/flake_id/builder.ex
@@ -1,130 +1,130 @@
# FlakeId: Decentralized, k-ordered ID generation service
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: LGPL-3.0-only
defmodule FlakeId.Builder do
@moduledoc false
defstruct node: nil, time: 0, sq: 0, clock: nil
@type t :: %__MODULE__{
node: non_neg_integer,
time: non_neg_integer,
sq: non_neg_integer
}
@type options :: [worker_id_option() | time_hint_option() | clock_option()]
@type worker_id_option :: {:worker_id, any()}
@type time_hint_option :: {:time_hint, false | true | atom()}
@type clock_option :: {module(), atom()}
@spec new(options()) :: t()
def new(options \\ []) do
worker_id = worker_id(Keyword.get(options, :worker_id))
time =
case Keyword.get(options, :time_hint) do
false -> 0
name when is_atom(name) -> FlakeId.TimeHint.get(name)
_ -> FlakeId.TimeHint.get()
end
clock = Keyword.get(options, :clock, default_clock())
%__MODULE__{node: worker_id, time: time, clock: clock}
end
def backwards?(%__MODULE__{time: time, clock: clock}) do
time > time(clock)
end
def time() do
time(default_clock())
end
def time(%__MODULE__{clock: clock}) do
time(clock)
end
def time({mod, fun}) do
apply(mod, fun, [:millisecond])
end
@spec get(t) :: {:ok, <<_::128>>, t} | FlakeId.error()
def get(state = %__MODULE__{clock: clock}) do
get(time(clock), state)
end
@spec get(non_neg_integer, t) :: {:ok, <<_::128>>, t} | FlakeId.error()
# Error when time didn't change and sequence is too big for 16bit int.
def get(time, %__MODULE__{time: time, sq: sq}) when sq >= 65_535 do
{:error, :clock_stuck}
end
# Matches when the calling time is the same as the state time. Incr. sq
def get(time, %__MODULE__{node: node, time: time, sq: seq} = state)
when is_integer(time) and is_integer(node) and is_integer(seq) do
new_state = %__MODULE__{state | sq: seq + 1}
{:ok, gen_flake(new_state), new_state}
end
# Error when clock is running backwards
def get(newtime, %__MODULE__{time: time}) when newtime < time do
{:error, :clock_running_backwards}
end
# Matches when the times are different, reset sq
def get(newtime, %__MODULE__{node: node, time: time, sq: seq} = state)
when is_integer(time) and is_integer(node) and is_integer(seq) and newtime > time do
new_state = %__MODULE__{state | time: newtime, sq: 0}
{:ok, gen_flake(new_state), new_state}
end
def get(_, _) do
{:error, :invalid_state}
end
@spec gen_flake(t) :: <<_::128>>
defp gen_flake(%__MODULE__{time: time, node: node, sq: seq}) do
<<time::integer-size(64), node::integer-size(48), seq::integer-size(16)>>
end
defp default_clock() do
if :erlang.system_info(:time_warp_mode) == :multi_time_warp do
{:erlang, :system_time}
else
{:os, :system_time}
end
end
def worker_id(setting \\ nil)
def worker_id(nil) do
worker_id(Application.get_env(:flake_id, :worker_id, :random))
end
def worker_id(:node) do
:erlang.phash2(node())
end
def worker_id({:mac, iface}) do
{:ok, addresses} = :inet.getifaddrs()
proplist = :proplists.get_value(iface, addresses)
hwaddr = Enum.take(:proplists.get_value(:hwaddr, proplist), 6)
<<worker::integer-size(48)>> = :binary.list_to_bin(hwaddr)
worker
end
def worker_id(:random) do
<<worker::integer-size(48)>> = :crypto.strong_rand_bytes(6)
worker
end
def worker_id(integer) when is_integer(integer) do
- 42
+ integer
end
def worker_id(binary) when is_binary(binary) do
:erlang.phash2(binary)
end
end
diff --git a/test/flake_id/builder_test.exs b/test/flake_id/builder_test.exs
index 500d6b8..c6801bb 100644
--- a/test/flake_id/builder_test.exs
+++ b/test/flake_id/builder_test.exs
@@ -1,69 +1,70 @@
defmodule FlakeId.BuilderTest do
use ExUnit.Case, async: true
alias FlakeId.Builder
describe "get/2" do
test "increment `:sq` when the calling time is the same as the state time" do
builder = Builder.new()
time = builder.time
assert {:ok, <<_::integer-size(128)>>, %Builder{time: ^time, sq: 1}} =
Builder.get(time, builder)
end
test "reset `:sq` when the times are different" do
time = Builder.time()
node = Builder.worker_id()
state = %Builder{time: time - 1, node: node, sq: 42}
assert {:ok, _, %Builder{time: ^time, sq: 0}} = Builder.get(time, state)
end
test "error when clock is running backwards" do
time = Builder.time()
state = %Builder{time: time + 1}
assert Builder.get(time, state) == {:error, :clock_running_backwards}
end
test "error when clock is stuck and `sq` overflows" do
time = Builder.time()
state = %Builder{node: 0, time: time, sq: 65534}
assert {:ok, _, %Builder{sq: sq}} = Builder.get(time, state)
state = %Builder{state | node: 0, sq: sq}
assert Builder.get(time, state) == {:error, :clock_stuck}
end
end
test "uses initial time from TimeHint" do
file = "TEST-flake-id-builder-initial-time-hint"
on_exit(fn -> File.rm(file) end)
FakeClock.set(42)
{:ok, _} =
FlakeId.TimeHint.start_link(name: TestTimeHint, file: file, clock: {FakeClock, :time})
builder = Builder.new(time_hint: TestTimeHint)
assert builder.time == FlakeId.TimeHint.get(TestTimeHint)
end
describe "worker_id" do
test "random" do
builder1 = Builder.new(worker_id: :random)
builder2 = Builder.new(worker_id: :random)
refute builder1.node == builder2.node
end
test "integer" do
- builder = Builder.new(worker_id: 42)
- assert builder.node == 42
+ integer = :rand.uniform(10000)
+ builder = Builder.new(worker_id: integer)
+ assert builder.node == integer
end
test "binary" do
hash = :erlang.phash2("hello")
builder = Builder.new(worker_id: "hello")
assert builder.node == hash
end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 23, 9:07 AM (1 d, 56 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
38930
Default Alt Text
(6 KB)

Event Timeline