Behavior of the commander.close() method for commanders created via leoBridge

30 views
Skip to first unread message

Félix

unread,
Jun 19, 2020, 9:33:43 PM6/19/20
to leo-e...@googlegroups.com
I'm doing preliminary tests with the "close" method in order to support multiple Leo files in leoInteg.

def closeFile(self, p_paramUnused):
'''Closes a leo file. A file can then be opened with "openFile"'''
print("Trying to close opened file " + str(self.commander.changed))
if self.commander:
if self.commander.changed:
return self.sendLeoBridgePackage('closed', False)
else:
self.commander.close()
return self.sendLeoBridgePackage('closed', True)
return self.sendLeoBridgePackage() # Just send empty as 'did nothing'

After going through the second branch and actually calling the .close() method on the controller that was made with the leoBridge, since I didnt change anything else to support a 'closed' commander, I expected the leoInteg view to fatally crash because all subsequent calls to 'getChildren' etc. would obviously fail because the commander was closed. 

Surprising thing is : There is no crash and I can seemingly do any browsing and editing as I could before the .close() call...

I suspect it's me who's not understanding what the self.commander.close method is actually expected to do, or maybe I should be calling something like "self.commander.file.close()" or something like that... Or maybe that just lowered an "opened" flag somewhere and all objects are still valid pointers until python does garbage collecting... i dunno :/

If anyone can shed some light on this it would be greatly appreciated! :)
--
Félix

EDIT / ADDENDUM WHILE STEPPING THROUGH WITH THE DEBUGGER
First it goes through this: 

@g.commander_command('close-window')
def close(self, event=None, new_c=None):
"""Close the Leo window, prompting to save it if it has been changed."""
g.app.closeLeoWindow(self.frame, new_c=new_c)


and then it goes through this without problems ... 
def closeLeoWindow(self, frame, new_c=None, finish_quit=True):
"""
Attempt to close a Leo window.

Return False if the user veto's the close.

finish_quit - usually True, close Leo when last file closes, but
False when closing an already-open-elsewhere file
during initial load, so UI remains for files
further along the command line.
"""
c = frame.c
if 'shutdown' in g.app.debug:
g.trace(f"changed: {c.changed} {c.shortFileName()}")
c.endEditing() # Commit any open edits.
if c.promptingForClose:
# There is already a dialog open asking what to do.
return False
g.app.recentFilesManager.writeRecentFilesFile(c)
# Make sure .leoRecentFiles.txt is written.
if c.changed:
c.promptingForClose = True
veto = frame.promptForSave()
c.promptingForClose = False
if veto: return False
g.app.setLog(None) # no log until we reactive a window.
g.doHook("close-frame", c=c)
#
# Save the window state for *all* open files.
g.app.saveWindowState(c)
g.app.saveEditorDockState(c)
g.app.commander_cacher.commit()
# store cache, but don't close it.
# This may remove frame from the window list.
if frame in g.app.windowList:
g.app.destroyWindow(frame)
g.app.windowList.remove(frame)
else:
# #69.
g.app.forgetOpenFile(fn=c.fileName(), force=True)
if g.app.windowList:
c2 = new_c or g.app.windowList[0].c
g.app.selectLeoWindow(c2)
elif finish_quit and not g.app.unitTesting:
g.app.finishQuit()
return True # The window has been closed.

Again, since I've got no gui and the commander I'm closing was opened through leoBridge, I guess I should just then 'pop' the commander from the array I was keeping it in, and consider it "closed" and its 'ressources' will be freed eventually? ( as this reference seems to indicate? https://stackoverflow.com/questions/49065803/python-delete-objects-and-free-up-space )

If that's the case then I could/might do it myself by copying parts of what this last function does (because sometimes I might want to 'force' close) 

Thanks for any insights! :) 
(edit: typos)


Edward K. Ream

unread,
Jun 20, 2020, 5:45:04 AM6/20/20
to leo-editor

On Friday, June 19, 2020 at 8:33:43 PM UTC-5, Félix wrote:
I'm doing preliminary tests with the "close" method in order to support multiple Leo files in leoInteg.

This is an excellent question. I think you understand the overall situation quite well.

I wrote a preliminary answer without realizing you had added the addendum. I don't see the addendum in my email, presumably because your edit didn't trigger another email. In future, please reply to yourself, rather than doing a major edit.


