python flask+gevent doesn't release memory. limit.conf - python

#packages.
greenlet==0.4.11
Flask==0.11.1
#centos, /etc/security/limit.conf
* soft nofile 65535
* hard nofile 65535
This is my test codes (python 3.5) I ran this and watched memory usage.
At First, It started with 30MB memory with 3 threads.
But After sending bulk "/do" request on this server,
memory increase to 60MB with 12 threads. Although sending and every request is done. this memory usage is not changed.
from gevent import monkey;monkey.patch_all(thread=False)
import gevent
from flask import Flask, request
from gevent.pywsgi import WSGIServer
import requests
app = Flask(__name__)
#app.route("/do", methods=['GET', 'POST'])
def ping():
data = request.get_json()
gevent.spawn(send_request, data)
return 'pong'
def send_request(data):
resp = requests.get("http://127.0.0.1:25000/ping", data=data)
if resp.text != 'pong':
app.logger.error(resp.text)
if __name__ == "__main__":
http = WSGIServer(("0.0.0.0", 9999), app)
http.serve_forever()
end_server = True
app.logger.info("Server will be closed")
I think this python uses all available 65535 file count.
How can I limit python to use less file count than I configured in limit.conf file?

python seems not reuse socket when it busy, so It makes socket over an over again until limit.conf nofile limit when sending request in spawn.
So, I just gave a limit for this python process.
import resource
resource.setrlimit(resource.RLIMIT_NOFILE, (1024, 1024))
== updated ==
But requests library still consumes a lot of memory..
I just decided to use tornado http server and AsyncHttpClient with this options below,
AsyncHTTPClient.configure("tornado.simple_httpclient.SimpleAsyncHTTPClient", max_clients=1000)
tornado.netutil.Resolver.configure("tornado.netutil.ThreadedResolver")
you need to write this code on global area below "import" stuffs.
and used gen.moment after finishing request to send it immediately.
#gen.coroutine
def get(self):
self.write("pong")
self.finish()
yield gen.moment
resp = yield self.application.http_client.fetch("...url...", method='POST', headers={"Content-Type": "application/json"},
body=json.dumps({..data..}))

Related

What is limiting the speed of HTTP requests over localhost?

When I try sending a 10MB HTTP request from my server to my client both running on the same machine, it takes ~2s to complete. I would expect it to be faster since all the data is just moving over localhost loopback. I followed this article to test my localhost speed and it showed ~2000MB/s, at that speed a 10MB request should take 5ms.
Why does my HTTP request take so long and how can I speed it up?
The context for why I'm asking is that I am trying to make a web based GUI for my Python project and it needs to display large data tables. I thought a web-based GUI could be just as fast as native frameworks like tkinter since over localhost the HTTP request time should be negligible. I tried FastAPI, Flask, Tornado and ExpressJS, but they all had similar slow performance.
server.py
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
import uvicorn
from fastapi.responses import PlainTextResponse
app = FastAPI()
app.add_middleware(CORSMiddleware,
allow_methods=["*"],
allow_headers=["*"],
allow_origins=["*"])
DATA = 'x' * 10 ** 7
#app.get("/api/data", response_class=PlainTextResponse)
def _():
return DATA
uvicorn.run(app, debug=False, port=8000)
client.py
import requests
import time
start = time.time()
response = requests.get("http://localhost:8000/api/data")
end = time.time()
duration = (end - start)
print(duration)
print(f"Response size: {len(response.content) / 1048576} MB")

mod-wsgi hangs during posting with https and large data packages

I created a https environment in win10 with apache24 + openssl(1.1.1) + mod-wsgi(4.5.24+ap24vc14).
It works well for http posting (no matter how big of the posting data package) but I met a problem for https posting.
For https posting:
when the client and the server are the same local machine, also works well no matter how big of the posting data package.
when the client is a different machine in the same domain, it also works well for small or medium posting data packages, maybe less than 3M, no precise number.
when the client is a different machine in the same domain and posting relatively big data packages, about 5M or 6M, after initial several successful posting, program hangs at server body=environ['wsgi.input'].read(length), no response and no error (seldomly it will pass successfully after a long time, but mostly it willl hang until the connection time out).
when debugging the client and the server, the runtime values of length are both correct and the same.
it seems body=environ['wsgi.input'].read(length) comes from sys.stdin.buffer.read(length), but I still can't find the root reason and a solution.
Client code:
import json
import requests
import base64
import requests.packages.urllib3.util.ssl_
requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS = 'ALL'
url="https://192.168.0.86"
# url="http://192.168.0.86"
f_img=open("./PICs/20191024142412.jpg",'rb')
# f_img=open("./PICs/20191023092645.jpg",'rb')
json_data={'type':'idpic','image':str(base64.b64encode(f_img.read()),'utf-8')}
result = requests.post(url,json=json_data,verify=False)
result_data=json.loads(result.content)
print(result_data)
Part of server codes:
class WSGICopyBody(object):
def __init__(self, application):
self.application = application
def __call__(self, environ, start_response):
from io import StringIO, BytesIO
length = environ.get('CONTENT_LENGTH', '0')
length = 0 if length == '' else int(length)
body = environ['wsgi.input'].read(length)
environ['body_copy'] = body
environ['wsgi.input'] = BytesIO(body)
app_iter = self.application(environ,self._sr_callback(start_response))
return app_iter
def _sr_callback(self, start_response):
def callback(status, headers, exc_info=None):
start_response(status, headers, exc_info)
return callback
app = Flask(__name__)
app.wsgi_app = WSGICopyBody(app.wsgi_app)
#app.route('/',methods=['POST'])
#app.route('/picserver',methods=['POST'])
def picserver():
print("before request.get_data")
request_json_data = request.environ['body_copy']

