Skip to content
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

ClassLoader issues with Jimfs.newFileSystem #18

Closed
cgdecker opened this issue Feb 20, 2015 · 10 comments
Closed

ClassLoader issues with Jimfs.newFileSystem #18

cgdecker opened this issue Feb 20, 2015 · 10 comments
Assignees
Labels
fixed type=defect Bug, not working as expected
Milestone

Comments

@cgdecker
Copy link
Member

It looks like there are problems using Jimfs in an environment where user and system code are loaded by separate classloaders. In that case, JimfsFileSystemProvider will be loaded and initialized by the system classloader (because of the META-INF/services entry), while a user who wants to create a file system by calling Jimfs.newFileSystem will be using classes loaded by a different classloader.

The problem is that to create a FileSystem instance, Jimfs calls FileSystems.newFileSystem(...), passing it an environment map containing the Configuration object to be used to configure the file system. When the JimfsFileSystemProvider instance loaded by the system classloader gets this object, it doesn't recognize it as an instance of Configuration, because it was loaded by a different classloader. This leads to an error like:

env map ({config=com.google.common.jimfs.Configuration@15b71125}) must contain key 'config' mapped to an instance of Jimfs.Configuration

Why do we do it this way in the first place? We want to call FileSystems.newFileSystem so that the canonical (system-loaded) JimfsFileSystemProvider instance is used to create (and more importantly, cache) the file system. This is necessary for methods like Paths.get(URI) to work, as it will go to that FileSystemProvider instance and ask it to resolve the URI to a Path. If it doesn't have the FileSystem instance cached, it won't be able to find it and get a Path from it.

Some possible solutions (and the problems with them):

  1. Change the environment map that's passed to FileSystems.newFileSystem to only contain standard JDK types. This should solve the classloader issues.
    • Problem: While most of the Configuration object could be converted to a map of standard JDK types, there are a couple things that cannot, specifically the PathType and the set of AttributeProviders the file system should use. It's possible (though it would require changing the API) that we could change the configuration from storing instances of these types to storing only the classes (requiring a default constructor or some such), in which case we could put the names of the classes into the map to be loaded and instantiated by the FileSystemProvider.
    • Even if we did this, though, there are still potential problems if, say, an AttributeProvider deals with attributes whose values are not standard JDK classes--the could be problems when a user tries to set an attribute value on a file and the AttributeProvider doesn't see it as the same class (due to the classloader issues).
  2. Stop using FileSystems.newFileSystem to create Jimfs file systems. Use a locally initialized instance of JimfsFileSystemProvider instead. This solves the problem because only the user code classloader should be involved.
    • Problem: It's no longer possible to get a Jimfs Path or FileSystem by its URI using the standard methods in the java.nio.file API. This also prevents things like the recently-added support for Jimfs URLs from working.
  3. Try to work around this by making the system-loaded FileSystemProvider for Jimfs not be the real JimfsFileSystemProvider.
    • Rather, it would just be used for caching FileSystem instances as needed for URI-based methods to work.
    • The real JimfsFileSystemProvider would be a singleton, and Jimfs would use that to create a FileSystem instance when Jimfs.newFileSystem is called. It would then pass the created FileSystem itself as a value in the environment map to FileSystems.newFileSystem, allowing the new FileSystem to be cached. Since FileSystem is a JDK type and since the system-loaded FileSystemProvider has no reason to care about any details beyond that, it should have no problem with the FileSystem coming from a different classloader.
    • Since the FileSystemProvider that created the FileSystem will be the one that handles implementing all the methods for it, there should be no issues with the FileSystem functionality.

I think approach 3 should work, but still need to confirm that. It's fairly gross, but should be transparent as long as people are using the API as expected.

@cgdecker cgdecker added the type=defect Bug, not working as expected label Feb 20, 2015
@cgdecker
Copy link
Member Author

Created a commit implementing approach 3: https://github.com/cgdecker/jimfs/commit/029a05e314ceeb9da7688cb3060c48c986343e03

All tests pass, but I need to try it out on App Engine or something else that would exhibit the class loading problems.

@cgdecker cgdecker self-assigned this Feb 20, 2015
@cpovirk
Copy link
Member

cpovirk commented Feb 23, 2015

The only thing I'm wondering is whether we can make the system-loaded JimfsFileSystemProvider force the system classloader to also load the other jimfs classes. Would that make later users get the system-loaded copies? I'm in over my head here....

Regardless, your reasoning behind approach 3 sound good to me.

@cgdecker
Copy link
Member Author

The only thing I'm wondering is whether we can make the system-loaded JimfsFileSystemProvider force the system classloader to also load the other jimfs classes. Would that make later users get the system-loaded copies? I'm in over my head here....

I have no idea. I guess that since the system class loader loads the java.nio.file classes themselves, that's why there's only one copy of them between the two class loaders? ClassLoaders confuse me. Even assuming that to be the case, I'm not sure how we'd force the system class loader to preemptively load the Jimfs classes before the user code's main method is called. I'm mostly just confusing myself more here.

I've verified that approach 3 works and created a test for it. It's slightly weird but in general not too bad in my opinion. The grossest thing about it is is that it requires a couple of reflective method calls at places where the system-loaded FileSystemProvider has to interact with the rest of the code and vice versa (for example, the code that handles how JimfsFileSystem removes itself from the system provider's file system cache when it's closed.)

@cpovirk
Copy link
Member

cpovirk commented Feb 23, 2015

SGTM. Oh, I meant to also make the obvious observation that someone on Stack Overflow might know the answer. Then I meant to make the equally obvious observation that probably that won't happen. But it might be worth a shot. Up to you.

