bottlepy / bottle

bottle.py is a fast and simple micro-framework for python web-applications.
http://bottlepy.org/
MIT License
8.37k stars 1.46k forks source link

How to stop the server? #1229

Closed josephernest closed 4 years ago

josephernest commented 4 years ago

I would like to be able to stop the server from a route, something like that:

import bottle

app = bottle.Bottle()

@bottle.route('/')
def index():
    return 'hello'

@bottle.route('/stop')
def stopit():
    # do cleanup here
    app.stop()       # the script should exit here

bottle.run(port=80)

but app.stop() is not defined.

How to stop a server from code?

Is there a stop function?

Or should we send a SIGKILL / SIGTERM ? Then is there a listener to do some final cleanup before the server stops?

defnull commented 4 years ago

The controlling part here is not bottle, but the server implementation. bottle.run() simply starts the selected wsgi-server and tells it to loop forever. Some have a shutdown method, but it is hard to get hold of it when using the run() convenience method and not start the server yourself. Most gracefully shutdown if they receive a SIGINT signal or encounter a SystemExit or KeyboardInterrupt exception. So, sys.exit() should do the trick.

That said, it is unusual to stop a server from within. You usually send a SIGINT from outside.

As for cleanup code: SIGINT triggers a KeyboardInterrupt in all running threads (if I remember correctly). So, finally blocks are still executed. But there is no guarantee. Never rely on clean-up code to run before a process terminates. SIGTERM, SIGHUB and SIGKILL will all ignore finally blocks and immediately exit the interpreter.

josephernest commented 4 years ago

Thank you for your answer. Last questions:

Here is the context (maybe you have an idea): https://stackoverflow.com/questions/61600356/how-to-commit-db-changes-in-a-flask-or-bottle-app-efficiently
I can't do SQLite db.commit() after each request (100ms is too much when there are many clients) so I wanted to do once every 30 seconds, but I need to be sure that it happens if the server is stopped. So I need to be able to have a stop handler.

defnull commented 4 years ago

If you are concerned about 100ms for a blocking db.commit() perrequst then perhaps stop using a single-threaded WSGI server implementation (wsgiref)? You are optimising at the wrong end.

josephernest commented 4 years ago

Thanks for your answer, I'll study this.

PS: It seems this has been added recently to Bottle: https://github.com/josephernest/bottle/blob/master/bottle.py#L3282

self.srv = make_server(self.host, self.port, app, server_cls,
                           handler_cls)

instead of

srv = make_server...

This is interesting because then we can simply do

server = bottle.WSGIRefServer(port=80)
bottle.run(server=server)

and this in a route:

server.srv.shutdown()

Is that correct?

defnull commented 4 years ago

Not a public or documented API, but that might work, yes. I'd still move away from wsgref as soon as possible.

josephernest commented 4 years ago

Thanks for the advice @defnull. Last thing: what do you think of this solution found on stackoverflow:

I eventually found the solution was to do:

sys.stderr.close()

inside the request (that got passed up to the bottle server and axed it).

? I tried and it works indeed.

DanielGlaas commented 3 years ago

The solution I found is:

bottle is started with

run(app, reloader=False, host='0.0.0.0', debug=True, quiet=True, port=2020)

And shutdown of the server including release of the ports is done via:

import psutil import signal

@app.route('/shutdown') def shutdown():

https://stackoverflow.com/a/60888399

current_process = psutil.Process()
current_process.send_signal(signal.SIGTERM)`