Class InMemoryConstructOutputSink
- All Implemented Interfaces:
ConstructOutputSink
At 1M SLOC the output is 1,255 MB against a 30,752 MB live heap, and holding it measured as no memory cost at all (max RSS moved -0.1%) - the bytes displace the page cache the writes were dirtying. It avoids 254,724 file creations and the 254,724 reads packaging used to do to build the jar from them.
toZipEntries(Path) produces exactly the entry names the file-based path produces, because
both compute outputRoot.toAbsolutePath().relativize(target.toAbsolutePath()).toString()
and hand it to zip.getPath(...) - see Packager.addFileBasedSet. That equivalence
is what lets the change be verified by byte-comparing the resulting jar rather than by
argument.
Keyed on the normalised absolute Path, not on File. File.equals
compares path STRINGS, so new File(dir, "a/B.class") and an equivalent path built any
other way are unequal objects for the same file. Writers and readers here do not construct their
paths identically - ByteCodeDirectiveListener rebuilds the path from the symbol's FQN -
so a File key would make read(File) silently miss.
-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionintcount()intflushTo(ConstructOutputSink fileSink) Write everything this sink is holding out to the files it would have occupied.Optional<byte[]> Read back a class this sink was given, if it has it.toZipEntries(Path outputRoot) The generated classes that live UNDER the given output root, as zip entries named relative to it.voidAccept one generated class.
-
Constructor Details
-
InMemoryConstructOutputSink
public InMemoryConstructOutputSink()
-
-
Method Details
-
write
Description copied from interface:ConstructOutputSinkAccept one generated class.- Specified by:
writein interfaceConstructOutputSink- Parameters:
target- the file this class WOULD occupy. It is the identity of the output, not necessarily a path that gets written: a jar sink relativises it to an entry name, an in-memory sink uses it as a key. Keeping it aFilemeans the naming stays identical to the pre-sink pipeline, which is what makes byte-identical output verifiable.content- the class bytes.
-
read
Description copied from interface:ConstructOutputSinkRead back a class this sink was given, if it has it.This exists because the sink is the single source of generated bytes, and something inside the compile needs to read them back:
ByteCodeDirectiveListenervalidates@BYTECODEdirectives during compilation and used to go straight to the filesystem. That is correct only while the bytes are on the filesystem, so it would fail - loudly, but for a confusing reason - the moment a caller chose a sink that holds them. Asking the sink removes the question rather than documenting it.- Specified by:
readin interfaceConstructOutputSink- Parameters:
target- the same file identity that was passed toConstructOutputSink.write(File, byte[]).- Returns:
- the bytes, or empty if this sink does not have them.
-
toZipEntries
The generated classes that live UNDER the given output root, as zip entries named relative to it.The filter is not incidental. One sink serves a whole compilation, and a dev build writes into two roots -
generated/main/jvmandgenerated/dev/jvm- which the packager adds as two separateZipSets. Relativising every entry against whichever root was asked for would name the other root's classes../dev/..., quietly producing a jar with entries outside its own tree.- Parameters:
outputRoot- the directory the file-based pipeline would have written into.
-
flushTo
Write everything this sink is holding out to the files it would have occupied.For the paths that genuinely need the exploded output rather than the jar - the
-ttest runner readsgenerated/dev/jvmdirectly - the bytes still have to land on disk. Holding them and flushing once keeps the batch path free of the 254,724 writes while leaving those consumers working.- Parameters:
fileSink- the sink that actually writes - normally aFileConstructOutputSink.- Returns:
- how many files were written.
-
count
public int count()
-