Call POST methods even if the previous request is not yet done using Tornado Python

How can I make a POST method to accept multiple requests and run in parallel?
I have this code and when I make two POST requests to localhost:5050/, the second request won't start until the first one gave a response.
from tornado import ioloop, web
class MyHandler(web.RequestHandler):
def post(self):
print("1")
print("2")
print("3")
print(self.request.body.rstrip())
app = web.Application([
web.URLSpec('/', MyHandler)
])
app.listen(port=5000)
print("Listening...")
ioloop.IOLoop.current().start()
I've read gen.coroutine but it doesn't work. What is the best way to handle parallel requests? Thanks in advance.
Tornado is built with assumption that large part of webserver time is spent handling IO. In non-ideal situation you end up with significant amount of compute required in webservice, it should be removed from tornado IOLoop. Given you have IO heavy app, tornado will provide needed concurrency. Here is an example simulating an IO heavy app.
from tornado import ioloop, web
from time import sleep
from random import randint
class MyHandler(web.RequestHandler):
async def post(self):
wait_val = randint(1, 10)
await ioloop.IOLoop.current().run_in_executor(None, sleep, wait_val)
print(self.request.body.rstrip())
app = web.Application([
web.URLSpec('/', MyHandler)
])
app.listen(port=5000)
print("Listening...")
ioloop.IOLoop.current().start()
To test the app you can use Siege
siege "http://127.0.0.1:5000/ POST"

Put AsyncHTTPClient or other awaitable in Tornado's get method wiil create ThreadPoolExcutor automatically

How can I prevent a Tornado server from creating ThreadPoolExector automatically.
env:
windows 10
python 3.7
Tornado 6.0.2
import tornado.ioloop
import tornado.web
from tornado.httpclient import HTTPRequest, AsyncHTTPClient
class TestHandler(tornado.web.RequestHandler):
WRITE_MP3_BUFFER_SIZE = 4096
async def get(self):
try:
http_client = AsyncHTTPClient()
req = HTTPRequest(
url='https://www.google.com',
method='GET')
response = await http_client.fetch(req)
contents = response.body.decode('utf-8')
self.write(contents)
except Exception as e:
self.write(str(e))
if __name__ == "__main__":
app = tornado.web.Application([
tornado.web.url(r"/", TestHandler),
])
app.listen(5000)
print("Service Started")
tornado.ioloop.IOLoop.current().start()
I debug this code in VS Code and query from http://127.0.0.1:5000 by Chrome, When I set up breakpoints in Vs Code at debugging, I found that a ThreadPoolExectutor emerged at call stack every query, will it increase unlimitedly and shutdown?
This ThreadPoolExecutor is used for DNS requests, and comes from the standard library's asyncio module. It has a limited size, so it will stop growing at some point (the limit depends on your version of python). You can control this with asyncio's set_default_executor method, but I wouldn't worry about it.

Gevent async server with blocking requests

I have what I would think is a pretty common use case for Gevent. I need a UDP server that listens for requests, and based on the request submits a POST to an external web service. The external web service essentially only allows one request at a time.
I would like to have an asynchronous UDP server so that data can be immediately retrieved and stored so that I don't miss any requests (this part is easy with the DatagramServer gevent provides). Then I need some way to send requests to the external web service serially, but in such a way that it doesn't ruin the async of the UDP server.
I first tried monkey patching everything and what I ended up with was a quick solution, but one in which my requests to the external web service were not rate limited in any way and which resulted in errors.
It seems like what I need is a single non-blocking worker to send requests to the external web service in serial while the UDP server adds tasks to the queue from which the non-blocking worker is working.
What I need is information on running a gevent server with additional greenlets for other tasks (especially with a queue). I've been using the serve_forever function of the DatagramServer and think that I'll need to use the start method instead, but haven't found much information on how it would fit together.
Thanks,
EDIT
The answer worked very well. I've adapted the UDP server example code with the answer from #mguijarr to produce a working example for my use case:
from __future__ import print_function
from gevent.server import DatagramServer
import gevent.queue
import gevent.monkey
import urllib
gevent.monkey.patch_all()
n = 0
def process_request(q):
while True:
request = q.get()
print(request)
print(urllib.urlopen('https://test.com').read())
class EchoServer(DatagramServer):
__q = gevent.queue.Queue()
__request_processing_greenlet = gevent.spawn(process_request, __q)
def handle(self, data, address):
print('%s: got %r' % (address[0], data))
global n
n += 1
print(n)
self.__q.put(n)
self.socket.sendto('Received %s bytes' % len(data), address)
if __name__ == '__main__':
print('Receiving datagrams on :9000')
EchoServer(':9000').serve_forever()
Here is how I would do it:
Write a function taking a "queue" object as argument; this function will continuously process items from the queue. Each item is supposed to be a request for the web service.
This function could be a module-level function, not part of your DatagramServer instance:
def process_requests(q):
while True:
request = q.get()
# do your magic with 'request'
...
in your DatagramServer, make the function running within a greenlet (like a background task):
self.__q = gevent.queue.Queue()
self.__request_processing_greenlet = gevent.spawn(process_requests, self.__q)
when you receive the UDP request in your DatagramServer instance, you push the request to the queue
self.__q.put(request)
This should do what you want. You still call 'serve_forever' on DatagramServer, no problem.

Categories