Skip to content

Commit cfd5972

Browse files
authored
Log::Metadata should put parent entries first on extend (like Hash#merge) (#16098)
`Log::Metadata` wrote its entries in the beginning of the allocated memory for all entries. When extending, parent entries were prepended to the memory. With this change, entries are written to the end of allocated memory to make it possible to write parent entries first when extending the `Metadata` instance. `#defrag` moves the local entries to the end of the parent entries or overwrites any existing parent entry for the current key.
1 parent 340f024 commit cfd5972

File tree

2 files changed

+48
-25
lines changed

2 files changed

+48
-25
lines changed

spec/std/log/metadata_spec.cr

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ describe Log::Metadata do
6363

6464
it "json" do
6565
m({a: 1}).to_json.should eq(%({"a":1}))
66-
m({a: 1, b: 1}).extend({b: 2}).to_json.should eq(%({"b":2,"a":1}))
66+
m({a: 1, b: 1}).extend({b: 2}).to_json.should eq(%({"a":1,"b":2}))
6767
end
6868

6969
it "defrags" do
@@ -72,17 +72,37 @@ describe Log::Metadata do
7272

7373
md.@size.should eq(1)
7474
md.@max_total_size.should eq(4)
75-
md.@overridden_size.should eq(1)
7675
md.@parent.should be(parent)
7776

7877
md.should eq(m({a: 3, b: 2}))
7978

8079
md.@size.should eq(2)
8180
md.@max_total_size.should eq(2)
82-
md.@overridden_size.should eq(1)
8381
md.@parent.should be_nil
8482
end
8583

84+
it "defrags deep" do
85+
grandparent = m({x: 100, y: 200, z: 300})
86+
parent = grandparent.extend({x: 101, a: 1, b: 2})
87+
md = parent.extend({x: 102, b: 22, c: 3})
88+
89+
md.should eq(m({x: 102, y: 200, z: 300, a: 1, b: 22, c: 3}))
90+
end
91+
92+
it "[] find parent entry after defrag" do
93+
grandparent = m({x: 100, y: 200, z: 300})
94+
parent = grandparent.extend({x: 101, a: 1, b: 2})
95+
md = parent.extend({x: 102, b: 22, c: 3})
96+
97+
_ = md.to_a
98+
99+
md[:y].should eq(200)
100+
md[:z].should eq(300)
101+
md[:x].should eq(102)
102+
md[:a].should eq(1)
103+
md[:b].should eq(22)
104+
end
105+
86106
it "[]" do
87107
md = m({a: 1, b: 2}).extend({a: 3})
88108

src/log/metadata.cr

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ class Log::Metadata
2020
# When the metadata is defragmented max_total_size will be updated with size
2121
protected getter max_total_size : Int32
2222
@max_total_size = uninitialized Int32
23-
# How many entries are potentially overridden from parent (ie: initial entries.size)
24-
@overridden_size = uninitialized Int32
2523
# How many entries are stored from @first.
26-
# Initially are @overridden_size, the one explicitly overridden in entries argument.
2724
# When the metadata is defragmented @size will be increased up to
2825
# the actual number of entries resulting from merging the parent
2926
@size = uninitialized Int32
27+
# Number of parent elements we've copied on defrag. Used to iterate parent
28+
# entries first in #each.
29+
@parent_size = uninitialized Int32
30+
3031
# @first needs to be the last ivar of Metadata. The entries are allocated together with self
3132
@first = uninitialized Entry
3233

@@ -42,7 +43,8 @@ class Log::Metadata
4243
end
4344

4445
protected def setup(@parent : Metadata?, entries : NamedTuple | Hash)
45-
@size = @overridden_size = entries.size
46+
@size = entries.size
47+
@parent_size = 0
4648
@max_total_size = @size + (@parent.try(&.max_total_size) || 0)
4749
ptr_entries = pointerof(@first)
4850

@@ -91,43 +93,44 @@ class Log::Metadata
9193
# will be recomputed, but the result should be the same.
9294
#
9395
# * @parent.nil? signals if the defrag is needed/done
94-
# * The values of @overridden_size, pointerof(@first) are never changed
96+
# * The value of pointerof(@first) never changes
9597
# * @parent is set at the very end of the method
9698
protected def defrag
9799
parent = @parent
98100
return if parent.nil?
99101

100-
total_size = @overridden_size
101102
ptr_entries = pointerof(@first)
102-
next_free_entry = ptr_entries + @overridden_size
103+
next_free_entry = ptr_entries + @size
104+
total_size = @size
103105

106+
# Copy parent entries that ain't overwritten
107+
parent_size = 0
104108
parent.each do |(key, value)|
105-
overridden = false
106-
@overridden_size.times do |i|
107-
if ptr_entries[i][:key] == key
108-
overridden = true
109-
break
110-
end
111-
end
112-
113-
unless overridden
114-
next_free_entry.value = {key: key, value: value}
115-
next_free_entry += 1
116-
total_size += 1
117-
end
109+
overwritten = Slice.new(ptr_entries, @size).any? { |entry| entry[:key] == key }
110+
next if overwritten
111+
next_free_entry.value = {key: key, value: value}
112+
parent_size += 1
113+
next_free_entry += 1
114+
total_size += 1
118115
end
119116

120117
@size = total_size
121118
@max_total_size = total_size
119+
@parent_size = parent_size
122120
@parent = nil
123121
end
124122

125123
def each(& : {Symbol, Value} ->)
126124
defrag
127125
ptr_entries = pointerof(@first)
126+
parent_size = @parent_size
127+
local_size = @size - parent_size
128128

129-
@size.times do |i|
130-
entry = ptr_entries[i]
129+
Slice.new(ptr_entries + local_size, parent_size).each do |entry|
130+
yield({entry[:key], entry[:value]})
131+
end
132+
133+
Slice.new(ptr_entries, local_size).each do |entry|
131134
yield({entry[:key], entry[:value]})
132135
end
133136
end

0 commit comments

Comments
 (0)