0

I have an app running constantly (started in Linux with screen -S myapp python3 app.py and then I detach it). It could be a Bottle app, Flask app, or any other system involving a forever-running event loop:

import anyframework   # can be bottle, flask or anything else
import sqlite3

@route('/')
def index():
    c = db.cursor()
    c.execute('INSERT INTO test VALUES (?)', ('test',))
    c.close()  # we can't commit here for *each* client request, it would eat 100ms for each request
    return 'hello'

@route('/makeitcrash')
def index():
    sdlfksdfs  # this will generate an error

def cleanup():
    db.commit()

db = sqlite3.connect('test.db')
run()

How to make sure reliably that cleanup() (and thus DB commit) is called in all possible cases of the server terminating? i.e.:

  • if the server is killed with SIGKILL, SIGTERM

  • if the server code has an error (exemple if http://example.com/makeitcrash is visited)

  • if I do CTRL+C in the terminal (inside the running screen)

?

I was about to use atexit and to add try: except: everywhere but I think it would introduce many code duplication to insert try: except: for every route.

What is the general solution for this problem?

davidism
  • 121,510
  • 29
  • 395
  • 339
Basj
  • 41,386
  • 99
  • 383
  • 673

2 Answers2

1

One approach is to catch signals and do cleanup when one is received.

import signal
import sys

from bottle import run

def handle_signal(signum, frame):
    print(f'handling signal {signum}')
    # do cleanup
    sys.exit(0)

signal.signal(signal.SIGINT, handle_signal)
# also SIGTERM, et al.

run()

Caveats

As @MarkAllen points out, SIGKILL can never be caught.

I also would advise against approaching the problem this way in the first place. Instead, see if you can design your web app so that it doesn't need to do any cleanup on shutdown. (For example, perform a durable write on each request, instead of buffering in memory.) Reducing the amount of state that you keep around will help simplify many things, including how to handle crashes/shutdowns.

ron rothman
  • 17,348
  • 7
  • 41
  • 43
0

"SIGKILL by its very nature cannot be trapped."

See this answer https://stackoverflow.com/a/30732799/7527728

You could possibly use a with statement for the cases which can be trapped, see https://www.python.org/dev/peps/pep-0343/

There's a very simple example of how to define enter and exit blocks here: https://effbot.org/zone/python-with-statement.htm

Mark Allen
  • 192
  • 9
  • Thanks. In my example code, where would you set the `with`? (Could you maybe post an example code?) – Basj May 05 '20 at 09:37
  • hey, I edited the answer with a link that has a simple example, it should be clear from there what you need to do, otherwise let me know :) – Mark Allen May 05 '20 at 09:52
  • Thanks @MarkAllen for your edit. I already know `with` in various cases (mostly for file open, etc.) but in this precise case here with Bottle or Flask, how would you use it? (some sample code would be help for this context) – Basj May 05 '20 at 09:54