Skip to content

Commit

Permalink
showcace for benoitc/gunicorn#3228
Browse files Browse the repository at this point in the history
  • Loading branch information
Michal Daniliszyn 1 committed Jun 17, 2024
0 parents commit f7e391b
Show file tree
Hide file tree
Showing 54 changed files with 10,532 additions and 0 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PURE_PYTHON=1
61 changes: 61 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover

# Translations
*.mo
*.pot

# Django stuff:
*.log

# Sphinx documentation
docs/_build/

# PyBuilder
target/

.idea/

.DS_Store
17 changes: 17 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
DOCKER_COMPOSE_COMMAND = docker-compose
DOCKER_COMPOSE_RUN ?= ${DOCKER_COMPOSE_COMMAND} run --rm -d
DOCKER_COMPOSE_UP ?= ${DOCKER_COMPOSE_COMMAND} up -d
PYTHON_RUN = python

# Sets everything up ready for `make start` to be run
init:
@echo " $(P) init"
@make build

# Build
build:
@echo " $(P) build"
${DOCKER_COMPOSE_COMMAND} build

start:
${DOCKER_COMPOSE_UP}
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Just run 'docker-compose build' and after 'docker-compose up'.
There will be two server instances and two client instances.
One server with Gunicorn with this fix: https://github.com/benoitc/gunicorn/pull/3228
and client sending requests in loop,
another with Gunicorn 22.0.0 version and client sending requests in loop.
Settings for both servers are the same.
We can see that in Gunicorn 22.0.0, worker waits for long time to finish restart while
in Gunicorn fixed version restart is very fast (without errors on the client side).
Empty file added __init__.py
Empty file.
31 changes: 31 additions & 0 deletions app_fixed/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
FROM python:3.11-slim-bullseye

ENV PURE_PYTHON 1

RUN apt-get update \
&& apt-get -yq --no-install-recommends install \
build-essential libpq5 libpq-dev git iproute2 procps netcat \
&& rm -rf /var/lib/apt/lists/*


RUN pip install --upgrade pip

RUN pip install poetry==1.6.1 \
&& poetry config virtualenvs.create false

RUN pip install git+https://github.com/tmikus/pycld2.git


WORKDIR /code

ARG BUILD_DATE="NA"
ARG VCS_REF="NA"

COPY gunicorn.py.ini /code/

COPY pyproject.toml poetry.lock /code/
RUN poetry install --no-root --no-interaction --no-ansi

ENV PYTHONPATH="${PYTHONPATH}:/code/"

ENV _PIP_USE_IMPORTLIB_METADATA=0
24 changes: 24 additions & 0 deletions app_fixed/Dockerfile.client
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM python:3.11-slim-bullseye

ENV PURE_PYTHON 1

RUN apt-get update \
&& apt-get -yq --no-install-recommends install \
build-essential libpq5 libpq-dev git iproute2 procps netcat \
&& rm -rf /var/lib/apt/lists/*

RUN pip install --upgrade pip

RUN pip install poetry==1.6.1 \
&& poetry config virtualenvs.create false

WORKDIR /code


COPY pyproject.toml poetry.lock /code/
RUN poetry install --no-root --no-interaction --no-ansi

ENV PYTHONPATH="${PYTHONPATH}:/code/"


ENV _PIP_USE_IMPORTLIB_METADATA=0
45 changes: 45 additions & 0 deletions app_fixed/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import multiprocessing


from flask import Flask
import time
from gevent import config, get_hub
from gevent.events import IEventLoopBlocked
import gevent.util
import logging
import zope.event
from greenlet import greenlet

# Should each hub start a native OS thread to monitor for problems?
# Such a thread will periodically check to see if the event loop is blocked for longer
# than max_blocking_time, producing output on the hub’s exception stream (stderr by default)
# if it detects this condition
# http://www.gevent.org/configuration.html#gevent._config.Config.monitor_thread
config.monitor_thread = True
config.max_blocking_time = 2


# start the monitor thread
hub = get_hub()
monitor = hub.start_periodic_monitoring_thread()





def create_app():
# create and configure the app
app = Flask(__name__)

# a simple page that says hello
@app.route('/')
def hello():
#time.sleep(5)
return 'Hello, World!'

return app





61 changes: 61 additions & 0 deletions app_fixed/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import requests
from multiprocessing.pool import Pool
from datetime import datetime
import argparse

HOST = "app:8400"


def parse(i, session=None, url=HOST):
try:
print(f"start: {i}")
response = None

print(f"{datetime.now()} before {i}")
if session:
response = session.get(f'http://{url}/?id={i}')
else:
response = requests.get(f'http://{url}/?id={i}')
print(f"{datetime.now()} after {i}")
if not response:
print(f"not response {i}")

return response

except Exception as e:
print(f"exception {i}")
print(e)


def _run_parallel_shared_session(pool_count=5, url=HOST):
while True:
with Pool(pool_count) as pool:
with requests.Session() as s:
results = pool.starmap(parse,
[[i, s, url] for i in range(0, 20000)])
print(results)

def _run_parallel(pool_count=5, url=HOST):
while True:
with Pool(pool_count) as pool:
results = pool.starmap(parse,
[[i, None, url] for i in range(0, 20000)])
print(results)




if __name__ == '__main__':
parser = argparse.ArgumentParser(
prog='ProgramName',
description='What the program does',
epilog='Text at the bottom of help')
parser.add_argument('usecase')
parser.add_argument('--url')
args = parser.parse_args()

if args.usecase == 'parallel':
_run_parallel(url=args.url)

if args.usecase == 'parallel_shared_session':
_run_parallel_shared_session(url=args.url)
11 changes: 11 additions & 0 deletions app_fixed/gunicorn.py.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""gunicorn WSGI server configuration."""

accesslog = '-'
bind = '0.0.0.0:8400'
loglevel = 'debug'
max_requests = 20
timeout = 60
worker_class = 'gevent'
workers = 1
graceful_timeout = 30
keepalive = 75
9 changes: 9 additions & 0 deletions app_fixed/gunicorn/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.

version_info = (22, 0, 0)
__version__ = ".".join([str(v) for v in version_info])
SERVER = "gunicorn"
SERVER_SOFTWARE = "%s/%s" % (SERVER, __version__)
11 changes: 11 additions & 0 deletions app_fixed/gunicorn/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.

from gunicorn.app.wsgiapp import run

if __name__ == "__main__":
# see config.py - argparse defaults to basename(argv[0]) == "__main__.py"
# todo: let runpy.run_module take care of argv[0] rewriting
run(prog="gunicorn")
4 changes: 4 additions & 0 deletions app_fixed/gunicorn/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
Loading

0 comments on commit f7e391b

Please sign in to comment.