Flask socketio debug with eventlet and Redis spawns extra greenthreads? - flask

I'm trying to put together a simple Flask / socketio / eventlet server that subscribes to Redis events. The behavior I'm seeing is that with Flask debug enabled, every time Werkzeug detects changes and restarts socketio, another one of my redis listeners is started as well (except the old listener doesn't exit).
Here's a working version with all of the socketio handlers removed:
import json
from flask import Flask, render_template
from flask_socketio import SocketIO, emit
from flask.ext.redis import FlaskRedis
import eventlet
eventlet.monkey_patch()
with open('config/flask.json') as f:
config_flask = json.load(f)
app = Flask(__name__, static_folder='public', static_url_path='')
app.config.update(
DEBUG= True,
PROPAGATE_EXCEPTIONS= True,
REDIS_URL= "redis://localhost:6379/0"
)
redis_cache = FlaskRedis(app)
socketio = SocketIO(app)
#app.route('/')
def index():
cache = {}
return render_template('index.html', **cache)
def redisReader():
print 'Starting Redis subscriber'
pubsub = redis_cache.pubsub()
pubsub.subscribe('msg')
for msg in pubsub.listen():
print '>>>>>', msg
def runSocket():
print "Starting webserver"
socketio.run(app, host='0.0.0.0')
if __name__ == '__main__':
pool = eventlet.GreenPool()
pool.spawn(redisReader)
pool.spawn(runSocket)
pool.waitall()
Throw in some manual redis-cli publishing (PUBLISH msg himom)
This produces the following output:
Starting Redis subscriber
Starting webserver
* Restarting with stat
>>>>> {'pattern': None, 'type': 'subscribe', 'channel': 'msg', 'data': 1L}
Starting Redis subscriber
Starting webserver
* Debugger is active!
* Debugger pin code: 789-323-740
(22252) wsgi starting up on http://0.0.0.0:5000
>>>>> {'pattern': None, 'type': 'subscribe', 'channel': 'msg', 'data': 1L}
>>>>> {'pattern': None, 'type': 'message', 'channel': 'msg', 'data': 'himom'}
>>>>> {'pattern': None, 'type': 'message', 'channel': 'msg', 'data': 'himom'}
Why is the Redis listener getting started multiple times? If I make changes and save them, Werkzeug will start another one every time. How do I deal with this correctly?
Here's a list of the packages involved and their versions:
Python 2.7.6
Flask 0.10.1
Werkzeug 0.11.4
eventlet 0.18.4
greenlet 0.4.9
Flask-Redis 0.1.0
Flask-SocketIO 2.2
** UPDATE **
I now have a partial solution. Everything above stays the same, except the pool behavior has been moved into Flask's 'before_first_request' function:
def setupRedis():
print "setting up redis"
pool = eventlet.GreenPool()
pool.spawn(redisReader)
def runSocket():
print "Starting Webserver"
socketio.run(app, host='0.0.0.0')
if __name__ == '__main__':
app.before_first_request(setupRedis)
print app.before_first_request_funcs
runSocket()
The remaining issue is that 'before_first_request' does not handle the case where there are existing websockets, but that is a separate question.

Add simple print(os.getpid()) and observe ps aux. You should notice there are two Python processes. Change DEBUG=False, and the "problem" is not reproducing, also there is one Python process now.
So the problem is that your code creates redisReader no matter if it's a "worker manager" or an actual "request handler" process. So, don't create and start that pool unconditionally. Consult Werkzeug documentation where is your "application init" event, and only start redisReader there.

The solution here was to put my threading into a function called by Flask:
def setupRedis():
pool = eventlet.GreenPool()
pool.spawn(redisReader)
...
app.before_first_request(setupRedis)
This solved the extra threads left behind on Werkzeug restarts.

Related

Flask MQTT high CPU usage

I'm using Flask on a project on an embedded system and I'm having performance issues. I'm running gunicorn with one eventlet worker by running:
gunicorn -b 0.0.0.0 --worker-class eventlet -w 1 'app:create_app()'
The problem I'm facing is that, when the MQTT messages start to pour with more cadence, the application starts to use almost all the CPU I have available. My initial thought was that I was handling the messages not ideally but, I even took out my handler, and just receive the messages, and the problem still persists.
I have another python application that subscribes to the same information with the paho client and this is not an issue, so I'm assuming I'm missing something on my Flask application and not the information itself.
My code is:
import eventlet
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, current_user
from flask_socketio import SocketIO
from flask_mqtt import Mqtt
eventlet.monkey_patch()
#USERS DB
db_alchemy = SQLAlchemy()
#socketIO
socketio = SocketIO(cors_allowed_origins="*", async_mode='eventlet')
# MQTT
mqtt_client = Mqtt()
'''
APPLICATION CREATION
'''
def create_app():
app = Flask(__name__)
if app.config["ENV"] == "production":
app.config.from_object("config.ProductionConfig")
else:
app.config.from_object("config.DevelopmentConfig")
#USERS DB
db_alchemy.init_app(app)
#LoginManager
login_manager = LoginManager()
login_manager.login_view = "auth.login"
login_manager.init_app(app)
#SOCKETIO
socketio.init_app(app)
#FLASK-MQTT
app.config['MQTT_BROKER_URL'] = 'localhost' #
app.config['MQTT_BROKER_PORT'] = 1883
app.config['MQTT_KEEPALIVE'] = 20
app.config['MQTT_TLS_ENABLED'] = False
mqtt_client.init_app(app)
return app
#MQTT
#mqtt_client.on_connect()
def mqtt_on_connect():
mqtt_client.subscribe('testTopic/#', 0)
#mqtt_client.on_disconnect()
def mqtt_on_disconnect():
loggerMqtt.warning(' > Disconnected from broker')
#mqtt_client.on_subscribe()
def mqtt_on_subscribe(client, obj, mid, granted_qos):
pass
#mqtt_client.on_message()
def mqtt_on_message(client, userdata, message):
pass
#mqtt_topicSplitter(client, userdata, message)
As you can see my handler mqtt_topicSplitter is commented but I'm still having performance issues. I've tried adding an sleep command [eventlet.sleep(0.1)] on the on_message handler which solved the CPU consumption problem but resulted on my application being constantly kicked from the broker.
I also tried using other workers (gevent, asyncio, ..) without success. Using the Flask development server is not an option, since is not recommended for production.
I'm sorry if I wasn't clear, but I'm not an expert, please feel free to ask me any questions if needed.
Thanks in advance.

how to add job by flask apscheduler api using postman

from flask import Flask
from flask_apscheduler import APScheduler
class Config(object):
JOBS = [
{
'id': 'job5',
'func': 'f_s_api.view:job1',
'trigger': 'interval',
'seconds': 50
}
]
SCHEDULER_API_ENABLED = True
def job1():
print('job add')
if __name__ == '__main__':
app = Flask(__name__)
app.config.from_object(Config())
scheduler = APScheduler()
scheduler.init_app(app)
scheduler.start()
app.run(debug= True , port= 8080)
output
Serving Flask app "view" (lazy loading)
Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
Debug mode: on
Running on http://127.0.0.1:8080/ (Press CTRL+C to quit)
Restarting with stat
Debugger is active!
Debugger PIN: 135-565-985
job add
ob add
run the flask and open the postman and http://localhost:5000/scheduler/jobs this flask apscheduler api url for add job in the form of post request then in body-->row select text type as JSON and the send the request.

concurrent celery tasks execute and stores result but .get not working

I have written a Celery Task class like this:
myapp.tasks.py
from __future__ import absolute_import, unicode_literals
from .services.celery import app
from .services.command_service import CommandService
from exceptions.exceptions import *
from .models import Command
class CustomTask(app.Task):
def run(self, json_string, method_name, cmd_id: int):
command_obj = Command.objects.get(id=cmd_id) # type: Command
try:
val = eval('CommandService.{}(json_string={})'.format(method_name, json_string))
status, error = 200, None
except Exception as e:
auto_retry = command_obj.auto_retry
if auto_retry and isinstance(e, CustomError):
command_obj.retry_count += 1
command_obj.save()
return self.retry(countdown=CustomTask._backoff(command_obj.retry_count), exc=e)
elif auto_retry and isinstance(e, AnotherCustomError) and command_obj.retry_count == 0:
command_obj.retry_count += 1
command_obj.save()
print("RETRYING NOW FOR DEVICE CONNECTION ERROR. TRANSACTION: {} || IP: {}".format(command_obj.transaction_id,
command_obj.device_ip))
return self.retry(countdown=command_obj.retry_count*2, exc=e)
val = None
status, error = self._find_status_code(e)
return_dict = {"error": error, "status_code": status, "result": val}
return return_dict
#staticmethod
def _backoff(attempts):
return 2 ** attempts
#staticmethod
def _find_status_code(exception):
if isinstance(exception, APIException):
detail = exception.default_detail if exception.detail is None else exception.detail
return exception.status_code, detail
return 500, CustomTask._get_generic_exc_msg(exception)
#staticmethod
def _get_generic_exc_msg(exc: Exception):
s = ""
try:
for msg in exc.args:
s += msg + ". "
except Exception:
s = str(exc)
return s
CustomTask = app.register_task(CustomTask())
The Celery App definition:
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery, Task
from django.conf import settings
# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myapp.settings')
_celery_broker = settings.CELERY_BROKER <-- my broker is amqp://username:password#localhost:5672/myhost
app = Celery('myapp', broker=_celery_broker, backend='rpc://', include=['myapp.tasks', 'myapp.controllers'])
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks(['myapp'])
app.conf.update(
result_expires=4800,
task_acks_late=True
)
my init.py the tutorial recommended:
from .celery import app as celery_app
__all__ = ['celery_app']
The controller that is running the task:
from __future__ import absolute_import, unicode_literals
from .services.log_service import LogRunner
from myapp.services.command_service import CommandService
from exceptions.exceptions import *
from myapp.services.celery import app
from myapp.services.tasks import MyTask
from .models import Command
class MyController:
def my_method(self, json_string):
<non-async set up stuff here>
cmd_obj = Command.objects.create(<stuff>) # type: Command
task_exec = MyTask.delay(json_string, MyController._method_name, cmd_obj.id)
cmd_obj.task_id = task_exec
try:
return_dict = task_exec.get()
except Exception as e:
self._logger.error("ERROR: IP: {} and transaction: {}. Error Type: {}, "
"Celery Error: {}".format(ip_addr, transaction_id, type(e), e))
status_code, error = self._find_status_code(e)
return_dict = {"error": error, "status_code": status_code, "result": None}
return return_dict
**So here is my issue: **
When I run this Django controller by hitting the view with one request, one after the other, it works perfectly fine.
However, the external service I am hitting will throw an error for 2 concurrent requests (and that is expected - that is ok). Upon getting the error, I retry my task automatically.
Here is the weird part
Upon retry, the .get() I have in my controller stops working for all concurrent requests. My controller just hangs there! And I know that celery is actually executing the task! Here is logs from the celery run:
[2018-09-25 19:10:24,932: INFO/MainProcess] Received task: myapp.tasks.MyTask[bafd62b6-7e29-4c39-86ff-fe903d864c4f]
[2018-09-25 19:10:25,710: INFO/MainProcess] Received task: myapp.tasks.MyTask[8d3b4279-0b7e-48cf-b45d-0f1f89e213d4] <-- THIS WILL FAIL BUT THAT IS OK
[2018-09-25 19:10:25,794: ERROR/ForkPoolWorker-1] Could not connect to device with IP <some ip> at all. Retry Later plase
[2018-09-25 19:10:25,798: WARNING/ForkPoolWorker-1] RETRYING NOW FOR DEVICE CONNECTION ERROR. TRANSACTION: b_txn || IP: <some ip>
[2018-09-25 19:10:25,821: INFO/MainProcess] Received task: myapp.tasks.MyTask[8d3b4279-0b7e-48cf-b45d-0f1f89e213d4] ETA:[2018-09-25 19:10:27.799473+00:00]
[2018-09-25 19:10:25,823: INFO/ForkPoolWorker-1] Task myapp.tasks.MyTask[8d3b4279-0b7e-48cf-b45d-0f1f89e213d4] retry: Retry in 2s: AnotherCustomError('Could not connect to IP <some ip> at all.',)
[2018-09-25 19:10:27,400: INFO/ForkPoolWorker-2] executed command some command at IP <some ip>
[2018-09-25 19:10:27,418: INFO/ForkPoolWorker-2] Task myapp.tasks.MyTask[bafd62b6-7e29-4c39-86ff-fe903d864c4f] succeeded in 2.4829552830196917s: {'error': None, 'status_code': 200, 'result': True}
<some command output here from a successful run> **<-- belongs to task bafd62b6-7e29-4c39-86ff-fe903d864c4f**
[2018-09-25 19:10:31,058: INFO/ForkPoolWorker-2] executed some command at IP <some ip>
[2018-09-25 19:10:31,059: INFO/ForkPoolWorker-2] Task command_runner.tasks.MyTask[8d3b4279-0b7e-48cf-b45d-0f1f89e213d4] succeeded in 2.404364461021032s: {'error': None, 'status_code': 200, 'result': True}
<some command output here from a successful run> **<-- belongs to task 8d3b4279-0b7e-48cf-b45d-0f1f89e213d4 which errored and retried itself**
So as you can see, the task does run on celery! It's just that the .get() I have in my controller is unable to pick these results back up - regardless of successful tasks or the erroneous tasks.
Often times, the error I get when running concurrent requests Error: "Received 0x50 while expecting 0xce". What is that? what does that mean? Again, weirdly enough, all this works when doing one request after another without Django handling multiple incoming requests. Although, I haven't been able to retry for single requests.
The RPC backend (which is what get is waiting for) is designed to fail if it is used more than once or after a celery restart.
a result can only be retrieved once, and only by the client that initiated the task. Two different processes can’t wait for the same result.
The messages are transient (non-persistent) by default, so the results will disappear if the broker restarts. You can configure the result backend to send persistent messages using the result_persistent setting.
So what looks like it is happening is that the exception causes celery to stop and break its rpc connection with the calling controller. Given your use case, it may make more sense to use a permanent results backend like redis or a database.

How to start Flask and Flask-SocketIO via the command line flask run

I started with an application that has several webservices defined. I was able to start the application via flask run on the command line. Afterwards, I integrated flask-sckoetio (i.e. I added the lines from flask_socketio import SocketIO, emit and socketio = SocketIO(app)) and now I'm not able anymore to start the server via flask run.
from flask import Flask, request, abort
from flask_socketio import SocketIO, emit
app = Flask(__name__)
socketio = SocketIO(app)
#app.route('/do_sth', methods=['POST'])
def do_sth():
return ""
I get the following message on the console:
* Serving Flask-SocketIO app "webservices.py"
* Forcing debug mode off
WebSocket transport not available. Install eventlet or gevent and gevent-websocket for improved perform
ance.
c:\program files\python36\lib\site-packages\flask_socketio\__init__.py:496: Warning: Silently ignoring
app.run() because the application is run from the flask command line executable. Consider putting app.
run() behind an if __name__ == "__main__" guard to silence this warning.
use_reloader=use_reloader, **kwargs)
So I updated my code to this:
from flask import Flask, request, abort
from flask_socketio import SocketIO, emit
app = Flask(__name__)
socketio = SocketIO(app)
#app.route('/do_sth', methods=['POST'])
def do_sth():
return ""
if __name__ == '__main__':
socketio.run(app)
But I still get the same error message and the server doesn't start. However, if I just execute the script everything works. But why is flask run not possible anymore?
It happens because of variable __name__ is equal to "__main__" only when the file called directly with a command like python file.py. But your file was imported and his __name__ variable is setted to name of a module which import them.
Solution:
Just delete string if __name__ == "__main__":
Did you install eventlet or gevent, which are mentioned in the error message?
They are also listed under requirements in flask-socketIO documentation.
Try installing them first.
After installing one of them, you can just use flask run to start your application.

