Skip to content

Commit 1ab1b0a

Browse files
fix: add sort before hashing attributes (#51)
Closes #48
1 parent 88465a9 commit 1ab1b0a

File tree

2 files changed

+39
-2
lines changed

2 files changed

+39
-2
lines changed

src/attributes.zig

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,29 @@ pub const Attributes = struct {
118118
}
119119

120120
pub const HashContext = struct {
121-
pub fn hash(_: HashContext, self: Self) u64 {
121+
fn compareAttributes(context: void, a: Attribute, b: Attribute) bool {
122+
_ = context;
123+
return std.mem.lessThan(u8, a.key, b.key);
124+
}
125+
126+
const MAX_ATTRS = 128;
127+
128+
pub fn hash(_: HashContext, self: Attributes) u64 {
122129
var h = std.hash.Wyhash.init(0);
123130
const attrs = self.attributes orelse &[_]Attribute{};
124-
for (attrs) |attr| {
131+
132+
// Enforce soft limit: only hash up to MAX_ATTRS attributes
133+
const count = @min(attrs.len, MAX_ATTRS);
134+
135+
var buffer: [MAX_ATTRS]Attribute = undefined;
136+
const to_sort = buffer[0..count];
137+
@memcpy(to_sort, attrs[0..count]);
138+
139+
if (to_sort.len > 1) {
140+
std.mem.sort(Attribute, to_sort, {}, compareAttributes);
141+
}
142+
143+
for (to_sort) |attr| {
125144
h.update(attr.key);
126145
h.update(attr.value.toStringNoAlloc());
127146
}

src/scope.zig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,21 @@ test "InstrumentationScope should equal correctly" {
9191

9292
try std.testing.expect(hashContext.eql(first, second));
9393
}
94+
95+
test "InstrumentationScope hash should be consistent regardless of attribute order" {
96+
const allocator = std.testing.allocator;
97+
98+
const attrs1 = try Attributes.from(allocator, .{ "key1", @as(u64, 42), "key2", true, "key3", @as([]const u8, "value3") });
99+
defer allocator.free(attrs1.?);
100+
101+
const attrs2 = try Attributes.from(allocator, .{ "key3", @as([]const u8, "value3"), "key1", @as(u64, 42), "key2", true });
102+
defer allocator.free(attrs2.?);
103+
104+
const scope1: InstrumentationScope = .{ .name = "testScope", .attributes = attrs1 };
105+
const scope2: InstrumentationScope = .{ .name = "testScope", .attributes = attrs2 };
106+
107+
const hashContext = InstrumentationScope.HashContext{};
108+
109+
try std.testing.expectEqual(hashContext.hash(scope1), hashContext.hash(scope2));
110+
try std.testing.expect(hashContext.eql(scope1, scope2));
111+
}

0 commit comments

Comments
 (0)