def closeFile(self, p_paramUnused):
'''Closes a leo file. A file can then be opened with "openFile"'''
print("Trying to close opened file " + str(self.commander.changed))
if self.commander:
if self.commander.changed:
return self.sendLeoBridgePackage('closed', False)
else:
self.commander.close()
return self.sendLeoBridgePackage('closed', True)
return self.sendLeoBridgePackage() # Just send empty as 'did nothing'

After going through the second branch and actually calling the .close() method on the controller that was made with the leoBridge, since I didnt change anything else to support a 'closed' commander, I expected the leoInteg view to fatally crash because all subsequent calls to 'getChildren' etc. would obviously fail because the commander was closed. 

Surprising thing is : There is no crash and I can seemingly do any browsing and editing as I could before the .close() call...

I suspect it's me who's not understanding what the self.commander.close method is actually expected to do, or maybe I should be calling something like "self.commander.file.close()" or something like that... Or maybe that just lowered an "opened" flag somewhere and all objects are still valid pointers until python does garbage collecting... i dunno :/

If anyone can shed some light on this it would be greatly appreciated! :)

See below.

EDIT / ADDENDUM WHILE STEPPING THROUGH WITH THE DEBUGGER
First it goes through this: 

@g.commander_command('close-window')
def close(self, event=None, new_c=None):
"""Close the Leo window, prompting to save it if it has been changed."""
g.app.closeLeoWindow(self.frame, new_c=new_c)


Yes. This is the code that corresponds to c.close(). The decorator injects this method into to all open commanders!
Exactly.

Again, since I've got no gui and the commander I'm closing was opened through leoBridge, I guess I should just then 'pop' the commander from the array I was keeping it in, and consider it "closed" and its 'ressources' will be freed eventually? ( as this reference seems to indicate? https://stackoverflow.com/questions/49065803/python-delete-objects-and-free-up-space )

The key fragment in g.app.closeWindow is:

if frame in g.app.windowList:
    g
.app.destroyWindow(frame)
    g
.app.windowList.remove(frame)
else:
   
# #69.
    g
.app.forgetOpenFile(fn=c.fileName(), force=True)

g.app.destroyWindow is:

def destroyWindow(self, frame):
   
"""Destroy all ivars in a Leo frame."""
   
if 'shutdown' in g.app.debug:
        g
.pr(f"destroyWindow:  {frame.c.shortFileName()}")
   
if g.app.externalFilesController:
        g
.app.externalFilesController.destroy_frame(frame)
   
if frame in g.app.windowList:
       
# g.pr('destroyWindow', (g.app.windowList)
        g
.app.forgetOpenFile(frame.c.fileName())
   
# force the window to go away now.
   
# Important: this also destroys all the objects of the commander.
    frame
.destroySelf()

However, the bridge uses a nullFrame, and nullFrame.destroySelf is a do-nothing.

So I conclude that the commander is not destroyed immediately. Otoh, the (null) frame is removed from g.app.windowList, so there is one less reference to the commander. It's possible that the gc could remove the commander later.

Summary

I think you understand the situation quite well.

c.close does the following:

- Prompts for save if the commander has changed.
- Clears c.frame from g.app.windowList.
- (Supposedly) completely destroys c.frame and c. But this doesn't happen when using a nullFrame.

My advice is not to worry about whether c has been garbage collected. You can safely access c if and only if c.frame exists in g.app.windowList. However, there is no guarantee that this advice is correct.

HTH.

Edward

Edward K. Ream

unread,
Jun 20, 2020, 5:55:15 AM6/20/20
to leo-editor
On Saturday, June 20, 2020 at 4:45:04 AM UTC-5, Edward K. Ream wrote:

My advice is not to worry about whether c has been garbage collected. You can safely access c if and only if c.frame exists in g.app.windowList.

The easy way to make this test is: `c in g.app.commanders()`.

Edward

Félix

unread,
Jun 20, 2020, 9:33:56 AM6/20/20
to leo-editor
Thats great! Thanks! I will also use your advice about replying instead of editing existing posts.

I was trying to make working 'close' and 'save as' methods last night and got caught up in trying to also make multiple opened files a reality also while at it.

I will show the name of the currently chosen file as the status bar indicator, and clicking it will unfold the list of currently opened leo files to choose from.

I'll get back to this later have a great and sunny Saturday everybody!

Reply all
Reply to author
Forward
0 new messages