Invalid request block size when using SocketIO with Flask and uWSGI

I'm trying to run a Flask application with SocketIO using uWSGI and gevent.
uwsgi --gevent 10 --socket :5000 --module run
However, I get the following error:
invalid request block size: 21573 (max 4096)...skip
This is my code:
from gevent import monkey
monkey.patch_all()
from flask import Flask, render_template, session, request
from flask.ext.socketio import SocketIO, emit, disconnect
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
application = app
socketio = SocketIO(app)
#app.route('/')
def index():
session['user'] = '1'
return render_template('index.html', name='simon')
#socketio.on('my event', namespace='/test')
def test_message(message):
emit('my response', {'data': message['data']})
#socketio.on('connect', namespace='/test')
def test_connect():
emit('my response', {'data': 'Connected %s' % session['user']})
#socketio.on('disconnect', namespace='/test')
def test_disconnect():
print('Client disconnected')
if __name__ == '__main__':
app.debug = True
socketio.run(app)
The problem is that you are using binary uwsgi protocol but accessing your server via http protocol. Try replacing --socket with --http-socket. See also https://stackoverflow.com/a/32894820/179581
My understanding is that the gevent support in uWSGI does not allow a custom gevent server class to be used, uWSGI provides its own. Unfortunately gevent-socketio needs its own server, which is subclassed from gevent's, so I think it is currently not possible to use uWSGI with Flask-SocketIO or gevent-socketio.
See the Flask-SocketIO documentation for alternatives.