commit 118301f024899f460a60c00ad413a7ee8bfaf0ef Author: RouxAntoine Date: Sun Mar 10 19:42:59 2024 +0100 feature: reproduce so threading http server hanging diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..060005d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# formatting rule for this project + +root = true + +[*] +indent_size = 4 +indent_style = space +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true + +# Docstrings and comments use max_line_length = 79 +[*.py] +max_line_length = 119 + +# Makefiles always use tabs for indentation +[Makefile] +indent_style = tab diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f036f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,134 @@ +# ---> Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# 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/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# intellij IDE +.idea +*.iml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6df3cc6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,5 @@ +"THE BEER-WARE LICENSE" (Revision 42): + + wrote this file. As long as you retain this notice you can +do whatever you want with this stuff. If we meet some day, and you think this +stuff is worth it, you can buy me a beer in return Poul-Henning Kamp diff --git a/Main.py b/Main.py new file mode 100755 index 0000000..99a79b0 --- /dev/null +++ b/Main.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from random import randint +from http.server import ThreadingHTTPServer, HTTPServer, SimpleHTTPRequestHandler +from signal import signal, SIGINT +from socketserver import ThreadingMixIn, BaseServer, BaseRequestHandler +from threading import Thread + + +class HandlerExtractCode(SimpleHTTPRequestHandler): + + def do_GET(self): + self.send_response(200) + self.send_header("Content-type", "application/json") + self.end_headers() + self.wfile.write(b"{ \"key\": \"value\" }") + + +class BackendThreadHttpServer(ThreadingHTTPServer): + + def __init__(self, server_address, request_handler_class): + super().__init__(server_address, request_handler_class) + + # properly close HttpServer and close ThreadMixIn with join client threads + def server_close(self): + # stop serve_forever infinite loop (This should be defined into HTTPServer.server_close method) + BaseServer.shutdown(self) + # # close TCP socket and more for future override into HTTPServer class + # HTTPServer.server_close(self) + # # join all process request threads + # ThreadingMixIn.server_close(self) + + +class BackendApplication(Thread): + + def __init__(self, handler, host: str = "0.0.0.0", port: int = 8080): + super().__init__(target=self.application_endpoint) + self.port: int = port + self.httpServer = BackendThreadHttpServer((host, port), handler) + + def application_endpoint(self): + print("serving at port", self.port) + self.httpServer.serve_forever() + + def stop(self): + print("stop listening at port", self.port) + self.httpServer.server_close() + + +def start_server(): + def handle_sigint(signal_received, frame): + print("stop server thread") + application_endpoint.stop() + print("wait server thread") + application_endpoint.join() + + # create server to handle oauth callback request + application_endpoint = BackendApplication(HandlerExtractCode, "127.0.0.1", 65534) + + # on sigint close server listen socket + signal(SIGINT, handle_sigint) + + # start server handler into new thread + application_endpoint.start() + + return application_endpoint + + +if __name__ == "__main__": + thread = start_server() + thread.join() + print("ended") diff --git a/README.md b/README.md new file mode 100644 index 0000000..509f983 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Reproducer to github issue on cpython repository + +This code create a Thread and run a minimal http server into it. + +[19556](https://github.com/python/cpython/pull/19556) + +## how to reproduce + +The tricky part is in `Main.py` line 28 class `BackendThreadHttpServer` method `server_close()` + +when running this code and exiting it with ctrl+c + +If `BackendThreadHttpServer#server_close()` method doesn't call `BaseServer.shutdown(self)` the thread join never occurred + +and so the program never end