Flask timeout when serve using gunicorn - python

I have an app that will convert audio file to text. Using flask and flask-socketio. It works perfectly when I run it using: "python run.py", but when I run it using: "gunicorn -k eventlet -b 0.0.0.0:5000 run:app" it will stop on the part where it calls the google speech to text api in audio.py file.
These are the current codes right now.
run.py:
from ats import socketio, app, db
if __name__ == '__main__':
db.create_all()
socketio.run(app, host='0.0.0.0', port=5001, debug=True)
init.py
import logging, json
from flask import Flask, jsonify, render_template, request
from flask_socketio import SocketIO, emit, send
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmall
app = Flask(__name__, instance_relative_config=True, static_folder="templates/static", template_folder="templates")
# Create db instance
db = SQLAlchemy(app)
ma = Marshmallow(app)
#app.route('/')
def index():
return render_template('index.html');
# import models
from ats import models
# set up CORS
CORS(app)
socketio = SocketIO(app, cors_allowed_origins='*', async_mode='eventlet')
# import blueprints
from ats.product.product import product_blueprint
# register blueprints
app.register_blueprint(product_blueprint, url_prefix='/api/product')
from ats import error_handlers
product.py
import os
import math
import eventlet
from os.path import join
from flask import Blueprint, request, jsonify, abort
from ats.utils import audio as AUDIO
product_blueprint = Blueprint('product', __name__)
#product_blueprint.route('/add', methods=['post'])
def addProduct():
try:
data = request.form
foldername = data['name']
scriptFile = request.files['script']
audioFile = request.files['audio']
# save the script and audio file to uploads folder
FILE.createFolder(foldername)
FILE.save(foldername, scriptFile)
FILE.save(foldername, audioFile)
# list the files in the uploads
audioFiles = FILE.getAudioFileList(foldername)
fileCount = len(audioFiles)
currentFile = 1
# ============ speech to text =============
for file in audioFiles:
recognizedText = AUDIO.convert(foldername, file)
# save to database
newAudio = {
'name': file,
'recognizedText': recognizedText,
'length': duration,
}
Audio.add(newAudio)
# emit event to update the client about the progress
percent = math.floor((currentFile / float(fileCount) ) * 100)
emit('upload_progress', {'data': percent}, room=data['sid'], namespace='/')
eventlet.sleep()
currentFile += 1
# Delete the files in uploads folder
FILE.delete(foldername)
return jsonify({'data': None, 'message': 'Product was added.', 'success': True}), 200
except Exception as e:
abort(500, str(e))
audio.py
import os
from ats import app
# Imports the Google Cloud client library
from google.cloud import speech
from google.cloud.speech import enums
from google.cloud.speech import types
# Instantiates a client
client = speech.SpeechClient()
def convert(foldername, filename):
try:
file = os.path.join(app.config['UPLOAD_FOLDER'], foldername, filename)
# Loads the audio into memory
with io.open(file, 'rb') as audio_file:
content = audio_file.read()
audio = types.RecognitionAudio(content=content)
config = types.RecognitionConfig(
encoding=enums.RecognitionConfig.AudioEncoding.LINEAR16,
sample_rate_hertz=48000,
language_code='ja-JP')
# Call speech in the audio file
response = client.recognize(config, audio) # The code will stop here, that results to worker timeout in gunicorn
return response
except Exception as e:
raise e
I've been searching solution for almost a week but I still couldn't find one. THank you for you're help guys.

When you run your application directly using python run.py there is no timeout applied the application takes whatever time it needs to process, however when you run your application using Gunicorn, the default timeout is 30 seconds which means that you will get a timeout error incase your application does not respond within 30 seconds. To avoid this you can increase the default timeout set by Gunicorn by adding --timeout <timeinterval-in-seconds>
The following command sets the timeout to 10 mins
gunicorn -k eventlet -b 0.0.0.0:5000 --timeout 600 run:app

