views:

688

answers:

2

OK this is presumably a hard one, I've got an pyGTK application that has random crashes due to X Window errors that I can't catch/control.

So I created a wrapper that restarts the app as soon as it detects a crash, now comes the problem, when the user logs out or shuts down the system, the app exits with status 1. But on some X errors it does so too.

So I tried literally anything to catch the shutdown/logout, with no success, here's what I've tried:

import pygtk
import gtk
import sys


class Test(gtk.Window):
    def delete_event(self, widget, event, data=None):
        open("delete_event", "wb")

    def destroy_event(self, widget, data=None):
        open("destroy_event", "wb")

    def destroy_event2(self, widget, event, data=None):
        open("destroy_event2", "wb")

    def __init__(self):
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
        self.show()
        self.connect("delete_event", self.delete_event)
        self.connect("destroy", self.destroy_event)
        self.connect("destroy-event", self.destroy_event2)      

def foo():
    open("add_event", "wb")

def ex():
    open("sys_event", "wb")


from signal import *
def clean(sig):
    f = open("sig_event", "wb")
    f.write(str(sig))
    f.close()
    exit(0)

for sig in (SIGABRT, SIGILL, SIGINT, SIGSEGV, SIGTERM):
    signal(sig, lambda *args: clean(sig))


def at():
    open("at_event", "wb")

import atexit
atexit.register(at)

f = Test()
sys.exitfunc = ex
gtk.quit_add(gtk.main_level(), foo)

gtk.main()
open("exit_event", "wb")

Not one of these succeeds, is there any low level way to detect the system shutdown? Google didn't find anything related to that.

I guess there must be a way, am I right? :/

EDIT: OK, more stuff.

I've created this shell script:

#!/bin/bash


trap test_term TERM
trap test_hup HUP


test_term(){
    echo "teeeeeeeeeerm" >~/Desktop/term.info
    exit 0
}

test_hup(){
    echo "huuuuuuuuuuup" >~/Desktop/hup.info
    exit 1
}

while [ true ]
do
    echo "idle..."
    sleep 2
done

And also created a .desktop file to run it:

[Desktop Entry]
Name=Kittens
GenericName=Kittens
Comment=Kitten Script
Exec=kittens
StartupNotify=true
Terminal=false
Encoding=UTF-8
Type=Application
Categories=Network;GTK;
Name[de_DE]=Kittens 

Normally this should create the term file on logout and the hup file when it has been started with &. But not on my System. GDM doesn't care about the script at all, when I relog, it's still running.

I've also tried using shopt -s huponexit, with no success.

EDIT2:
Also here's some more information aboute the real code, the whole thing looks like this:

Wrapper Script, that catches errors and restarts the programm
    -> Main Programm with GTK Mainloop
        -> Background Updater Thread

The flow is like this:

Start Wrapper
-> enter restart loop
    while restarts < max:
        -> start program
            -> check return code
                -> write error to file or exit the wrapper on 0

Now on shutdown, start program return 1. That means either it did hanup or the parent process terminated, the main problem is to figure out which of these two did just happen. X Errors result in a 1 too. Trapping in the shellscript doesn't work.

If you want to take a look at the actual code check it out over at GitHub:
http://github.com/BonsaiDen/Atarashii

A: 

You forgot to close gtk's event loop.

This code exits with code 0 when you close the window:

import gtk

class Test(gtk.Window):
    def destroy_event(self, widget, data=None):
        gtk.main_quit()

    def __init__(self):
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
        self.connect("destroy", self.destroy_event)
        self.show()

f = Test()
gtk.main()

EDIT: Here's the code to catch the SIGTERM signal:

import signal

def handler(signum, frame):
    print 'Signal handler called with signal', signum
    print 'Finalizing main loop'
    gtk.main_quit()

signal.signal(signal.SIGTERM, handler)

The rest of the code is exactly as above, no changes. It works here when I send SIGTERM to the python process: gtk main loop ends and program exits with exit code 0.

nosklo
That doesn't solve the problem, destroy_event doesn't even get called when the user logs out or shuts down the system, none of the above files gets created. The other problem is due to the wrapper script, it gets "killed" first, so the app will exit with 1 because its parent process went away.
Ivo Wetzel
Further research indicates that actually the process should get a SIGTERM. But it doesn't get one, seems that it's just getting killed...
Ivo Wetzel
According to the source of GDM it first sends SIGTERM, it seems python just don't cares :/
Ivo Wetzel
@Ivo Wetzel: I've edited my answer.
nosklo
Still won't work, it seems the GnomeDesktopManager somehow freaks out, I connected to every single signal, but none of them are received, I guess it really just does a -kill. I also created a shell script with traps all the signals, still no luck.
Ivo Wetzel
@Ivo Wetzel: I can't reproduce the issue. Here it works flawlessy. I've made it create a file on SIGTERM and it creates the file just fine, when I log out. Using GDM here on Ubuntu 9.10. It must be something else you're doing in your code (are you using threading?)
nosklo
I'm on Ubuntu too, I've updated the question with a shell script that doesn't work either.
Ivo Wetzel
A: 

OK, I finally found the solution :)

You simply can't rely on signals in this case. You have to connect to the Desktop Session in order to get notified that a logout is going to happen.

import gnome.ui

gnome.program_init('Program', self.version) # This is going to trigger a warning that program name has been set twice, you can ignore this, it seems to be a problem with a recent version of glib, the warning is all over the place out there
client = gnome.ui.master_client() # connect us to gnome session manager, we need to init the program before this
client.connect('save-yourself', self.on_logout) # This gets called when the user confirms the logout/shutdown
client.connect('shutdown-cancelled', self.on_logout_cancel) # This gets called when the logout/shutdown is canceled
client.connect('die', self.on_logout) # Don't know when this gets called it never got in my tests

def on_logout(self, *args):
    # save settings an create a file that tells the wrapper that we have exited correctly!
    # we'll still return with status code 1, but that's just gtk crashing somehow

def on_logout_cancel(self, *args):
    # simply delete the logout file if it exists

One important note here: Don't try to exit your program in on_logout, if you do so, GNOME won't recognize that your program has been exited and will give you the dialog that some programs are still running.

Ivo Wetzel