-
Notifications
You must be signed in to change notification settings - Fork 695
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
ClassPool is leaking file handles #165
Comments
I agree that interface However, I am not convinced that jar and/or zip file handles are not correctly terminated. Are you not perhaps using As far as I can tell the only time javassist opens these streams is in javassist/src/main/javassist/ClassPoolTail.java Lines 281 to 297 in e41e079
Unless I am mistaken... |
If you call Relevant code: It is entirely possible I'm missing something though |
Ahh yes, you are correct, my bad.
javassist/src/main/javassist/ClassPoolTail.java Lines 226 to 240 in e41e079
javassist/src/main/javassist/ClassPool.java Lines 1000 to 1007 in e41e079
Note: Since you need to locate the class path instance to supply to I have to agree, this seems broken or incomplete and we need to find a fix. As I mentioned in my first comment, it is
This means that so far, unless a If we want to free
The only disadvantage I can think of is a possible performance knock having to open & close the archive each time to load a class found to be located within said archive and whether this will be noticeable/significant.
Disadvantage is always that these hooks cannot be guaranteed which kind of defeats the purpose.
The enclosing class knows what resources it has opened and should at the very least provide a documented mechanism by which to release them. I think this is the solution @cmelchior had in mind but instead of tracking Or am I making no sense whatsoever =) |
No, that is pretty much what I had in mind 😄 This was also our work-around as seen here: https://github.com/realm/realm-java/blob/master/realm-transformer/src/main/groovy/io/realm/transformer/ManagedClassPool.groovy The problem with only letting That said, I have only touched a very small part of the JavaAssist API, and you seem to have other ideas about avoiding having to close anything at all. If that is possible it would be less breaking to already existing users of |
Oh yes you make a valid point... this won't break backward compatibility but instead a fix will apply backwards because nobody, ok most bodies present company excluded, weren't aware they needed to do resource clean up anyway. As a non intrusive fix the What I had in mind is turning this snippet: javassist/src/main/javassist/ClassPoolTail.java Lines 129 to 183 in e41e079
Into something like: hmmmmmm ok maybe I should stare at this a little while longer. Mind you... if I am not mistaken Let me hack out a new @cmelchior I hope you are keen to drive this patch, I really don't mind helping out but I'm not looking to swipe this from you. As you may have noticed I already have several patches lying in waiting, not at all sure how I feel about that but either way I should probably not pile on more work before those get done. Besides having more pending PRs form devs other than myself might just be the motivation we need. =) This definitely needs to be fixed, lets get it done! |
So something on these lines: final class JarClassPath implements ClassPath {
List<String> jarfileEntries;
String jarfileURL;
JarClassPath(String pathname) throws NotFoundException {
try(JarFile jarfile = new JarFile(pathname)) {
jarfileEntries = jarfile.stream()
.map(e->e.getName())
.filter(e->e.endsWith(".class"))
.collect(Collectors.toList());
jarfileURL = new File(pathname).getCanonicalFile()
.toURI().toURL().toString();
return;
}
catch (IOException e) {}
throw new NotFoundException(pathname);
}
public InputStream openClassfile(String classname)
throws NotFoundException
{
URL jarURL = find(classname);
if (null != jarURL)
try {
return jarURL.openConnection().getInputStream();
}
catch (IOException e) {}
throw new NotFoundException("broken jar file?: "
+ classname);
}
public URL find(String classname) {
String jarname = classname.replace('.', '/') + ".class";
if (jarfileEntries.contains(jarname))
try {
return new URL("jar:" + jarfileURL + "!/" + jarname);
}
catch (MalformedURLException e) {}
return null; // not found
}
public String toString() {
return jarfileURL == null ? "<null>" : jarfileURL;
}
} Ok it's not source=1.6, will have to dumb it down a bit, does give the general idea. All tests succeed so that has to be a good sign. Basic break down:
@cmelchior What you thinking? Not sure if |
Seems impossible to screw this up.
class JarURLInputStream extends java.io.FilterInputStream {
JarURLInputStream (InputStream src) {
super (src);
}
public void close () throws IOException {
try {
super.close();
} finally {
if (!getUseCaches()) {
jarFile.close();
}
}
}
}
Now seeing this I am convinced we have the correct solution in hand. |
So here you go the dumbed down and source=1.6 compliant version of final class JarClassPath implements ClassPath {
List<String> jarfileEntries;
String jarfileURL;
JarClassPath(String pathname) throws NotFoundException {
JarFile jarfile = null;
try {
jarfile = new JarFile(pathname);
jarfileEntries = new ArrayList<String>();
for (JarEntry je:Collections.list(jarfile.entries()))
if (je.getName().endsWith(".class"))
jarfileEntries.add(je.getName());
jarfileURL = new File(pathname).getCanonicalFile()
.toURI().toURL().toString();
return;
} catch (IOException e) {}
finally {
if (null != jarfile)
try {
jarfile.close();
} catch (IOException e) {}
}
throw new NotFoundException(pathname);
}
@Override
public InputStream openClassfile(String classname)
throws NotFoundException
{
URL jarURL = find(classname);
if (null != jarURL)
try {
return jarURL.openConnection().getInputStream();
}
catch (IOException e) {}
throw new NotFoundException("broken jar file?: "
+ classname);
}
@Override
public URL find(String classname) {
String jarname = classname.replace('.', '/') + ".class";
if (jarfileEntries.contains(jarname))
try {
return new URL(String.format("jar:%s!/%s", jarfileURL, jarname));
}
catch (MalformedURLException e) {}
return null; // not found
}
@Override
public String toString() {
return jarfileURL == null ? "<null>" : jarfileURL.toString();
}
} nJoy! |
Cool, I'll be happy to put together a PR. Would it also make sense to deprecate |
Yes go ahead and roll away. Checklist:
Since it's package scope and not public API, add to that the fact that we just replaced the only actual implementation I think they can all be removed.
hmmm I still don't like that Did I miss anything? |
@cmelchior please feel free to make it your own! |
jboss-javassist/javassist#165 jboss-javassist/javassist#171 Change-Id: I6ebb2e837a8acffdc3a5cc9830d0a1b9d2c0d375 Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
jboss-javassist/javassist#165 jboss-javassist/javassist#171 Change-Id: I6ebb2e837a8acffdc3a5cc9830d0a1b9d2c0d375 Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
Reported in realm/realm-java#5521
By further investigation, it appears that the
ClassPool
is holding on to file descriptors ifClassPath
elements referencing Jar or Zip files are not removed again before discarding theClassPool
.This can be seen in the docs for
ClassPath.close()
: https://github.com/jboss-javassist/javassist/blob/master/src/main/javassist/ClassPath.java#L67 which are only being called when callingClassPool.removeClassPath(ClassPath)
: https://github.com/jboss-javassist/javassist/blob/master/src/main/javassist/ClassPoolTail.java#L239This is rather non-obvious as the ClassPool is not marked as
Closable
and nowhere in the description of theClassPool
does it indicate that you need to cleanup resources.I'm not sure if this is a bug, but as a minimum the docs probably need to be updated to make this use case clear.
Right now we work around it by having subclass
ManagedClassPool
that is marked asClosable
and remember allClassPath
elements so it can remove them whenclose()
is called.The text was updated successfully, but these errors were encountered: