Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F112340
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
6 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Nov 23, 9:07 AM (19 h, 18 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
38930
Default Alt Text
(6 KB)
Attached To
Mode
R17 flake_id
Attached
Detach File
Event Timeline
Log In to Comment