Question
I've read up some on accessing status from a Celery worker from a Flask application, like in this tutorial, but can you go the other way? Send an interrupt or get introspection into a Celery worker after it's been started?
I've read a bit about signals, but either don't understand them yet or it's not what I'm looking for. Possibly both.
Background
I'm using Celery to kick off a long-running loop that subscribes to an MQTT topic, I'd like to be able to also shut down that process/subscription from another endpoint in my Flask app. What's the best way to do this? Or a way?
Example Code
from flask import Flask
from celery import Celery
import time
app = Flask(__name__)
app.config['CELERY_BROKER_URL'] = 'redis://localhost:6379/0'
app.config['CELERY_RESULT_BACKEND'] = 'redis://localhost:6379/0'
celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
celery.conf.update(app.config)
#celery.task(bind=True)
def test_loop(self):
i=0
running = True
while running:
i = i+1
print "loop running %d" % i
time.sleep(1)
#app.route('/')
def index():
return 'index page'
#app.route('/start')
def start():
global task
task = test_loop.delay()
return "started loop"
#app.route('/stop')
def stop():
global task ### What I'm having trouble with
task.running = False ### How can I interrupt/introspect into the task?
return "stopped loop"
TL/DR
Is there a way to send an interrupt or get introspection into a Celery worker after it's been started? How can I stop a long-running loop started in a Celery Worker from Flask?
My personal thoughts behind this would be to stay away from tasks that run forever.
If you absolutely must abort a task then you can use revoke.
http://docs.celeryproject.org/en/latest/userguide/workers.html#revoke-revoking-tasks
#app.route('/stop')
def stop():
global task
task.revoke(terminate=True, signal='SIGKILL')
return "stopped loop"
Celery may be overkill for your use case but I'm not totally sure what your end goal is so I can't really offer any alternatives.
Related
How I can start flask server in a thread with custom ip (listening the network)?
This line doesn't block the main thread, but it doesn't listen connections from the network.
threading.Thread(target = app.run).start()
When this is used it waits to this thread to finish, and main thread is blocked.
#threading.Thread(target = app.run(host='192.168.1.42')).start()
I have tried to make game, Pygame is running in mainthread and flask server is used to host webpage, which offer joystick for players.
At the moment i can control it with local machine, but not via mobilephone. And if i config the flask with custom ip, the main thread will stop for to wait the server thread.
It's something to do with invoking and referring, but i don't know how to setup the thread with argument, but without invoking.
Whole pycharm project is in GitHUB
Here is the server.py
from flask import Flask,render_template,request
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
import threading
def initServer(controlDataToPyGame): #argument is queue for transferring data to main thread
app = Flask(__name__)
app.debug= False
#app.route('/')
def index():
print("index")
return render_template('index.html')
#app.route('/play/')
def play():
print("play")
return render_template('controller.html')
#app.route("/Control/")
def UP():
x = request.args.get('joyX')
y = request.args.get('joyY')
controlDict = {"name":"ice", "x":x,"y":y}
controlDataToPyGame.put(controlDict)
return ("nothing")
##this doesn't block the main thread, but it doesn't listen connections from the network.
threading.Thread(target = app.run).start()
#when this is used it waits to this thread to finish, and main thread is blocked.
#threading.Thread(target = app.run(host='192.168.1.42')).start()
If you read documentation for Thread then you see args= and kwargs=
threading.Thread(target=app.run, kwargs={'host': '192.168.1.42'}).start()
Using
threading.Thread(target = app.run(host='192.168.1.42')).start()
you simply run app.run() before sending its result to Thread like
result = app.run(host='192.168.1.42')
Thread(target=result).start()
and it runs app.run() in main thread forever - and it never use Thread
I am building a python flask service for which I am trying to setup a timeout for each individual POST request.
As I understand whenever someone sends a post request to my RESTful service, a new thread (virtual or real) starts executing it.
Now in order for my server to serve a lot of requests I want it to return a TIME-OUT response if a process runs for more than a constant time defined for it (TIMEOUT_TIME) set for each POST method and stop the execution of that individual thread.
Can you propose me an abstract scheme that I could implement, using flask-methods?
One way to do it is to run the request processing in a separate process and terminate it if a timeout is exceeded:
#!/usr/bin/env python3
import time
from multiprocessing import Process
from flask import Flask, request, jsonify
app = Flask(__name__)
#app.route('/api/sleep', methods=['POST'])
def sleep():
duration = int(request.args.get('duration', 1))
timeout = float(request.args.get('timeout', 2))
proc = Process(target=process_request, args=(duration,))
proc.start()
proc.join(timeout)
if proc.is_alive():
proc.terminate()
proc.join()
return jsonify(success=False, message='timeout exceeded'), 408
return jsonify(success=True, message='well done')
def process_request(t):
time.sleep(t)
if __name__ == '__main__':
app.run(host='localhost', port=8080, debug=True)
In this example, when a sleep duration is less than a given timeout, a user will get a successful response:
curl -X POST http://localhost:8080/api/sleep?duration=1\&timeout=2
{
"message": "well done",
"success": true
}
Otherwise, the user will get 408 error:
curl -X POST http://localhost:8080/api/sleep?duration=2\&timeout=1
{
"message": "timeout exceeded",
"success": false
}
The problem with this approach is noted in the docs
Note that exit handlers and finally clauses, etc., will not be executed.
It means that the running processes won't be able to clean up before exiting which might cause problems. Another solution is to use a special Joiner thread which will be used to join worker processes or threads later on in the case the timeout is exceeded:
#!/usr/bin/env python3
import time
from queue import Queue
from threading import Thread
from flask import Flask, request, jsonify
class Joiner(Thread):
def __init__(self):
super().__init__()
self.workers = Queue()
def run(self):
while True:
worker = self.workers.get()
if worker is None:
break
worker.join()
app = Flask(__name__)
#app.route('/api/sleep', methods=['POST'])
def sleep():
duration = int(request.args.get('duration', 1))
timeout = int(request.args.get('timeout', 2))
worker = Thread(target=process_request, args=(duration,))
worker.start()
worker.join(timeout)
if worker.is_alive():
joiner.workers.put(worker)
return jsonify(success=False, message='timeout exceeded'), 408
return jsonify(success=True, message='well done')
def process_request(t):
time.sleep(t)
if __name__ == '__main__':
joiner = Joiner()
joiner.start()
app.run(host='localhost', port=8080, debug=True)
joiner.workers.put(None)
joiner.join()
Here, before running the flask server a Joiner thread instance is created and started. Once the server is stopped, we put None into the joiner.workers queue to signal the joiner thread to finish.
I would like to start new Redis workers (as new thread) from a Flask interface.
For this, I have the following function (utils.py):
def start_worker():
listen = ['default']
redis_url = os.getenv('REDISTOGO_URL', 'redis://localhost:6379')
conn = redis.from_url(redis_url)
with Connection(conn):
print('STARTING WORKER..')
worker = Worker(Queue('default'), connection=conn, name='foo2')
worker.work()
and the following call in routes.py:
#from threading import Thread #import threading # for starting worker as new thread)
from concurrent.futures import ThreadPoolExecutor
executor = ThreadPoolExecutor(max_workers=2)
#app.route('/')
#app.route('/index', methods=['GET','POST'])
#login_required
def index():
form = WorkerForm()
if form.validate_on_submit():
#w = Thread(target=utils.start_worker)
#w.daemon = True
#w.start()
executor.submit(utils.start_worker)
return redirect(url_for('index'))
Now, when I run the function start_worker() manually from the console, I see the worker registering.
When calling the function through Flask, I see the comment thrown ("STARTING WORKER.."). But no worker is registering.
Initially, I wanted to start it as normal Thread (commented code), but this results in ValueError: signal only works in main thread.
What may I be missing here?
Thanks
I'm working on writing a Python web app with Flask using Azure to host. I need to do some background work. When I test the app locally, everything works great. However, as soon as I push the update to Azure, it stops functioning. Right now, I have a multithreading.Process set up, and based on the log files, Azure isn't starting another process. Here is the relevant parts of my code:
#task queue and comm pipes
tasks = Queue()
parent_pipe, child_pipe = Pipe()
def handle_queue_execution(tasks, pipe):
logging.info("starting task queue handler")
while True:
if pipe.recv():
logging.debug("preparing to get task from queue")
task = tasks.get()
args = tasks.get()
logging.debug("executing task %s(%s)", get_fn_name(task), clean_args(args))
task(args)
logging.debug("task %s(%s) executed successfully", get_fn_name(task), clean_args(args))
queue_handler = Process(target=handle_queue_execution, args=(tasks, child_pipe,))
queue_handler.daemon = True
if __name__ == '__main__':
queue_handler.start()
There are a few semi-related questions I have on this:
1) Why won't Azure start another process?
You'll note that the handle_queue_execution function begins with a logger call. That message doesn't appear in the log file when hosted on Azure, nor do the queued tasks appear to execute. Again, both aspects of this work as expected when running on localhost.
2) Is there a better way?
I'm fairly new to both Python and Azure, so if there's a better way to do this type of task handling, I'm open to hear about it. I've looked into using something like Celery, but I can't figure out how to set it up, and I'd prefer to make my own implementation as I'm learning these new skills.
Thanks very much.
Python has multiple other ways to start new processes. Threading would most likely be the easiest here.
#task queue and comm pipes
import threading
tasks = Queue()
parent_pipe, child_pipe = Pipe()
def handle_queue_execution(tasks, pipe):
logging.info("starting task queue handler")
while True:
if pipe.recv():
logging.debug("preparing to get task from queue")
task = tasks.get()
args = tasks.get()
logging.debug("executing task %s(%s)", get_fn_name(task), clean_args(args))
task(args)
logging.debug("task %s(%s) executed successfully", get_fn_name(task), clean_args(args))
T1 = threading.Thread(target=handle_que_execution, args=(tasks, child_pipe,))
if __name__ == '__main__':
T1.start()
I just need to run an async task once a route is called. The response should be returned once the task is initiated. How can I do it without using RabbitMQ and Celery in python.
#app.route('/preprocess')
def pre_process():
thr = threading.Thread(target=PreProcessor().initiate_preprocess_task())
thr.start()
return "Pre-process task initiated!"
if __name__ == '__main__':
app.run(threaded=True, use_reloader=True)
You can use BackgroundScheduler and start this task when the route is called and stop it in the background task.
Example of code :
from apscheduler.schedulers.background import BackgroundScheduler
sched = BackgroundScheduler()
sched.add_job(fetch_data_coronavirus, 'interval', minutes=1, id='my_job_id')
# to start the background task
sched.start()
# to stop the background task
sched.remove_job('my_job_id')