-
Notifications
You must be signed in to change notification settings - Fork 126
8368333: [lworld] Add preview mode to ImageReader and JRT file-system #1619
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: pr/1618
Are you sure you want to change the base?
Conversation
This is a new version of #1613, re-created so there's now a pr/xxx branch for use by dependent PRs. |
👋 Welcome back david-beaumont! A progress list of the required criteria for merging this PR into |
@david-beaumont This change is no longer ready for integration - check the PR body for details. |
Webrevs
|
/** | ||
* Set on all locations in the {@code /modules/xxx/META-INF/preview/...} | ||
* namespace. | ||
* | ||
* <p>This flag is mutually exclusive with {@link #FLAGS_HAS_PREVIEW_VERSION}. | ||
*/ | ||
public static final int FLAGS_IS_PREVIEW_VERSION = 0x2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems to be redundant with HAS_PREVIEW_VERSION when seen in a preview location.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, but HAS_PREVIEW_VERSION is never set on a preview location. It's only for non-preview locations to indicate that a (different) preview version exists.
HAS_PREVIEW_VERSION can also be set for /packages/xxx directory entries (where it means "some of the entries are modules in which preview versions of resources exist" rather than "there's a META-INF/preview version of this entry"). IS_PREVIEW_VERSION is kind of meaningless for the /packages/xxx directories though, and is never set on them.
I'm not sure I'm a fan of trying to merge the semantics of these into some sort of "HAS_OR_IS_PREVIEW_VERSION" flag though. We're not short on flag space (and are extremely unlikely to ever be).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see the semantics of the flag as there-is-a-preview-version
of the resource.
Where-ever it appears, it means that the resource/class is in the preview branch of the hierarchy.
If the flag appears in the normal hierarchy index, it still means the same, look in the preview hierarchy.
If it appears on a directory, it means there preview resources in/below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no "the resource/class" in the jimage file, because both can be present. It depends on whether the path to the entry contains META-INF/preview or not. So where a normal resource has a preview version, the same flag would be set on both.
For example, I don't see how this works if the flags have the same value.
// Regardless of preview mode, don't return resources requested directly
// via their preview path.
if ((flags & ImageLocation::FLAGS_IS_PREVIEW_VERSION) != 0) {
return 0L;
}
// Even if there is a preview version, we might not want to return it.
if (!is_preview_mode || (flags & ImageLocation::FLAGS_HAS_PREVIEW_VERSION) == 0) {
return locOffset;
}
/** | ||
* Indicates that a location only exists due to preview resources. | ||
* | ||
* <p>This can apply to both resources and directories in the | ||
* {@code /modules/xxx/...} namespace, as well as {@code /packages/xxx} | ||
* directories. | ||
* | ||
* <p>For {@code /packages/xxx} directories it indicates that, for every | ||
* module in which the package exists, it is preview only. | ||
* | ||
* <p>This flag is mutually exclusive with {@link #FLAGS_HAS_PREVIEW_VERSION} | ||
* and need not imply that {@link #FLAGS_IS_PREVIEW_VERSION} is set (i.e. | ||
* for {@code /packages/xxx} directories). | ||
*/ | ||
public static final int FLAGS_IS_PREVIEW_ONLY = 0x4; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The preview-only case is expected to be the empty set. There seems to be a lot of code dedicated to it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well it's a small set, but vitally important. Preview only is the case where you can't lookup the normal resource first and check its flags to see if there's a preview version. Since you must correct find all preview-only resources, you are left with either:
- always speculatively looking up all resources in their preview location when not found otherwise.
- Doing something "cleverer" to reduce double lookups.
In the C++ class-loader (as I understand it) most lookups will be for things referenced in other class files, and thus expected to exist. This makes "if you can't find the normal version, look for the preview version" acceptable (I think) because this should be only in cases where you're looking for something that should exist at one of the two paths.
In the Java stuff, there's a lot of speculative lookup, especially for non-class resources. All modules are tested for any non-existent resource. So in this case the strategy needs to be cleverer or you'll basically (at least) double the cost of a lot of things.
So I settled on the easiest approach to be to just (efficiently) pre-scan for preview resources and put them in the node cache during init (this is only about the same work the old code did for link nodes etc.). Once they're cached, the lookup code no longer has to care about 2nd lookups or anything. Preview versions and preview-only versions are just treated the same, they're in the cache, so they're found and returned without looking at anything else.
This is partly why there's the "ensureCached" method which guarantees you never replace what's in the cache with anything else (so preview stuff put there during init of the reader is "safe" from accidental overwriting).
The new flag stuff and change of ordering of package entries is there mostly to support efficient pre-scanning of the small set of preview only resources.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still going to say, there are no plans to have any Preview-only resources.
And yes, a preview-only flag would be useful to (mostly) avoid a second lookup.
If I understand the design, there is still a location in the normal tree with the preview-only flag but there is no resource at that location, only the flags. And a second lookup is needed to find the location in the preview heirarchy. The caching avoids repeated 2nd lookups.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any new lambda or inner-class in a preview class is a new preview-only resource. It's definitely going to happen.
Additionally, if an entry is preview only, there's simply nothing in the jimage at the non-preview location. No flags, no nothing.
* Helper function to calculate preview flags (ATTRIBUTE_PREVIEW_FLAGS). | ||
* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A summary of what gets what flags might be easier to read than the code.
Or list the flags and refer to their descriptions (that would describe the paths where the flags would be found)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in latest snapshot.
* Indicates that a non-preview location is associated with preview | ||
* resources. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment would read better saying that preview resources exist related to this location.
More like FLAGS_HAS_PREVIEW_VERSION.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, but it's deliberately not worded like that because this flag can be set for "/packages/xxx" entries, for which "preview resources exist related to this location" is not a meaningful statement.
This is where you could add another flag (e.g. "FLAGS_PACKAGE_HAS_PREVIEW_ENTRIES"), but it felt better to use the same flag for this, since where it's calculated becomes a neat case of just propagating the flag (next PR):
// Calculate the package's ImageLocation flags.
boolean hasPreviewVersion =
moduleReferences.stream().anyMatch(ModuleReference::hasPreviewVersion);
boolean isPreviewOnly =
moduleReferences.stream().allMatch(ModuleReference::isPreviewOnly);
return (hasPreviewVersion ? ImageLocation.FLAGS_HAS_PREVIEW_VERSION : 0)
| (isPreviewOnly ? ImageLocation.FLAGS_IS_PREVIEW_ONLY : 0);
And the flag/method names in this code just match across this logic.
src/java.base/share/classes/jdk/internal/jimage/ImageReader.java
Outdated
Show resolved
Hide resolved
src/java.base/share/classes/jdk/internal/jimage/ImageReader.java
Outdated
Show resolved
Hide resolved
src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystemProvider.java
Outdated
Show resolved
Hide resolved
src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java
Outdated
Show resolved
Hide resolved
* Renaming slightly confusing "testEncoder" method. * Fixing unit tests to use new constructor.
* Removing package root flag based on feedback. * Changing existing package flags during writing to match altered flag values. * Feedback changes, and fixing some comments. * Renaming slightly confusing "testEncoder" method. * Fixing unit tests to use new constructor. * Word smithing flags definitions. * Add workaround until new image writing code is in * Clarifying flag docs for /packages/xxx case * Java ImageReader changes for preview mode
@@ -1,5 +1,5 @@ | |||
/* | |||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restoring the copyright year accidentally removed in the last change.
/** | ||
* Returns whether the package associated with this reference contains | ||
* resources in this reference's module. | ||
* | ||
* <p>An invariant of the module system is that while a package may exist | ||
* under many modules, it is only non-empty in one. | ||
*/ | ||
public boolean hasContent() { | ||
return ((flags & FLAGS_HAS_CONTENT) != 0); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is the presumed hierarchy of package non-emptyness used?
} | ||
|
||
/** Returns whether this reference exists only in preview mode. */ | ||
public boolean isPreviewOnly() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pick either PreviewOnly or hasNormal and use consistently. (Even if one is the opposite of the other).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are NOT opposites. It is not possible to use only one or these flags and continue to be logically correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, the implementation of isPreviewOnly()
is exactly !hasNormalVersion(flags)
// TODO: Support preview mode in ExplodedImage and remove this check. | ||
if (mode.isPreviewModeEnabled()) | ||
throw new UnsupportedOperationException( | ||
"Preview mode not yet supported for exploded images"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd remove "yet", it implies some planned work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well I do plan to do it.
public static final int ATTRIBUTE_COMPRESSED = 6; | ||
public static final int ATTRIBUTE_UNCOMPRESSED = 7; | ||
public static final int ATTRIBUTE_COUNT = 8; | ||
public static final int ATTRIBUTE_PREVIEW_FLAGS = 8; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment above these constants that the values are defined in ImageFile.hpp.
/** If set, this package exists in preview mode. */ | ||
private static final int FLAGS_HAS_PREVIEW_VERSION = 0x1; | ||
/** If set, this package exists in non-preview mode. */ | ||
private static final int FLAGS_HAS_NORMAL_VERSION = 0x2; | ||
/** If set, the associated module has resources (in normal or preview mode). */ | ||
// TODO: Make this private again when image writer code is updated. | ||
public static final int FLAGS_HAS_CONTENT = 0x4; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please change the prefix of these to distinguish them from the ImageLocation flags.
For example, "PKG_" or "MR_".
public static final int MAJOR_VERSION = 1; | ||
public static final int MINOR_VERSION = 0; | ||
public static final int MINOR_VERSION = 1; | ||
private static final int HEADER_SLOTS = 7; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment before these constants connecting them to imageFile.hpp.
public interface ImageStrings { | ||
// String offset constants are useful for efficient classification | ||
// of location entries without string comparison. These may change | ||
// between jimage versions (they are checked during initialization). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The checking seem only to be done when ImageStringsWriter is loaded/initialized.
There may be a small risk that a mismatch of the image with the ImageStringsReader could occur.
Can they be checked also by the reader?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I don't see a nice way to do this at runtime since you can't lookup string offsets by their value (as far as I can see).
It's no more fragile than the old code (which had hard-coded constants for "class" and "" (and wasn't self-checking their validity). At least now it's tested at build time.
If the version number matches it should be safe. I'll add a comment about how changing existing entries is problematic.
* <p>An invariant of the module system is that while a package may exist | ||
* under many modules, it only has content in one. | ||
*/ | ||
public boolean hasContent() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As suggested, hasResoruces
would be consistent with the lookups for resources.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
// A relativized mapping from non-preview name to directories containing | ||
// preview-only nodes. This is used to add preview-only content to | ||
// directories as they are completed. | ||
private final HashMap<String, Directory> previewDirectoriesToMerge; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be cleared or freed after the ImageReader is open. Its no longer needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's used when directories are completed (as stated in the comment):
List<Node> previewOnlyNodes = getPreviewNodesToMerge(dir);
...
children.addAll(previewOnlyNodes);
This is so that a preview only directory (and all its content) appears correctly in its parent's child list.
} | ||
ImageLocation loc = findLocation(moduleName, resourcePath); | ||
return loc != null && isResource(loc); | ||
return loc != null && loc.getType() == RESOURCE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would there be any benefit to caching the resource here, since it has been found and will likely be opened again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very unlikely to be worth it, and outside the scope of this work (since nothing I'm doing changes this behaviour).
ImageReader deals with small nodes and has a surprisingly low footprint. Caching content would require managing a size bounded cache, since it's well over 100MB of content.
Java changes for supporting preview mode when preview mode resources (with new location flags) are available.
At the moment, this code will operate on non-preview jimage files (1.0) and act as if no preview resources are available by virtue of the default value for missing attributes and package flags being zero (which matches jimage 1.0).
This should be reviewed on top of #1618
Progress
Integration blocker
Issue
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/valhalla.git pull/1619/head:pull/1619
$ git checkout pull/1619
Update a local copy of the PR:
$ git checkout pull/1619
$ git pull https://git.openjdk.org/valhalla.git pull/1619/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 1619
View PR using the GUI difftool:
$ git pr show -t 1619
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/valhalla/pull/1619.diff
Using Webrev
Link to Webrev Comment