I'm creating a local app. I'd like to be able to shutdown the server from am api call, but do not have any success.
Here're some code:
# q.py
from multiprocessing import Queue
q = Queue()
def stop_server(*args, **kwargs):
q.put("EXTERMINATE")
# app.py
app = FastAPI()
#app.get("/kill")
def index(background_tasks: BackgroundTasks):
background_tasks.add_task(stop_server)
return {"killed": True}
# main.py
from q import q
def start_server():
uvicorn.run(app="app:app", host="127.0.0.1", port=8080)
if __name__ == "__main__":
server = Process(target=start_server)
server.start()
# sleep(5)
# q.put("EXTERMINATE")
# sleep(3)
while True:
msg = q.get()
if msg == "EXTERMINATE":
while server.is_alive():
server.terminate()
sleep(0.1)
server.join(timeout=1)
q.close()
break
Accessing "127.0.0.1:8080/kill" does nothing.
There is a commented block that manually put a message into the queue. If you uncomment the code, you should see the server terminated successfully.
So, how could I access the queue from inside a fastapi handler?
Related
I have a schedule script running a job with:
schedule.every(3).seconds.do(jobCheckSmth)
while True:
schedule.run_pending()
time.sleep(1)
I wanted to use web interface to check on it's status instead of print() on the CLI with
run(host='localhost', port=80, debug=True)
But it blocks code execution so I have to Ctrl-C to break webserver loop to continue to run while loop
Bottle v0.12.18 server starting up (using WSGIRefServer())...
Listening on http://localhost:80/ Hit Ctrl-C to quit.
Here is an example that works really well for me using gevent to turn bottle into async. It's either this, or you have to run schedule in it's own thread outside of your bottle app. But honestly you should be doing this.
import gevent
from gevent import monkey,spawn as gspawn, sleep as gsleep, socket, signal_handler as sig
monkey.patch_all()
import signal
import arrow
from bottle import Bottle, static_file, get, post, request, response, template, redirect, hook, route, abort
from gevent.pywsgi import WSGIServer
from geventwebsocket.handler import WebSocketHandler
def start():
def start_thread():
set()
while 1:
try:
schedule.run_pending()
except:
logger.exception('Scheduler Exception')
gsleep(5) # This is the polling cycle for pending jobs
print('Scheduler Started...')
gspawn(start_thread)
def sample():
gspawn(jobCheckSmth)
def set():
# schedule.every(180).seconds.do(func)
schedule.every().day.at("00:00").do(sample)
logger.info('Started Schedule at {}'.format(arrow.now()))
#get('/')
def app():
return 'Hello World!'
if __name__ == '__main__':
scheduler.start()
botapp = bottle.app()
server = WSGIServer(("0.0.0.0", int(port)), botapp , handler_class=WebSocketHandler)
def shutdown():
print('Shutting down ...')
server.stop(timeout=60)
exit(signal.SIGTERM)
sig(signal.SIGTERM, shutdown)
sig(signal.SIGINT, shutdown)
server.serve_forever()
I'm writing a simple module in my Python app to communicate with the server. I use gevent and zeromq socket. This module will run in a thread.
Here is a demo
import threading
import gevent
import zmq.green as zmq
class SocketTest(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.context = zmq.Context()
self.socket = None
self.running = False
self.sock_greenlet = None
def run(self):
self.socket = self.context.socket(zmq.DEALER)
self.socket.setsockopt(zmq.RCVTIMEO, 1000)
self.socket.connect('tcp://127.0.0.1:9999')
self.running = True
self.sock_greenlet = gevent.spawn(self.process)
print('Starting')
self.sock_greenlet.join()
def process(self):
while self.running:
print('Wait for data')
data = self.socket.recv()
# do something
print(data)
gevent.sleep(0.5)
def stop(self):
print('Stop app')
self.running = False
# I want to ask all greenlets to exit, or kill them
# gevent.wait(timeout=0)
gevent.kill(self.sock_greenlet)
print('End of stop')
def test():
try:
app = SocketTest()
app.start()
app.join()
except KeyboardInterrupt:
app.stop()
print('Exit')
if __name__ == '__main__':
test()
When I press Ctrl + C, my app doesn't exit. I understand that my thread is running an event loop. But I don't know how to stop greenlet process properly or kill it.
In main thread, I will call app.stop, is it safe if I access some variables of gevent loop ?
( I want to send a goodbye message to server when my app exits)
I use this in all my projects, works great.
if __name__ == '__main__':
server = WSGIServer(("0.0.0.0", int(port)), app , handler_class=WebSocketHandler)
def shutdown():
print('Shutting down ...')
server.stop(timeout=60)
exit(signal.SIGTERM)
gevent.signal(signal.SIGTERM, shutdown)
gevent.signal(signal.SIGINT, shutdown) #CTRL C
server.serve_forever()
I haven't found a way to set a handler to detect when a flask server is already running. Consider the following code snippet:
import flask
import requests
def on_start():
# send a request to the server, it's safe to do so
# because we know it's already running
r = requests.get("http://localhost:1234")
print(r.text) # hello world
app = flask.Flask(__name__)
#app.route("/")
def hello():
return "hello world"
app.run(port=1234, host="localhost", on_start=on_start)
The last line fails because on_start is not an argument of run, but hopefully you get the idea of what I'm trying to do. How can I do it?
What you can do is wrap the function that you want to kick off with before_first_request decorator as found here ==> http://flask.pocoo.org/docs/1.0/api/#flask.Flask.before_first_request
However, it won't get kicked off until someone makes a request to the server but you can do something like this:
import requests
import threading
import time
from flask import Flask
app = Flask(__name__)
#app.before_first_request
def activate_job():
def run_job():
while True:
print("Run recurring task")
time.sleep(3)
thread = threading.Thread(target=run_job)
thread.start()
#app.route("/")
def hello():
return "Hello World!"
def start_runner():
def start_loop():
not_started = True
while not_started:
print('In start loop')
try:
r = requests.get('http://127.0.0.1:5000/')
if r.status_code == 200:
print('Server started, quiting start_loop')
not_started = False
print(r.status_code)
except:
print('Server not yet started')
time.sleep(2)
print('Started runner')
thread = threading.Thread(target=start_loop)
thread.start()
if __name__ == "__main__":
start_runner()
app.run()
Details & Source via Google-fu: https://networklore.com/start-task-with-flask/
I would like to stop my flask server as soon as an unhandled exception occurs.
Here is an example:
from flask import Flask
app = Flask(__name__)
#app.route('/')
def hello_world():
1/0 # argh, exception
return 'Hello World!'
if __name__ == '__main__':
app.run(port=12345)
If you run this and go to localhost:12345, your browser tells you "internal server error" and the python console logs a DivisionByZero exception.
But the server app doesn't crash. Flask wraps your routes into its own error handling and it only prints the exception.
I would like to make the server stop as soon as a route produces an exception. But I didn't find this behaviour in the API. You can specify an errorhandler but that is only to give custom error messages to the client after your route failed.
Stopping Flask requires getting into Werkzeug internals. See http://flask.pocoo.org/snippets/67/
Extracted from a single-user app:
from flask import request
#app.route('/quit')
def shutdown():
...
shutdown_hook = request.environ.get('werkzeug.server.shutdown')
if shutdown_hook is not None:
shutdown_hook()
return Response("Bye", mimetype='text/plain')
The shutdown_hook bit is what you'd need in an exception handler.
from multiprocessing import Process
server = Process(target=app.run)
server.start()
# ...
server.terminate()
server.join()
or if Threaded to run Flask Web Server in background around other python code:
import threading
import ctypes
webserverurl = 127.0.0.1
webserverport = 8080
class thread_with_exception(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
self.interval = 1
self.daemon = True
def __del__(self):
print(f"Thread terminated");
def run(self):
# target function of the thread class
try:
while True:
print(f"{self.name} Waiting for Web")
app.run(host=webserverurl, port=webserverport)
finally:
print('ended')
def get_id(self):
# returns id of the respective thread
if hasattr(self, '_thread_id'):
return self._thread_id
for id, thread in threading._active.items():
if thread is self:
return id
def raise_exception(self):
thread_id = self.get_id()
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread_id), ctypes.py_object(SystemExit))
print(f"{self.name} terminated")
if res > 1:
ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread_id), 0)
print('Exception raise failure')
t1 = thread_with_exception('Web Server 1')
t1.start()
time.sleep(10)
t1.raise_exception()
# del t1
credit to Andrew Abrahamowicz here:
https://code.activestate.com/recipes/496960-thread2-killable-threads
ctypes.c_long( required for Linux else Windows only: https://stackoverflow.com/a/61638782/3426192
other ways to do this here: https://w3guides.com/tutorial/how-to-close-a-flask-web-server-with-python
I'm trying to build a Flask application that has some task running in the background. This task (a worker) uses standard logging module for logging what is going on. I would like to use Server Sent Events to push the log messages directly to the web browser, but I can't get them broadcasted by gevent.
In the following snippet the worker is launched properly, SSEHandler.emit method is called as it should, but the notify function doesn't seem to be executed after I do gevent.spawn.
main.py
import gevent
from gevent.wsgi import WSGIServer
from gevent.queue import Queue
from flask import Flask, Response
import time
import logging
import threading
from worker import Worker
class SSEHandler(logging.Handler):
def __init__(self):
logging.Handler.__init__(self)
self.subscriptions = []
def emit(self, record):
try:
msg = self.format(record)
print "sending", msg
def notify(subs, msg):
print "broadcasting!"
for sub in subs[:]:
sub.put(msg)
gevent.spawn(notify, self.subscriptions, msg)
except (KeyboardInterrupt, SystemExit):
raise
except:
self.handleError(record)
def subscribe(self):
print "subscribed"
q = Queue()
self.subscriptions.append(q)
try:
while True:
result = q.get()
yield "data: %s\n\n"%result
except GeneratorExit: # Or maybe use flask signals
subscriptions.remove(q)
app = Flask(__name__)
handler = SSEHandler()
handler.setLevel(logging.DEBUG)
worker = None
# Client code consumes like this.
#app.route("/")
def index():
debug_template = """
<html>
<head>
</head>
<body>
<h1>Server sent events</h1>
<div id="event"></div>
<script type="text/javascript">
var eventOutputContainer = document.getElementById("event");
var evtSrc = new EventSource("/subscribe");
evtSrc.onmessage = function(e) {
console.log(e.data);
eventOutputContainer.innerHTML = e.data;
};
</script>
</body>
</html>
"""
return(debug_template)
#app.route("/subscribe")
def subscribe():
return Response(handler.subscribe(), mimetype="text/event-stream")
#app.route("/start")
def start():
def run():
global worker
global handler
worker = Worker(handler)
worker.go()
threading.Thread(target=run).start()
return "Going"
if __name__ == "__main__":
app.debug = True
server = WSGIServer(("", 5000), app)
server.serve_forever()
worker.py
import logging
import time
class Worker:
def __init__(self, handler):
self.log = logging.getLogger('sselog.worker.Worker')
self.log.setLevel(logging.DEBUG)
self.log.addHandler(handler)
self.log.info("Initialized")
def go(self):
i = 0
while True:
time.sleep(1)
self.log.info("I'm working so hard %u", i)
i+=1
Well, the problem was that I used plain threads and sleep instead of the gevent stuff. After changing it and/or applying a monkey patch, everything works perfectly.