Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly handle SIGINT to close files/connections

I want to implement a proper SIGINT handling in my script, which opens multiple files and a database connection. These should be closed if the script is CTRL+C'd or somehow else interrupted.

Previously I've used the KeyboardInterrupt exception to catch CTRL+C, there I checked if files/connections are defined, if so close them, then exit.

Is this really the pythonic way to do it, or is it better adviced to use signal handlers? e.g.

import signal, sys, time

def handler(signum, frame):
    print("..kthxbye")
    sys.exit(1)

def main():
    signal.signal(signal.SIGINT, handler)
    i = 0
    while True:
        print(i)
        i += 1
        time.sleep(1)

if __name__ == "__main__":
    main()

This seems cleaner to me, yet don't I know how I would pass filenames or database connections to the handler.

like image 716
Daedalus Mythos Avatar asked Oct 15 '14 09:10

Daedalus Mythos


2 Answers

I would rather catch the KeyboardInterrupt exception on the main thread. KeyboardInterrupt is the result of the default SIGINT handler of python. The exception handler of a KeyboardInterrupt exception is a much safer/friendly context than what you are in when catching SIGINT directly.

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        cleanup()

EDIT: Here is how to share variables (state) between the two methods:

Procedural:

import sys, time

class SharedState:
    def __init__(self):
        self.var0 = 42
        self.var1 = 'string'

# method 1
shared_variable = 'woof woof'

# method 2: avoiding global declarations in functions
shared_state = SharedState()

def main():
    # In order to write a global variable you need a global
    # declaration otherwise the assignment would create a
    # local variable

    global shared_variable
    shared_variable = 5

    shared_state.var0 = 10

    time.sleep(10)

def cleanup():
    print shared_variable
    print shared_state.var0
    sys.exit(1)

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        cleanup()

Object oriented (my preference):

import sys, time

# method 3: object oriented programming
class Program:
    def __init__(self):
        self.var0 = 42
        self.var1 = 'string'

    def main(self):
        self.var0 = 5
        self.var1 = 'woof woof'
        time.sleep(10)

    def cleanup(self):
        # both main and cleanup can access the member
        # variables of this class
        print self.var0
        print self.var1
        sys.exit(1)

    def execute(self):
        try:
            self.main()
        except KeyboardInterrupt:
            self.cleanup()

if __name__ == '__main__':
    Program().execute()
like image 116
pasztorpisti Avatar answered Oct 01 '22 18:10

pasztorpisti


My suggestion is to use the signal library to handle the signals. Signals are not exceptions and they are part of Inter Process Communication (IPC) infrastructure of the Operating System.

Signals can help you to communicate with your program, like reloading the configuration file, closing your log file handler during log rotation and so on. Most of the daemon process like apache dose it.

Shell scripts have trap command to process the signals and take appropriate actions based on the signals captured.

Generally python closes all file handlers and database connection automatically during the time of exit. But to be safe we can have a function to handle them implicitly.

Below code traps SIGINT and closes the files properly.

import signal
import sys

die = False

def handler(signum, frame):
    global die
    print('Got SIGINT.')
    die = True

def closeFile(fh):
    fh.flush()
    fh.close()

signal.signal(signal.SIGINT, handler)

fh = open('/tmp/a.txt', 'w')

while True:
    data = input('> ')

    if data == 'q':
        closeFile(fh)
        break
    else:
        fh.write(data + '\n')

    if die:
        closeFile(fh)
        print('Completed cleanup.. ')
        sys.exit()
like image 26
Kannan Mohan Avatar answered Oct 01 '22 18:10

Kannan Mohan