Skip to content

Commit 75a1147

Browse files
committed
Added a couple of class methods and specs to generate guaranteed unique CSUUIDs even if they are produced in the same nanosecond of execution.
1 parent cabc95a commit 75a1147

File tree

2 files changed

+57
-12
lines changed

2 files changed

+57
-12
lines changed

spec/csuuid_spec.cr

+11
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,15 @@ describe CSUUID do
7575
ParseDate.parse("2020/07/29 09:15:37").not_nil!.in(Time::Location.local)
7676
)
7777
end
78+
79+
it "can produce two unique CSUUIDs consecutively" do
80+
uuid1 = CSUUID.unique
81+
uuid2 = CSUUID.unique
82+
uuid1.should_not eq uuid2
83+
end
84+
85+
it "can generate a long sequence of unique CSUUIDs" do
86+
uuids = CSUUID.generate(10000)
87+
uuids.uniq.size.should eq uuids.size
88+
end
7889
end

src/csuuid.cr

+46-12
Original file line numberDiff line numberDiff line change
@@ -40,23 +40,57 @@ require "crystal/spin_lock"
4040
# ```
4141
#
4242
struct CSUUID
43-
VERSION = "0.1.1"
43+
VERSION = "0.2.0"
4444

4545
@@mutex = Crystal::SpinLock.new
4646
# @@mutex = Mutex.new(protection: Mutex::Protection::Reentrant)
4747
@@prng = Random::ISAAC.new
4848
@@string_matcher = /^(........)-(....)-(....)-(....)-(............)/
49-
# :nodoc:
50-
class_property identifier
51-
# :nodoc:
52-
class_property extra
49+
@@unique_identifier : Slice(UInt8) = Slice(UInt8).new(6, 0)
50+
@@unique_seconds_and_nanoseconds : Tuple(Int64, Int32) = {0_i64, 0_i32}
5351

5452
@bytes : Slice(UInt8) = Slice(UInt8).new(16)
5553
@seconds_and_nanoseconds : Tuple(Int64, Int32)?
5654
@timestamp : Time?
5755
@utc : Time?
5856
@location : Time::Location = Time::Location.local
5957

58+
def self.unique
59+
@@mutex.sync do
60+
t = Time.local
61+
if t.internal_nanoseconds == @@unique_seconds_and_nanoseconds[1] &&
62+
t.internal_seconds == @@unique_seconds_and_nanoseconds[0]
63+
increment_unique_identifier
64+
else
65+
@@unique_seconds_and_nanoseconds = {t.internal_seconds, t.internal_nanoseconds}
66+
@@unique_identifier = @@prng.random_bytes(6)
67+
end
68+
69+
new(
70+
@@unique_seconds_and_nanoseconds[0],
71+
@@unique_seconds_and_nanoseconds[1],
72+
@@unique_identifier
73+
)
74+
end
75+
end
76+
77+
def self.generate(count)
78+
result = [] of CSUUID
79+
count.times {result << unique}
80+
81+
result
82+
end
83+
84+
# :nodoc:
85+
def self.increment_unique_identifier
86+
5.downto(0) do |position|
87+
new_byte_value = @@unique_identifier[position] &+= 1
88+
break unless new_byte_value == 0
89+
end
90+
91+
@@unique_identifier
92+
end
93+
6094
def initialize(uuid : String)
6195
@bytes = uuid.tr("-", "").hexbytes
6296
end
@@ -81,13 +115,13 @@ struct CSUUID
81115

82116
private def initialize_impl(seconds : Int64, nanoseconds : Int32, identifier : Slice(UInt8) | String | Nil)
83117
id = if identifier.is_a?(String)
84-
buf = Slice(UInt8).new(6)
85-
number_of_bytes = identifier.size < 6 ? identifier.size : 6
86-
buf[0, number_of_bytes].copy_from(identifier.hexbytes[0, number_of_bytes])
87-
buf
88-
else
89-
identifier
90-
end
118+
buf = Slice(UInt8).new(6)
119+
number_of_bytes = identifier.size < 6 ? identifier.size : 6
120+
buf[0, number_of_bytes].copy_from(identifier.hexbytes[0, number_of_bytes])
121+
buf
122+
else
123+
identifier
124+
end
91125

92126
IO::ByteFormat::BigEndian.encode(seconds, @bytes[2, 8])
93127
IO::ByteFormat::BigEndian.encode(nanoseconds, @bytes[0, 4])

0 commit comments

Comments
 (0)