cgdecker added a commit that referenced this issue Feb 25, 2015
See #18 for more information.

- SystemJimfsFileSystemProvider (which is loaded as a service by the system classloader) only serves as a cache of FileSystem instances as needed to make Paths.get(URI) work.
- JimfsFileSystemProvider is the actual provider that is returned by JimfsFileSystem.provider(). It is a singleton and provides the real implementation of most of the FileSystemProvider methods, but does not cache FileSystem instances.

We now create the FileSystem instances directly when the user calls Jimfs.newFileSystem rather than passing the Configuration off to FileSystems.newFileSystem. This should ensure no classloader issues when creating the FileSystem. Once created, the FileSystem itself is passed to FileSystems.newFileSystem. There should be no classloader issues here either, since the new SystemJimfsFileSystemProvider implementation does not deal with any other Jimfs-specific classes.

Also add a test that fails without this change.
-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=87170296
@cgdecker
Copy link
Member Author

Fixed in 9acd047.

@masngh
Copy link

masngh commented Mar 7, 2016

is there a place where I can reference a release (in pom) that contains this fix? thanks.

@cgdecker
Copy link
Member Author

cgdecker commented Mar 7, 2016

Yes, Jimfs 1.1 contains this fix.

@masngh
Copy link

masngh commented Mar 7, 2016

I see. Just to make sure. The 1.1 release is on 2/12 (http://mvnrepository.com/artifact/com.google.jimfs/jimfs/1.1), but the check in seems to be on 2/25:
9acd0479acd047 on Feb 25, 2015
[https://avatars2.githubusercontent.com/u/101568?v=3&s=40] cgdeckerhttps://github.com/cgdecker Split JimfsFileSystemProvider up to work around classloader issues.9acd047

that’s why I suspect it’s not in. I am having problem with the following statement:

FileSystem fs = Jimfs.newFileSystem(Configuration.unix());

Running in eclipse. It gives me this exception:

java.lang.NoSuchMethodError: com.google.common.collect.ImmutableSet.copyOf(Ljava/util/Collection;)Lcom/google/common/collect/ImmutableSet;
at com.google.common.jimfs.Configuration$Builder.setRoots(Configuration.java:611)
at com.google.common.jimfs.Configuration$UnixHolder.(Configuration.java:92)
at com.google.common.jimfs.Configuration.unix(Configuration.java:86)
at com.thermofisher.qpcr.analysis.cbt.TestUtls.t2(TestUtls.java:102)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

sm

From: Colin Decker <notifications@github.hscsec.cnmailto:notifications@github.com>
Reply-To: google/jimfs <reply@reply.github.hscsec.cnmailto:reply@reply.github.com>
Date: Monday, March 7, 2016 at 1:39 PM
To: google/jimfs <jimfs@noreply.github.hscsec.cnmailto:jimfs@noreply.github.com>
Cc: Shaohua Ma <shaohua.ma@lifetech.commailto:shaohua.ma@lifetech.com>
Subject: Re: [jimfs] ClassLoader issues with Jimfs.newFileSystem (#18)

Yes, Jimfs 1.1 contains this fix.


Reply to this email directly or view it on GitHubhttps://github.com//issues/18#issuecomment-193466177.

@cgdecker
Copy link
Member Author

cgdecker commented Mar 7, 2016

The 1.1 release was on 2/12/2016, while the change was made in 2015.

The error you're seeing likely means you have a very old version of Guava (or perhaps even google-collections, its predecessor) on your classpath (perhaps transitively through some other dependency). You'll need to find out where that's coming from (mvn dependency:tree might help) and deal with it somehow... ideally by upgrading to a new version of that library that depends on a newer version of Guava, or else by excluding Guava/google-collections from that library's dependencies.

@masngh
Copy link

masngh commented Mar 8, 2016

Thanks.

you’re right. it’s due to the old version of the google collection. Replacing the lib with guava and the issue is gone.

sm

From: Colin Decker <notifications@github.hscsec.cnmailto:notifications@github.com>
Reply-To: google/jimfs <reply@reply.github.hscsec.cnmailto:reply@reply.github.com>
Date: Monday, March 7, 2016 at 1:58 PM
To: google/jimfs <jimfs@noreply.github.hscsec.cnmailto:jimfs@noreply.github.com>
Cc: Shaohua Ma <shaohua.ma@lifetech.commailto:shaohua.ma@lifetech.com>
Subject: Re: [jimfs] ClassLoader issues with Jimfs.newFileSystem (#18)

The 1.1 release was on 2/12/2016, while the change was made in 2015.

The error you're seeing likely means you have a very old version of Guava (or perhaps even google-collections, its predecessor) on your classpath (perhaps transitively through some other dependency). You'll need to find out where that's coming from (mvn dependency:tree might help) and deal with it somehow... ideally by upgrading to a new version of that library that depends on a newer version of Guava, or else by excluding Guava/google-collections from that library's dependencies.


Reply to this email directly or view it on GitHubhttps://github.com//issues/18#issuecomment-193474137.

ghost pushed a commit to facebook/buck that referenced this issue Aug 18, 2016
…priately.

Summary:
The Thrift API for Eden fails if the path passed to `getSHA1()` is a symlink.
This adds logic to Buck to try to recover from this in a graceful way.

This upgrades us from jimfs-1.0 to jimfs-1.1 to pick up this fix:
google/jimfs#18

Test Plan:
Introduced a new unit test: `EdenProjectFilesystemDelegateTest`.
Incidentally, this is our first use of PowerMock in Buck.

Reviewed By: yiding

fbshipit-source-id: a472b95
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
fixed type=defect Bug, not working as expected
Projects
None yet
Development

No branches or pull requests

3 participants