Skip to content

Commit c89fad9

Browse files
dougqhclaude
andcommitted
Add Entry concurrency tests for tag-id lazy name/hash resolution
Tag-id-constructed entries resolve their name lazily from the tagId via KnownTags on first tag()/getKey(), caching into the non-volatile `tag` field — a benign race. Run tag-id entries (Object/int/boolean) plus matches() through the existing shuffled multi-threaded harness so 4 threads resolve concurrently; assert all agree on the same interned constant and that hash() equals the tagId's nameHash. Also stress a string entry's lazy hash() now that it writes into the low 32 bits of `tagId` (formerly a separate int lazyTagHash). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 027404f commit c89fad9

1 file changed

Lines changed: 125 additions & 0 deletions

File tree

internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import java.util.concurrent.ThreadFactory;
2121
import java.util.function.Function;
2222
import java.util.function.Supplier;
23+
import org.junit.jupiter.api.AfterAll;
24+
import org.junit.jupiter.api.BeforeAll;
2325
import org.junit.jupiter.api.DisplayName;
2426
import org.junit.jupiter.api.Test;
2527
import org.junit.jupiter.params.ParameterizedTest;
@@ -550,6 +552,129 @@ public void removalChange() {
550552
assertTrue(removalChange.isRemoval());
551553
}
552554

555+
// ---------------------------------------------------------------------------------------------
556+
// Tag-id-constructed entries: the name is resolved lazily from the tagId via KnownTags on first
557+
// tag()/getKey(). That resolution (and the cache write to the volatile-free `tag` field) is a
558+
// benign race — these tests run tag-id entries through the existing multi-threaded harness so 4
559+
// threads resolve the name concurrently; all must agree, and on the same interned constant.
560+
// ---------------------------------------------------------------------------------------------
561+
562+
static final String[] TAG_NAMES = {"tag.alpha", "tag.beta", "tag.gamma"};
563+
564+
static long tagId(int serial, int fieldPos, String name) {
565+
long nameHash = TagMap.Entry._hash(name) & 0xFFFFFFFFL;
566+
return ((long) serial << 48) | ((long) fieldPos << 32) | nameHash;
567+
}
568+
569+
@BeforeAll
570+
static void registerResolver() {
571+
KnownTags.register(
572+
new KnownTags.Resolver() {
573+
@Override
574+
public String nameOf(long tagId) {
575+
int serial = (int) (tagId >>> 48);
576+
// returns the same interned constant each call, so racing resolutions agree by identity
577+
return (serial >= 1 && serial <= TAG_NAMES.length) ? TAG_NAMES[serial - 1] : null;
578+
}
579+
580+
@Override
581+
public long keyOf(String name) {
582+
for (int i = 0; i < TAG_NAMES.length; ++i) {
583+
if (TAG_NAMES[i].equals(name)) return tagId(i + 1, i, TAG_NAMES[i]);
584+
}
585+
return 0L;
586+
}
587+
});
588+
}
589+
590+
@AfterAll
591+
static void clearResolver() {
592+
KnownTags.register(null);
593+
}
594+
595+
// resolved name must be the exact interned constant, and hash() must equal the tagId's low 32
596+
// bits (nameHash) — both stressed concurrently by the shared-entry multi-threaded harness.
597+
static Check checkResolvedTagId(long id, String name, TagMap.Entry entry) {
598+
return multiCheck(
599+
checkKey(name, entry),
600+
checkTrue(() -> entry.tag() == name, "tag() returns interned constant"),
601+
checkEquals((int) (id & 0xFFFFFFFFL), () -> entry.hash(), "Entry::hash == nameHash"));
602+
}
603+
604+
@Test
605+
@DisplayName("tag-id entry: Object resolves name lazily under race")
606+
public void tagIdEntryObject() {
607+
long id = tagId(1, 0, TAG_NAMES[0]);
608+
test(
609+
() -> TagMap.Entry.newAnyEntry(id, "bar"),
610+
TagMap.Entry.ANY,
611+
(entry) ->
612+
multiCheck(
613+
checkResolvedTagId(id, TAG_NAMES[0], entry),
614+
checkValue("bar", entry),
615+
checkTrue(entry::isObject),
616+
checkType(TagMap.Entry.OBJECT, entry)));
617+
}
618+
619+
@Test
620+
@DisplayName("tag-id entry: int resolves name lazily under race")
621+
public void tagIdEntryInt() {
622+
long id = tagId(2, 1, TAG_NAMES[1]);
623+
test(
624+
() -> TagMap.Entry.newIntEntry(id, 42),
625+
TagMap.Entry.INT,
626+
(entry) ->
627+
multiCheck(
628+
checkResolvedTagId(id, TAG_NAMES[1], entry),
629+
checkValue(42, entry),
630+
checkIsNumericPrimitive(entry),
631+
checkType(TagMap.Entry.INT, entry)));
632+
}
633+
634+
@Test
635+
@DisplayName("tag-id entry: boolean resolves name lazily under race")
636+
public void tagIdEntryBoolean() {
637+
long id = tagId(3, 2, TAG_NAMES[2]);
638+
test(
639+
() -> TagMap.Entry.newBooleanEntry(id, true),
640+
TagMap.Entry.BOOLEAN,
641+
(entry) ->
642+
multiCheck(
643+
checkResolvedTagId(id, TAG_NAMES[2], entry),
644+
checkValue(true, entry),
645+
checkType(TagMap.Entry.BOOLEAN, entry)));
646+
}
647+
648+
@Test
649+
@DisplayName("string entry: lazy hash() under race")
650+
public void stringEntryLazyHash() {
651+
// string-constructed entry computes hash() lazily, writing into the low 32 bits of the `tagId`
652+
// field (formerly a separate `int lazyTagHash`). Stress concurrent first-resolution.
653+
String name = "some.unknown.tag.name";
654+
test(
655+
() -> TagMap.Entry.newObjectEntry(name, "v"),
656+
TagMap.Entry.OBJECT,
657+
(entry) ->
658+
multiCheck(
659+
checkEquals(TagMap.Entry._hash(name), () -> entry.hash(), "lazy hash()"),
660+
checkKey(name, entry),
661+
checkValue("v", entry)));
662+
}
663+
664+
@Test
665+
@DisplayName("tag-id entry: matches() resolves the name under race")
666+
public void tagIdEntryMatches() {
667+
long id = tagId(1, 0, TAG_NAMES[0]);
668+
test(
669+
() -> TagMap.Entry.newObjectEntry(id, "bar"),
670+
TagMap.Entry.OBJECT,
671+
(entry) ->
672+
multiCheck(
673+
checkTrue(() -> entry.matches(TAG_NAMES[0]), "matches(name)"),
674+
checkFalse(() -> entry.matches("nope"), "!matches(other)"),
675+
checkKey(TAG_NAMES[0], entry)));
676+
}
677+
553678
static final int NUM_THREADS = 4;
554679
static final ExecutorService EXECUTOR =
555680
Executors.newFixedThreadPool(

0 commit comments

Comments
 (0)