Make agent index generation stable irrespective of filesystem iteration order#11889
Make agent index generation stable irrespective of filesystem iteration order#11889bric3 wants to merge 4 commits into
Conversation
… iteration `AgentJarIndex.IndexGenerator` used `Files.walkFileTree` traversal order as stable input. But directory entry order is explicitly unspecified, and in CI, the folder creation in `included/` depends on when the parallel Gradle task are finished. This causes a different index file to be generated, and invalidates the input of the `shadowJar`. Note the index remains correct, however this behavior changes the prefix IDs in the index file. This happens because `Files.walkFileTree` rely under the hood on `UnixFileSystemProvider.newDirectoryStream` which uses `readdir(3)` C api which asks the file system via syscall, in particular we can read: > The order in which filenames are read by successive calls to > readdir() depends on the filesystem implementation; it is unlikely > that the names will be sorted in any fashion. https://www.man7.org/linux/man-pages/man3/readdir.3.html https://man7.org/linux/man-pages/man2/getdents.2.html The tool `find` has the same behavior. https://sources.debian.org/src/findutils/4.10.0-3/gl/lib/fts.c/#L1444
PerfectSlayer
left a comment
There was a problem hiding this comment.
Looking good and left minor comments
| private static Path writeIndex(Path resourcesDir, Path indexFile) throws IOException { | ||
| AgentJarIndex.IndexGenerator generator = new AgentJarIndex.IndexGenerator(resourcesDir); | ||
| generator.buildIndex(); | ||
| generator.writeIndex(indexFile); | ||
| return indexFile; | ||
| } | ||
|
|
||
| private static List<String> readPrefixes(Path indexFile) throws IOException { | ||
| try (DataInputStream in = | ||
| new DataInputStream(new BufferedInputStream(Files.newInputStream(indexFile)))) { | ||
| int prefixCount = in.readInt(); | ||
| String[] prefixes = new String[prefixCount]; | ||
| for (int i = 0; i < prefixCount; i++) { | ||
| prefixes[i] = in.readUTF(); | ||
| } | ||
| return Arrays.asList(prefixes); | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 suggestion: What about reusing buildAndReadIndex instead?
There was a problem hiding this comment.
I introduced writeIndex (returns a Path) / readPrefixes (returns a List) because these tests assert the serialized index, not only the lookup behavior after deserialization which is the goal of buildAndReadIndex (returns a AgentJarIndex) to me.
In particular for prefix ordering readPrefixes is what the test needs.
Originally, normalization was introduced so that the index would be same across OS, but it's not really improtant for windows.
72bab0b to
51b20c7
Compare
This comment has been minimized.
This comment has been minimized.
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
|
By the way the reproducer, it leverages the
|
What Does This Do
Refactors
AgentJarIndex.IndexGeneratorto ensure the index file generation is immune to the unspecified order of filesystem traversal. This resolves inconsistencies in the index file created due to the dependency on traversal order ofFiles.walkFileTree.Motivation
The previous implementation relied on the order in which files were traversed by
Files.walkFileTree, which is inherently unstable (order-wsie) and filesystem-dependent. This led to varying index files, especially in CI environments with parallel Gradle tasks. Although the generated index was correct, the varying prefix IDs impacted reproducibility and the downstreamshadowJar.Additional Notes
This happens because
Files.walkFileTreerely under the hood onUnixFileSystemProvider.newDirectoryStreamwhich usesreaddir(3)std lib C api which asks the file system via syscall, in particular we can read (bold are mine):https://www.man7.org/linux/man-pages/man3/readdir.3.html
https://man7.org/linux/man-pages/man2/getdents.2.html
Note
generateAgentJarIndexis still re-executed because it relies on included jars fromit.inputs.files(includedJarFileTree), the actual folder being:workspace/dd-java-agent/build/generated/includeddd-trace-java/dd-java-agent/build.gradle
Lines 342 to 365 in fc7d48c
However, the rehydrated pipeline cache from gitlab only capture folders in the
.gradlefolder (unerstandably)The
expandAgentShadowJar*tasks filling upincludedAgentDirare Copy like tasks and as such are not cached as well, which made the task not up-to-date.So tHere is another update coming to that task on the build file, PR not yet opened.
See Gradle build cache points (1, 2)
Contributor Checklist
type:and (comp:orinst:) labels in addition to any other useful labelsclose,fix, or any linking keywords when referencing an issueUse
solvesinstead, and assign the PR milestone to the issue/merge. You can also:/merge --commit-message "..."/merge -c/merge -f --reason "reason"; please use this judiciously, as some checks do not run at the PR-level (note: the PR still needs to be mergeable, this will only skip the pre-merge build)Jira ticket: [PROJ-IDENT]