On Sun, 10 Nov 2024 17:04:12 GMT, Alan Bateman <al...@openjdk.org> wrote:

>> If we load the custom DefaultFileSystemProvider by SystemClassLoader,then we 
>> will step into the method 
>> SystemModuleFinders$SystemModuleReader#findImageLocation again, and the var 
>> imageReader will be null.But we can not define a custom classLoader to load 
>> the custom DefaultFileSystemProvider since it needs to maintain JDK 8 source 
>> compatibility.So the only way that I can think of is to use the 
>> installedProvider which has the 'file' scheme to directly get the 'modules' 
>> path value.
>> 
>> In ImageReaderFactory:
>> 
>> private static final Path BOOT_MODULES_JIMAGE = null;
>> 
>>     static {
>>         // check installed providers
>>         for (FileSystemProvider provider : 
>> FileSystemProvider.installedProviders()) {
>>             if ("file".equalsIgnoreCase(provider.getScheme())) {
>>                 try {
>>                     BOOT_MODULES_JIMAGE = 
>> provider.getFileSystem(URI.create("file:/")).getPath(JAVA_HOME, "lib", 
>> "modules");
>>                     if (BOOT_MODULES_JIMAGE != null) break;
>>                 } catch (UnsupportedOperationException uoe) {
>>                 }
>>             }
>>         }
>>     }
>> 
>> What do you think of this?I am new to openjdk source development.So I truely 
>> appreciate the time you dedecate to my question!
>
> I assume this will lead to recursive initialisation, e.g. 
> `FileSystems.getFileSystem(URI.create("jrt:/"))` will use 
> FileSystemProvider.installedProviders to iterate over the installed providers.
> 
> I don't have time to look into this more but I think it will have to 
> reflectively get the FileSystem so that the code isn't compiled with 
> references to the built-in provider, as in something like this (not tested):
> 
> 
>     private static final Path BOOT_MODULES_JIMAGE;
>     static {
>         FileSystem fs;
>         if (ImageReaderFactory.class.getClassLoader() == null) {
>             // fs = DefaultFileSystemProvider.theFileSystem()
>             try {
>                 fs = (FileSystem) 
> Class.forName("sun.nio.fs.DefaultFileSystemProvider")
>                         .getMethod("theFileSystem")
>                         .invoke(null);
>             } catch (Exception e) {
>                 throw new ExceptionInInitializerError(e);
>             }
>         } else {
>             fs = FileSystems.getDefault();
>         }
>         BOOT_MODULES_JIMAGE = fs.getPath(JAVA_HOME, "lib", "modules");
>     }
> 
> Also just to say that we need to create a good test for this issue. I expect 
> it will be rarely to interpose on the default file system and use jrtfs at 
> the same time, but that is what this bug report is about so we'll need to 
> create a good test.

#### The reason why it is difficult to solve the issue through modifying 
ImageReadFactory directly

1. We only can use the `java8 `api
2. We can not access any classes which are not exported by `java.base` 
module,even by using java reflection.Because when there is a remote/target jdk, 
the class in jrt-fs.jar loaded by `JrtFsLoader`  is actually in `Unnamed 
Module`.

The essence of this issue is that when loading the Main class, which depends on 
Path.of, then it needs to load CustomFileSystemProvider, resulting in a 
circular dependency, which will access the fields of a class that is still 
being initialized.

The correct situation should be that Main class should be loaded with 
CustomFileSystemProvided, and when it comes to CustomFileSystemProvided it 
should use Built-in FileSystemProvider.

But if we following the above assumptions, and when we **first load the main 
class**, we will run to the `SystemModuleFinders# findImageLocation`  twice 
then the `ImageReader imageReader = SystemImage.reader()` will be null because 
we should load the customFileSystemProvider during main class loading and the 
the both of loading will use the same  `SystemModuleFinders# findImageLocation` 
method.So we must load the CustomFileSystemProvided **before** loading main 
class.

#### The solution

1. In `System#initPhase3` which will be called before loading main class, load 
the CustomFileSystemProvided after `VM.initLevel(4);`.Because we will use 
**SystemClassLoader**.
2. And we need also to ensure the return value of `FileSystems#getDefault` is 
`DefaultFileSystemProvider.theFileSystem`, which depends on the value of 
`VM.isModuleSystemInited()`. But now the `VM.initLevel` is **4** so the value 
of `VM.isModuleSystemInited()` is **false**. At first I want to change the 
judgment condition of `if` . That means I need to create a initLevel after 
`SYSTEM_BOOTED = 4`, but there is already `SYSTEM_SHUTDOWN = 5` defined in VM 
class.I can not set a `4.5` value to a new initLevel,so I add a new  boolen 
field to represent the status of loading CustomFileSystemProvider.Its initValue 
is **false** and its value can only be set from false to true so there is no 
need for lock.
3. Change the judgment condition of `if` to `if 
(VM.isModuleSystemInited()&&VM.isCustomDefaultFileSystemProviderLoaded())`
4. After load the CustomFileSystemProvided,  regardless of whether the  
`-Djava.nio.file.spi.DefaultFileSystemProvider`  was set, it needs to call 
`VM.setCustomDefaultFileSystemProviderLoaded();`

After loading the CustomFileSystemProvided, the initialization of `Path 
BOOT_MODULES_JIMAGE = ImageReaderFactory#Paths.get(JAVA_HOME, "lib", 
"modules");` will use the fileSystem which is provided by the loaded 
CustomFileSystemProvided. Even it will also call 
`FileSystems#getDefaultProvider` , but during where it actually only use the 
constructor to new instance because relevent classes were already loaded.

For detail modification see the latest commit.

I recompile the modified source and test the situation mentioned in the 
issue(actually the issue is submitted by me through oracle bug submission 
because I am not openjdk author) and the normal situation, it successed.

-------------

PR Review Comment: https://git.openjdk.org/jdk/pull/21997#discussion_r1850371330

Reply via email to