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

FileHandle#exists() doesn't close resource on iOS #2345

Closed
dermetfan opened this issue Sep 18, 2014 · 13 comments
Closed

FileHandle#exists() doesn't close resource on iOS #2345

dermetfan opened this issue Sep 18, 2014 · 13 comments

Comments

@dermetfan
Copy link
Contributor

Calling FileHandle#exists() (internal file) on iOS seems to forget closing a resource.

When I launch the iOS version of the app with i18n installed, I get this trace. It seems to run, correctly, but I'm guessing there should be a close in there somewhere...

[WARN] android.System: A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks.
[WARN] android.System: java.lang.Throwable: Explicit termination method 'close' not called
    at dalvik.system.CloseGuard.open(CloseGuard.java)
    at java.io.RandomAccessFile.<init>(RandomAccessFile.java)
    at java.io.RandomAccessFile.<init>(RandomAccessFile.java)
    at java.util.zip.ZipFile.<init>(ZipFile.java)
    at java.util.zip.ZipFile.<init>(ZipFile.java)
    at java.lang.PathClassLoader.init(PathClassLoader.java)
    at java.lang.PathClassLoader.findResource(PathClassLoader.java)
    at java.lang.ClassLoader.getResource(ClassLoader.java)
    at java.lang.Class.getResource(Class.java)
    at com.badlogic.gdx.files.FileHandle.exists(FileHandle.java)
    at com.badlogic.gdx.utils.I18NBundle.loadBundle(I18NBundle.java)
    at com.badlogic.gdx.utils.I18NBundle.loadBundleChain(I18NBundle.java)
    at com.badlogic.gdx.utils.I18NBundle.loadBundleChain(I18NBundle.java)
    at com.badlogic.gdx.utils.I18NBundle.createBundleImpl(I18NBundle.java)
    at com.badlogic.gdx.utils.I18NBundle.createBundle(I18NBundle.java)
    at com.egghead.logic.LogicI18n.<init>(LogicI18n.java)
    at com.egghead.logic.App.createI18n(App.java)
    at com.egghead.core.AppCore.create(AppCore.java)
    at com.egghead.logic.App.create(App.java)
    at com.badlogic.gdx.backends.iosrobovm.IOSGraphics.draw(IOSGraphics.java)
    at com.badlogic.gdx.backends.iosrobovm.IOSGraphics$1.draw(IOSGraphics.java)
    at org.robovm.apple.uikit.UIView.$cb$drawRect$(UIView.java)
    at org.robovm.apple.uikit.UIApplication.main(Native Method)
    at org.robovm.apple.uikit.UIApplication.main(UIApplication.java)
    at com.eggheadgames.quicklogicproblems.IOSLauncher.main(IOSLauncher.java)

Since I don't have an iOS device I can't try if FileHandle#exists() alone triggers this, so I can currently only assure this happens when loading an I18NBundle. Here's some code to replicate:

public class I18NBundleIOSUnclosedResourceTest extends ApplicationAdapter {
    @Override
    public void create() {
        I18NBundle bundle = I18NBundle.createBundle(Gdx.files.internal("i18nStub"));
        Gdx.app.log("key", bundle.get("key"));
        Gdx.app.exit();
    }
}

Put this into your assets folder as i18nStub.properties:

key=value

This should load the bundle, print key: value to the console and exit.

Some more information from @mikemee who tested on an iOS device:

Fwiw, like all good memory bugs, it comes and gos. If I try and strip out everything it goes. If I try to binary-search to see exactly what trips it, then sometimes it will trip and other times it won't - with exactly the same code. However, with most stuff commented out in my IOSLauncher it will still trip, so I'm reasonably confident... I think part of the issue is that it terminates before the message can be emmitted. (As it is, sometimes the value/key message is mixed in with the error message).

I did just switch to my faster device (ipad Mini instead of iPhone 4) and it did occur more frequently, with even more code stripped away. So I agree you should log it. Thanks!

@MobiDevelop
Copy link
Member

This looks like it is deep within the depths of the runtime. The exists method is calling Class#getResource to determine existence of the resource, which seems to result in some ZipFile access where streams are opened and not explicitly closed. I don't think there is anything that can be done about this on the libgdx side.

@davebaol
Copy link
Member

I agree, that's what the stack trace says.

EDIT:
Also, looks like it happens only when the file doesn't exist on the internal filesystem.
Using FileHandler.exists() directly would be a good test.

@mikemee
Copy link
Contributor

mikemee commented Sep 18, 2014

Fwiw, the file referenced does exist when this is happening.

@davebaol
Copy link
Member

@mikemee
Yes, but I18NBundle tries to load behind the scene all the files whose name ends with the candidate locales. Usually some of them are missing.
So can you test exists directly for both existing and non-existing files?

@mikemee
Copy link
Contributor

mikemee commented Sep 18, 2014

