Python3 threading with uWSGI - python

I wasted many time but couldn't find a solution.
If i use threads in my app deployed with uwsgi, they aren't sync.
Here simple code for an example(wsgi.py):
from time import sleep
import threading
i = 0
def daemon():
global i
while True:
i += 1
print(i)
sleep(3)
th = threading.Thread(target=daemon, args=())
th.start()
def application(environ, start_response):
start_response('200 OK', [('Content-Type','text/html')])
return [str(i).encode()]
And when I run this app the i increases in log, but I always get 1 when a make request from browser.(Or get 0 if I move sleep(3) before i first increment)
I tried uwsgi.thread decorator, but got the same result.
uwsgi config:
[uwsgi]
socket = 127.0.0.1:3034
plugins-dir = /srv/uwsgi
plugin = python34
uid = py3utils
gid = py3utils
chdir = /srv/python/3/py3utils/tht/app/
wsgi-file = wsgi.py
enable-threads = true
daemonize = %(chdir)/../uwsgi.log
master = true
die-on-term = true
touch-reload = ../uwsgi_restart.txt
*sorry for my English

This happens because after importing your application the master process forks into a worker:
spawned uWSGI master process (pid: 7167)
spawned uWSGI worker 1 (pid: 7169, cores: 1)
spawned uWSGI http 1 (pid: 7170)
So your thread which prints i is running in master process, and your requests are processed by the worker. The worker during the fork sees i equal to 1. If you move sleep before incrementing i the process manages to fork before the first increment.
Threads except the main one are not copied during a fork, so i does not increment in the worker.
You should use something like uwsgidecorators.thread:
from time import sleep
import threading
import uwsgidecorators
i = 0
#uwsgidecorators.postfork
#uwsgidecorators.thread
def daemon():
global i
while True:
i += 1
print(i)
sleep(3)
def application(environ, start_response):
start_response('200 OK', [('Content-Type','text/html')])
return [str(i).encode()]
Or use:
[uwsgi]
master = false

Python threading is disabled by default in uwsgi, you can enable it by adding option --enable-threads:
uwsgi --http :8090 --wsgi-file uwsgi_test.py --enable-threads
It works in my test environment.

Related

gunicorn returns same pid for different worker process

I'm trying to get a better understanding of how gunicorn manages its processes so I wrote the following code:
from fastapi import FastAPI
import os
from time import sleep
app = FastAPI()
sleep_time = [5, 0]
global_var = 100
order_called = 0
#app.get('/test')
def test_handler():
global global_var, order_called
order_called += 1
local_order_called = order_called
sleep(sleep_time[order_called - 1]) # doing some time consuming stuff here
global_var += 1
return {"test": os.getpid(), "global_var": global_var, "order called": local_order_called}
And I started gunicorn with:
gunicorn -w 2 -k uvicorn.workers.UvicornWorker server:app --preload
The server is supposed to output: pid, 102, 1 and pid, 101, 2 for two consecutive requests - because it takes more time for worker #1 to finish.
I'm getting the correct output, but somehow the two pids returned are the same, which is very strange - this happens with or without the --preload.
Anyone can shed some lights on this? Thanks!
What makes you think the server should produce multiple PIDs? I don't see anything here that would fork another process. os.getpid() will return the same value each time if called by the server. Hope that helps!

gunicorn with eventlet runs threads in sequential manner

from flask import Flask
app = Flask(__name__)
import threading
class SThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
for i in range(1, 1000):
print 0
t = SThread()
t.start()
for i in range(1, 1000):
print 1
t.join()
#app.route('/')
def hello_world():
return 'Hello, World!'
When you start your server like this, gunicorn run:app -b 0.0.0.0:8000, you will see all 0s and 1s will be in random order, main thread and child thread are running parallel.
But when you run same piece of code with gunicorn --worker-class eventlet run:app -b 0.0.0.0:8000, you will see first there will be all 0s and then there will be all 1s. That means main thread and child thread are not running parallel.
Is this expected behaviour?
And how can I use eventlet and make use of threading behaviour?
Edited ::
Based on suggestion, I am trying to do something like this to achieve threads like random behaviour and to join these multiple execution streams.
But it is running in sequential manner only.
from flask import Flask
app = Flask(__name__)
import eventlet
def background():
for i in range(1, 10000):
print 0
return 42
def callback(gt, *args, **kwargs):
result = gt.wait()
print("[cb] %s" % result)
greenth = eventlet.spawn(background)
for i in range(1, 10000):
print 1
greenth.link(callback)
#app.route('/')
def hello_world():
return 'Hello, World!'
This "tight loop" doesn't give chance to run other green threads.
for i in range(1, 1000):
print 0
Eventlet / gevent / asyncio / other similar technologies provide cooperative multithreading. So you must write code that cooperates. You may find this answer useful https://stackoverflow.com/a/14227272/73957
In more "real code", you'd perform some network IO or wait on synchronisation which would run other green threads implicitly. Otherwise you need to yield control to other green threads explicitly: eventlet.sleep()
Unwanted code review: it would help if you decided to use one of eventlet or threading.