It's working now, by running it using uwsgi instead of gunicorn. Here's the config, service and nginx
ats.ini
[uwsgi]
module = wsgi:app
master = true
processes = 1
socket = ats.sock
chmod-socket = 660
vacuum = true
die-on-term = true
/etc/systemd/system/ats.service
[Unit]
Description=uWSGI instance to serve ats
After=network.target
[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/user/ats
Environment="PATH=/home/user/ats/env/bin"
ExecStart=/home/user/ats/env/bin/uwsgi --ini ats.ini --gevent 100
[Install]
WantedBy=multi-user.target
nginx
server {
listen 80;
server_name <ip_address or domain>;
access_log /var/log/nginx/access.log;
location / {
include uwsgi_params;
uwsgi_pass unix:/home/user/ats/ats.sock;
proxy_set_header Connection "Upgrade";
client_max_body_size 200M;
}
location /socket.io {
include uwsgi_params;
uwsgi_pass unix:/home/user/ats/ats.sock;
proxy_set_header Connection "Upgrade";
}
}
Thank you guys

Google cloud python had some conflict with gevent. I found out from this thread that in order for them to work, you need to add the following in the beginning of init.py:
from gevent import monkey
monkey.patch_all()
import grpc.experimental.gevent as grpc_gevent
grpc_gevent.init_gevent()

I met this problem too today, finally I found that the bug caused by proxy setting. at first, I set my proxy is "",
os.environ['http_proxy'] = ""
os.environ['https_proxy'] = ""
and I get the error about time out in request, after I comment the code and it works
# os.environ['http_proxy'] = ""
# os.environ['https_proxy'] = ""
I think it is not an error about gunicore timeout default setting, it is about system proxy setting.

Related

FastAPI server running on AWS App Runner fails after 24 hours

I have a FastAPI server configured with Gunicorn, deployed on AWS App Runner. When I try to access the endpoint, it works perfectly, however, after 24 hours, when I try to access the same endpoint, I get a 502 bad gateway error, and nothing is logged on cloudWatch after this point, until I redeploy the application, then it starts working fine again.
I suspect this has to do with my Gunicorn configuration itself which was somehow shutting down my API after some time, and not AWS App Runner, but I have not found any solution. I have also shown my Gunicorn setup below. Any hep will be appreciated.
from fastapi import FastAPI
import uvicorn
from fastapi.middleware.cors import CORSMiddleware
from gunicorn.app.base import BaseApplication
import os
import multiprocessing
api = FastAPI()
def number_of_workers():
print((multiprocessing.cpu_count() * 2) + 1)
return (multiprocessing.cpu_count() * 2) + 1
class StandaloneApplication(BaseApplication):
def __init__(self, app, options=None):
self.options = options or {}
self.application = app
super().__init__()
def load_config(self):
config = {
key: value for key, value in self.options.items()
if key in self.cfg.settings and value is not None
}
for key, value in config.items():
self.cfg.set(key.lower(), value)
def load(self):
return self.application
#api.get("/test")
async def root():
return 'Success'
if __name__ == "__main__":
if os.environ.get('APP_ENV') == "development":
uvicorn.run("api:api", host="0.0.0.0", port=2304, reload=True)
else:
options = {
"bind": "0.0.0.0:2304",
"workers": number_of_workers(),
"accesslog": "-",
"errorlog": "-",
"worker_class": "uvicorn.workers.UvicornWorker",
"timeout": "0"
}
StandaloneApplication(api, options).run()
I had the same problem. After a lot of trial and error, two changes seemed to resolve this for me.
Set uvicorn --timeout-keep-alive to 65. For gunicorn this param is --keep-alive. I'm assuming the Application Load Balancer throws 502 if uvicorn closes the tcp socket before ALB does.
Change the App Runner health check to use HTTP rather than TCP ping to manage container recycling. Currently the AWS UI doesn't allow you to make this change. You will have to do this using aws cli. Use any active URL path for ping check - in your case /test
aws apprunner update-service --service-arn <arn> --health-check-configuration Protocol=HTTP,Path=/test
#2 might just be enough to resolve the issue.

wsgi not importing system environment variables in Flask

If I run flask app directly, I am getting environment variables, using wsgi I am not getting system variables,
wsgi.py
from myproject import app
if __name__ == "__main__":
app.run()
myproject.ini
[uwsgi]
module = wsgi:app
master = true
processes = 5
#protocol = httpa
protocol = http
socket = 0.0.0.0:8443
#shared-socket = 0.0.0.0:8443
buffer-size=32768
#chmod-socket = 660
#vacuum = true
#https = =0,foobar.crt,foobar.key,HIGH
die-on-term = true
enable-threads = true
vacuum = true
req-logger = file:/var/log/uwsgi/app/cart-req.log
logger = file:/var/log/uwsgi/app/cart-err.log
myproject.py
import logging, re, subprocess, json, hashlib, os, jwt, mysql.connector, datetime
from flask import Flask, request, jsonify
from flask import current_app
from flask_restful import Resource, Api
import socket, logging, sys, os, pkgutil, importlib, inspect, logging.handlers
from importlib import import_module
from logging.config import dictConfig
class GetInput(Resource):
def get(self):
output = os.environ.get("MYSYSVAR")
if output is None:
return {'message': 'System Enviroment Variable not found'}, 404
return {'user': output}, 200
cli = sys.modules['flask.cli']
cli.show_server_banner = lambda *x: None
app = Flask(__name__)
api = Api(app)
api.add_resource(GetInput, '/getinput')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8443, debug=True)
This is Linus OS (Cent OS), I set enviroment variable like below
added entry in /etc/profile
export MYSYSVAR=4
I also tried adding entry in below file
/etc/environment
MYSYSVAR=4
source /etc/environment
Not sure, it is not working If I run with web gateway that is wsgi, but it works if I run directly myproject.py
Note: system inbuilt variable like USER working
Can anyone help how to fix it.
you need to add your env file in the virtualhost like:
<VirtualHost *:443>
...
SetEnv /dir/to/app/folder/.env
...
</VirtualHost>

how can I connect, from a flask app, to a remote elasticsearch cluster on aws?

I have an elasticsearch cluster running on an EC2 server. I get a variety of different error mesages when I try to connect.
Currently, in the elasticsearch.yml file all the transport items are commented out but I have tried:
network.host: 0.0.0.0
and
network.host: ec2-xx-xxx-xxx.aws.instance.com
In my flask app the code is as follows:
from datetime import datetime
from flask import Flask, jsonify, request
from elasticsearch import Elasticsearch
#es = Elasticsearch()http://34.245.51.240/
es = Elasticsearch(['ec2-34-xxx-xx-240.eu-west-1.compute.amazonaws.com','9200'])
#es = Elasticsearch(['34.245.51.240','9200'])
application = Flask(__name__)
#application.route('/', methods=['GET'])
def index():
#results = es.get(index='contents', doc_type='title', id='my-new-slug')
#return jsonify(results['_source'])
doc = {
'author': 'kimchy',
'text': 'Elasticsearch: cool. bonsai cool.',
'timestamp': datetime.now(),
}
res = es.index(index="test-index", doc_type='tweet', id=1, body=doc)
print(res['res'])
return res
#application.run(port=5000, debug=True)
if __name__ == '__main__':
application.debug = True
application.run()
I have googled multiple times and tried every possible configuration that I can find.
What is the correct way to achieve this?
Thank you.
I hope this will help other people.
elasticsearch defaults to Port 9200 so it is necessary to open such a Port on the EC2 server as such:
CustomTCP TCP 9200 0.0.0.0
This is done by editing the security group wizard that set up security groups when you set up the server.
Then in your Python application the connection string is:
es = Elasticsearch("http://00.111.222.33") //the public IP you can see on your EC2 dashboard
That's it. Hours of anguish and so simple.

Server not responding although the webhook to Telegram Bot API is set up