@davebaol Happy to test on my system where this repros (I'm the Michael referenced). Can you expand on: Using FileHandler.exists() directly would be a good test. and I'll give it a try?

@davebaol
Copy link
Member

@mikemee
Something like that

public class FileHandleExistsTest extends ApplicationAdapter {
    @Override
    public void create () {
        FileHandle fh = Gdx.files.internal("my_file");
        if (fh.exists())
            Gdx.app.log("FileHandleExistsTest", "File exists");
        else
            Gdx.app.log("FileHandleExistsTest", "File doesn't exist");
        Gdx.app.exit();
    }
}

Run it with and without my_file in assets folder.

EDIT:
If the issue doesn't appear systematically try to put the code inside a loop

@mikemee
Copy link
Contributor

mikemee commented Sep 19, 2014

First attempt, without the file existing:

[WARN] android.System: A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks.
[WARN] android.System: java.lang.Throwable: Explicit termination method 'close' not called
    at dalvik.system.CloseGuard.open(CloseGuard.java)
    at java.io.RandomAccessFile.<init>(RandomAccessFile.java)
    at java.io.RandomAccessFile.<init>(RandomAccessFile.java)
    at java.util.zip.ZipFile.<init>(ZipFile.java)
    at java.util.zip.ZipFile.<init>(ZipFile.java)
    at java.lang.PathClassLoader.init(PathClassLoader.java)
    at java.lang.PathClassLoader.findResource(PathClassLoader.java)
    at java.lang.ClassLoader.getResource(ClassLoader.java)
    at java.lang.Class.getResource(Class.java)
    at com.badlogic.gdx.files.FileHandle.exists(FileHandle.java)
    at com.eggheadgames.quicklogicproblems.IOSLauncher$FileHandleExistsTest.create(IOSLauncher.java)
    at com.badlogic.gdx.backends.iosrobovm.IOSGraphics.draw(IOSGraphics.java)
    at com.badlogic.gdx.backends.iosrobovm.IOSGraphics$1.draw(IOSGraphics.java)
    at org.robovm.apple.uikit.UIView.$cb$drawRect$(UIView.java)
    at org.robovm.apple.uikit.UIApplication.main(Native Method)
    at org.robovm.apple.uikit.UIApplication.main(UIApplication.java)
    at com.eggheadgames.quicklogicproblems.IOSLauncher.main(IOSLauncher.java)

2014-09-19 17:05:52.420 IOSLauncher[1817:60b] [info] FileHandleExistsTest: File doesn't exist

This was followed by a successful attempt when the file did exist (i tried it 3 times to be sure - with the file missing it failed on the first attempt):

2014-09-19 17:08:38.182 IOSLauncher[1825:60b] [debug] IOSApplication: View: Portrait 768x1024
2014-09-19 17:08:38.187 IOSLauncher[1825:60b] [debug] IOSGraphics: 768.0x1024.0, 1.0
2014-09-19 17:08:39.044 IOSLauncher[1825:60b] [debug] IOSGraphics: Calculating density, UIScreen.mainScreen.scale: 1.0
2014-09-19 17:08:39.047 IOSLauncher[1825:60b] [debug] IOSGraphics: Display: ppi=132, density=1.0
2014-09-19 17:08:39.553 IOSLauncher[1825:60b] [debug] IOSApplication: created
2014-09-19 17:08:39.579 IOSLauncher[1825:60b] Flurry: Starting session on Agent Version [Flurry_iOS_120_4.2.4] 
2014-09-19 17:08:39.609 IOSLauncher[1825:60b] [debug] IOSApplication: resumed
2014-09-19 17:08:39.624 IOSLauncher[1825:60b] [info] FileHandleExistsTest: File exists

@davebaol
Copy link
Member

Thanks for the verification.

@mikemee
Copy link
Contributor

mikemee commented Sep 19, 2014

No worries. Loving this framework and it's working out well for my company. So I want to take the time to give back in all ways possible. Too easy to 'let someone else worry about it'.

I was able to build libgdx not too long ago, so if/when there's a proposed fix, I'm happy to try it.

@MobiDevelop
Copy link
Member

So, we verified that the warning shows only when a file does not exist, and that it is a result of the underlying runtime not closing some stream it creates when determining if that file exists? If that's the case, it sounds like something that we should close as not a libgdx defect.

@davebaol
Copy link
Member

Yes that's exactly the case.
However I'm not sure why FileHandler.exists() falls back to Class.getResource() if the file doesn't exist in the internal filesystem. There must be a reason, I guess.

@MobiDevelop
Copy link
Member

The reason for the fallback is that when internal files are bundled into the jar (or iOS app, it seems), they become resources instead of files.

I'll go ahead and close this out since it isn't actually a libgdx defect.

@davebaol
Copy link
Member

IIRC robovm uses the java runtime from android. So it might be a robovm issue or a android runtime issue.
Maybe @badlogic as a member of the robovm team is interested in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants