12

Original situation:

The application I'm working on at the moment will receive notification from another application when a particular file has had data added and is ready to be read. At the moment I have something like this:

class Foo(object):
    def __init__(self):
        self.myFile = open("data.txt", "r")
        self.myFile.seek(0, 2) #seeks to the end of the file

        self.mainWindow = JFrame("Foo",
                                 defaultCloseOperation = JFrame.EXIT_ON_CLOSE,
                                 size = (640, 480))
        self.btn = JButton("Check the file", actionPerformed=self.CheckFile)
        self.mainWindow.add(self.btn)
        self.mainWindow.visible = True

    def CheckFile(self, event):
        while True:
            line = self.myFile.readline()
            if not line:
                break
            print line

foo = Foo()

Eventually, CheckFile() will be triggered when a certain message is received on a socket. At the moment, I'm triggering it from a JButton.

Despite the fact that the file is not touched anywhere else in the program, and I'm not using with on the file, I keep on getting ValueError: I/O operation on closed file when I try to readline() it.

Initial Solution:

In trying to figure out when exactly the file was being closed, I changed my application code to:

foo = Foo()
while True:
    if foo.myFile.closed == True:
        print "File is closed!"

But then the problem went away! Or if I change it to:

foo = Foo()
foo.CheckFile()

then the initial CheckFile(), happening straight away, works. But then when I click the button ~5 seconds later, the exception is raised again!

After changing the infinite loop to just pass, and discovering that everything was still working, my conclusion was that initially, with nothing left to do after instantiating a Foo, the application code was ending, foo was going out of scope, and thus foo.myFile was going out of scope and the file was being closed. Despite this, swing was keeping the window open, which was then causing errors when I tried to operate on an unopened file.

Why I'm still confused:

The odd part is, if foo had gone out of scope, why then, was swing still able to hook into foo.CheckFile() at all? When I click on the JButton, shouldn't the error be that the object or method no longer exists, rather than the method being called successfully and giving an error on the file operation?

My next idea was that maybe, when the JButton attempted to call foo.CheckFile() and found that foo no longer existed, it created a new Foo, somehow skipped its __init__ and went straight to its CheckFile(). However, this doesn't seem to be the case either. If I modify Foo.__init__ to take a parameter, store that in self.myNum, and print it out in CheckFile(), the value I pass in when I instantiate the initial object is always there. This would seem to suggest that foo isn't going out of scope at all, which puts me right back where I started!!!

EDIT: Tidied question up with relevant info from comments, and deleted a lot of said comments.

agf
  • 171,228
  • 44
  • 289
  • 238
Cam Jackson
  • 11,860
  • 8
  • 45
  • 78
  • 1
    Is the other application that is writing to data.txt opening the file exclusively? – arunkumar Aug 09 '11 at 05:58
  • 3
    I think it's fairly clear that the Jython garbage collection scheme is what's tripping you up here. Python with its refcounting wouldn't run into this problem, as a weakref would be the only way to make the `file` be closed, but then the `Foo` would be killed as well. The precise detail of what's going on here should be interesting... – Chris Morgan Aug 12 '11 at 00:51
  • to save anyone else suggesting this (it was an answer which i have deleted) - it does not seem likely that it's because the file is in text mode. there is a restriction on certain seek offsets for text mode files, but end of file should be ok (the restriction is to avoid jumping into the middle of a multi-byte character). – andrew cooke Aug 13 '11 at 04:53

2 Answers2

4

* Initial, Partial Answer (Added to Question) *

I think I just figured this out. After foo = Foo(), with no code left to keep the module busy, it would appear that the object ceases to exist, despite the fact that the application is still running, with a Swing window doing stuff.

If I do this:

foo = Foo()
while True:
    pass

Then everything works as I would expect.

I'm still confused though, as to how foo.CheckFile() was being called at all. If the problem was that foo.myFile was going out of scope and being closed, then how come foo.CheckFile() was able to be called by the JButton?

Maybe someone else can provide a better answer.

agf
  • 171,228
  • 44
  • 289
  • 238