I run this script on Pythonanywhere. Webhook gets set up, but when I try using the webhook_handler it gives me "Bad Request The browser (or proxy) sent a request that this server could not understand". What am I missing?
import sys
import os
import time
sys.path.append(os.path.join(os.path.abspath('.'), 'path/to/virtualenv/'))
from flask import Flask, request
import telegram
# CONFIG
TOKEN = '<token>'
HOST = 'username.pythonanywhere.com' # Same FQDN used when generating SSL Cert
PORT = 8443
CERT = "ssl_certs/cert.pem"
CERT_KEY = "ssl_certs/key.pem"
bot = telegram.Bot(TOKEN)
app = Flask(__name__)
#app.route('/')
def hello():
return '<h1> BITCONNECT!!! </h1>'
#app.route('/' + TOKEN, methods=['POST'])
def webhook_handler():
update = telegram.Update.de_json(request.get_json(force=True), bot)
bot.sendMessage(chat_id=update.message.chat.id, text='Hello, there')
return '<h1> OK </h1>'
def setwebhook():
bot.setWebhook(url = "https://%s/%s" % (HOST, TOKEN), certificate = open(CERT, 'rb'))
if __name__ == '__main__':
context = (CERT, CERT_KEY)
setwebhook()
time.sleep(5)
app.run(host = '0.0.0.0', port = PORT, debug = True)
And here's the WSGI configuration file:
import sys
# add your project directory to the sys.path
project_home = u'/home/username/project'
if project_home not in sys.path:
sys.path = [project_home] + sys.path
# import flask app but need to call it "application" for WSGI to work
from main import app as application

Flask session value not persisting on shared hosting

I developed a Flask application on localhost (running Python 3). It works, but when transferred to my shared hosting account (running Python 2), it doesn't. I fixed all the issues related to Python versions. But the session is not working. Its value does not persists between requests.
I tried to recreate the problem with simpler code (test.py), the commented out part is how session is configured in my application:
import sys
sys.path.insert(0, '/home/user_name/public_html')
from flask import Flask, request, session
from flask.ext.session import Session
from tempfile import mkdtemp
from cs50 import SQL
from constants import *
app = Flask(__name__)
#app.config["SESSION_TYPE"] = "filesystem"
#app.config["SESSION_PERMANENT"] = False
#app.config["SESSION_FILE_DIR"] = mkdtemp()
Session(app)
#app.route('/set/')
def set():
session['key'] = 'value'
return 'ok'
#app.route('/get/')
def get():
return "{}".format(session.get('key'))
If you go to /set/, you will see ok. But on /get/ you will see None.
And here's my CGI file (which I need only for shared hosting, not for localhost):
#!/home/user_name/public_html/cgi-bin/flask/bin/python
import sys
sys.path.insert(0, '/home/user_name/public_html')
# Enable CGI error reporting
import cgitb
cgitb.enable()
import os
from wsgiref.handlers import CGIHandler
app = None
try:
import test
app = test.app
except Exception, e:
print "Content-type: text/html"
print
cgitb.handler()
exit()
#os.environ['SERVER_PORT'] = '80'
#os.environ['REQUEST_METHOD'] = 'POST'
class ScriptNameStripper(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
environ['SCRIPT_NAME'] = ''
return self.app(environ, start_response)
app = ScriptNameStripper(app)
try:
CGIHandler().run(app)
except Exception, e:
print "Content-type: text/html"
print
cgitb.handler()
exit()
In case .htaccess file is needed:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /cgi-bin/mas.cgi/$1 [L]
Googling didn't help, and other similar questions on Stackoverflow don't fix it. Any solution/help is welcomed. Thanks!
I am still not sure if session was being written or not (as #Danila Ganchar pointed out), but only commenting out the 3rd configuration line solved the issue.
So the changes made in test.py are:
app.config["SESSION_TYPE"] = "filesystem"
app.config["SESSION_PERMANENT"] = False
#app.config["SESSION_FILE_DIR"] = mkdtemp()
I guess mkdtemp() wasn't working as it was on localhost.

Categories

Resources