Using multiprocessing with gunicorn in Flask application

I made a basic flask application using Gunicorn with worker class gevent. The issue I ran into was as follows. If I had a basic flask app like this:
from multiprocessing import Pool
import Queue
import random
from threading import Thread
import time
from flask import Flask
app = Flask(__name__)
def f(x):
return random.randint(1, 6)
def thread_random(queue):
time.sleep(random.random())
queue.put(random.randint(1, 6))
def thread_roll():
q = Queue.Queue()
threads = []
for _ in range(3):
t = Thread(target=thread_random, args=(q, ))
t.start()
threads.append(t)
for t in threads:
t.join()
dice_roll = sum([q.get() for _ in range(3)])
return dice_roll
#app.route('/')
def hello_world():
# technique 1
pool = Pool(processes=4)
return 'roll is: %s \n' % sum(pool.map(f, range(3)))
# technique 2
return 'roll is: %s \n' % thread_roll()
if __name__ == '__main__':
app.run(debug=True)
And I took two techniques at it, technique 1 will break gunicorn if I run it like:
sudo gunicorn -b 0.0.0.0:8000 app:app --worker-class gevent
but technique 2 won't. I see this is because technique 1 relies on multiprocessing and technique 2 relies on threads, but I can't figure out why a gevent worker class doesn't allow for a pool?
If you're using gevent. You should try using monkey_patch.
http://www.gevent.org/gevent.monkey.html

How to add jobs to apscheduler via request using uwsgi + flask_apscheduler

When run under Flask's local development server, jobs are added and run normally. When run under uWSGI, the job appears to get added to the job store but never is executed. A simple example with the described undesired behavior is given below:
__init__.py
import flask
from datetime import datetime, timedelta
from flask_apscheduler import APScheduler
app = flask.Flask("apscheduler_test")
app.config["SCHEDULER_API_ENABLED"] = True
scheduler = APScheduler()
scheduler.init_app(app)
scheduler.start()
def test_job():
print("test job run")
#app.route("/test")
def apscheduler_test():
print("Adding Job")
scheduler.add_job(id="101",
func=test_job,
next_run_time=(datetime.now() + timedelta(seconds=10)))
return "view", 200
if __name__ == '__main__':
app.run(port=5050)
apschedule_test.ini
[uwsgi]
pidfile = /var/run/%n.pid
chdir = /opt/apscheduler
master = true
enable-threads = true
threads = 20
http-socket = :48197
logto = /var/log/%n.log
plugin = python3
module = %n
callable = app
processes = 1
uid = root
gid = root
daemonize = /var/log/apscheduler_test.log
Try adding this flag to your ini file:
lazy-apps=true
Similar issue: uWSGI lazy-apps and ThreadPool

Using custom signal handlers with gunicorn

I have a Flask application with custom signal handlers to take care of clean up tasks before exiting. When running the application with gunicorn, gunicorn kills the application before it can complete all clean up tasks.
You didn't explain what you mean by custom signal handlers, but I'm not sure that you should be using Flask's signals to capture process-level events, like shutdown. Instead, you can use the signal module from the standard library to hook onto the SIGTERM signal, like so:
# app.py - CREATE THIS FILE
from flask import Flask
from time import sleep, time
import signal
import sys
def create_app():
signal.signal(signal.SIGTERM, my_teardown_handler)
app = Flask(__name__)
#app.route('/')
def home():
return 'hi'
return app
def my_teardown_handler(signal, frame):
"""Sleeps for 3 seconds, then creates/updates a file named app-log.txt with the timestamp."""
sleep(3)
with open('app-log.txt', 'w') as f:
msg = ''.join(['The time is: ', str(time())])
f.write(msg)
sys.exit(0)
if __name__ == '__main__':
app = create_app()
app.run(port=8888)
# wsgi.py - CREATE THIS FILE, in same folder as app.py
import os
import sys
from werkzeug.wsgi import DispatcherMiddleware
from werkzeug.exceptions import NotFound
from app import create_app
app = DispatcherMiddleware(create_app())
Assuming you have a virtual environment with Flask and Gunicorn installed, you should then be able to launch the app with Gunicorn:
$ gunicorn --bind 127.0.0.1:8888 --log-level debug wsgi:app
Next, in a separate terminal, you can send the TERM signal to your app, like so:
$ kill -s TERM [PROCESS ID OF GUNICORN PROCESS / $(ps ax | grep gunicorn | head -n 1 | awk '{print $1}')]
And to observe the results, you should notice that the contents of the app-log.txt file get updated when you run that kill command, after the three-second delay. You could even spawn a third terminal window in this directory and run watch -n 1 "cat app-log.txt" to observe this file being updated in real time, while you cycle between starting the app and sending the TERM signal.
As for tying that into production, I know that Supervisor has a configuration option to specify the stopsignal, like so:
[program:my-app]
command = /path/to/gunicorn [RUNTIME FLAGS]
stopsignal = TERM
...
But that's a separate topic from the original issue of ensuring that your app's clean up tasks are completely executed.

Categories