Cam Jackson
  • 11,860
  • 8
  • 45
  • 78
  • Just to clarify, this is the "Initial Solution" mentioned in the question. This doesn't answer the "Why I'm still confused" part, which is what agf's bounty is really for. – Cam Jackson Aug 13 '11 at 07:00
  • Have you tried moving everything from `__init__` into a `run` method, then `from javax.swing import SwingUtilities` and `SwingUtilities.invokeLater(Foo())` in the global scope? (On the off chance this is right, I'll repost it as an answer.) – agf Aug 13 '11 at 12:43
  • I only just found out about SwingUtilities today, so it's something I'll look into. But it will have to wait until Monday morning, this is a work thing :P – Cam Jackson Aug 13 '11 at 12:45
  • Making Foo inherit from JFrame made no difference. I had a quick try at using SwingUtilities, but I haven't been able to get it to work yet. The code in the question is the entire source, so feel free to have a go yourself if you know how SwingUtilities works. – Cam Jackson Aug 16 '11 at 02:03
  • After discovering that I should be using `SwingUtilities` to run things on the EDT (I've gone through so many Swing tutorials that completely fail to mention this), I put in the time to actually figure out how it works. Unfortunately, it made no difference either. Still no real solution to this, other than Brandon's guesswork, which I suspect is probably correct. – Cam Jackson Aug 17 '11 at 03:51
2

I think the problem arises from memory being partitioned into two types in Java, heap and non-heap.Your class instance foo gets stored in heap memory while its method CheckFile is loaded into the method area of non-heap memory. After your script finishes, there are no more references to foo so it gets marked for garbage collection, while the Swing interface is still referring to CheckFile, so it gets marked as in-use. I am assuming that foo.myFile is not considered static so it also is stored in heap memory. As for the Swing interface, it's presumably still being tracked as in-use as long as the window is open and being updated by the window manager.


Edit: your solution of using a while True loop is a correct one, in my opinion. Use it to monitor for events and when the window closes or the last line is read, exit the loop and let the program finish.


Edit 2: alternative solution - try having foo inherit from JFrame to make Swing keep a persistent pointer to it in its main loop as long as the window is open.

  • @agf: It's an answer to the question of why is this happening, which was the explicit question. I think the solution in how to avoid it is right there in his "Initial solution" area. He needs to create a main system loop for the program which monitors for updates (read lines from the file or window destroy events) and when everything is finished, it exits the loop and garbage collection does its job. –  Aug 13 '11 at 12:33
  • @agf: See CamJackson's comment on his own answer; it's not so much about finding a solution to avoiding the problem but about why this is actually happening. –  Aug 13 '11 at 12:36
  • Swing doesn't handle the main loop automatically? That seems the a very, very standard thing for a GUI toolkit to do. Further, none of the Jython or Java Swing examples I see have an explicit main loop. He wants a better solution, and so do I (which is why I set the bounty). – agf Aug 13 '11 at 12:40
  • @agf: I would think that Swing handles the main loop so I admit it's a bit weird. I haven't worked with Jython much so I'm not an expert, but perhaps it would be better to make `foo` inherit from `JFrame`. When the Swing main loop starts, it should keep a persistant reference to `foo` until the window is exited. –  Aug 13 '11 at 12:45
  • +1 tomorrow when I have votes again; I think the JFrame might work. – agf Aug 13 '11 at 12:48
  • Well, as it happens, I actually need a main loop anyway, for listening on sockets and one or two other things. So from a practical point of view, this is no longer a real problem for me. My main interest in this is know what is actually causing the problem, and why. I will experiment with the JFrame suggestion though and see if it makes a difference. – Cam Jackson Aug 14 '11 at 10:00
  • @CamJackson: does my explanation help? It happens because of how Java handles garbage collection. –  Aug 14 '11 at 17:28
  • It does seem likely that the intricacies of the JVM garbage collector are to blame, but your answer doesn't address the final paragraph of the question. If I pass in 5 as an argument when creating `foo`, store it in `self.myNum`, and print it from `CheckFile()` immediately before the file operations, then that number always prints successfully, even when the file error is occurring. That suggests that foo is not, in fact, being garbage collected o_O. So why the hell is the file object, and not the integer, being cleaned up!? – Cam Jackson Aug 15 '11 at 00:31
  • This might be due to the fact that static fields are stored in non-heap memory. Python doesn't really have static fields but maybe an integer type is considered one. I'm guessing here but it's a possibility. –  Aug 15 '11 at 08:51
  • 1
    Somehow I can't believe the button can hold a reference to the BoundMethod CheckFile without that keeping the instance alive. BoundMethods do have a reference back to their instance. – Jürgen Strobel Aug 18 '11 at 17:19
  • Yes, and the fact that I can store some data as an instance attribute, and it is never lost, would seem to prove that the `foo` object is not being garbage collected. So what happens to cause the file to be closed? – Cam Jackson Aug 19 '11 at 00:04
  • Here's a nice review of Java garbage collection: http://java.sun.com/docs/books/performance/1st_edition/html/JPAppGC.fm.html#1006018 . –  Aug 19 '11 at 16:33