Flask hot reload not working in Python 3.10: "operation was attempted on something that is not a socket"

104 Views Asked by At

I am using Python 3.10.11, Flask 3.0.0 and Werkzeug 3.0.1. I've worked on a project for couple of months, took a break and now, couple of months later for some reason the hot reload doesn't work. Instead, it throws this error. Moreover, Sometimes the app doesn't update even when I restart it (close and start new terminal). I tried solving this issue as an environmental issue and as a version mismatch issue but both solutions ended up not working. I don't have any sockets in my app as well.

Exception in thread Thread-2 (serve_forever):
Traceback (most recent call last):
  File "C:\Users\USER\AppData\Local\Programs\Python\Python310\lib\threading.py", line 1016, in _bootstrap_inner
    self.run()
  File "C:\Users\USER\AppData\Local\Programs\Python\Python310\lib\threading.py", line 953, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\USER\AppData\Local\Programs\Python\Python310\lib\site-packages\werkzeug\serving.py", line 806, in serve_forever
    super().serve_forever(poll_interval=poll_interval)
  File "C:\Users\USER\AppData\Local\Programs\Python\Python310\lib\socketserver.py", line 232, in serve_forever
    ready = selector.select(poll_interval)
  File "C:\Users\USER\AppData\Local\Programs\Python\Python310\lib\selectors.py", line 324, in select
    r, w, _ = self._select(self._readers, self._writers, [], timeout)
  File "C:\Users\USER\AppData\Local\Programs\Python\Python310\lib\selectors.py", line 315, in _select
    r, w, x = select.select(r, w, w, timeout)
OSError: [WinError 10038] An operation was attempted on something that is not a socket 

I am using pipenv, tried running the app in multiple ways. flask --debug run py ./app.py pipenv run py ./app.py

I just can't understand why it stopped working. I checked for version mismatch and tried changing multiple python versions, and multiple Flask & Werkzeug versions, unfortunately with no luck. I tried reseting netsh: netsh winsock reset as presented here but with no luck as well. I tried deleting the environment and creating a new one. I ran this app globally and locally (in env) but got the same issue. Sometimes running in environment it catches only the first reload and then stops working (without any reload options)

I am clueless. What can cause this? My only guess is maybe when it tries to reload the app, it crashes somewhere that I can't "Ctrl + C" out of and this causes a loop which corrupts the whole listening port. Changing ports doesn't work either.

1

There are 1 best solutions below

1
Ilan Yashuk On

@JohnGordon and @MarkTolonen were right. I was trying to fix the problem for hours without even knowing what line caused it. I stripped out all the logic and found out that the line db = MyDBClass() in my main flask app was causing the error.

What caused the error

My db class had a function that ran in a separate, detached thread which handled its request queue. As lots of requests were coming in simultaneously, This thread was a must for my database in order to not mix the results given to the requests. Hot reloading the flask app created multiple database instances which created multiple threads connected to the same database. Two threads can't access the same database and therefore we got this error.

import logging
import threading
from flask_cors import CORS
from flask import Flask, request
# Other imports
        
app = Flask(__name__)

db = PostgreDB() 
# Causes an error because a hot-reload will create multiple threads using the same database here 

def restart_db():
    global db
    db = PostgreDB() # < Error


@app.route('/')
    def index():
        return "Hurray!"


if __name__ == '__main__':

    app.run(host='0.0.0.0', port=5500, debug=True)

Fix: Flask's global namespace g

Flask gives us a global namespace to use within an application context. According to Flask's documentation:

Typically, an application context will have the same lifetime as a request.

So, for each request, the database class will be initiated again. This means a thread can now properly be terminated (of course it's termination must be implemented in YourDBClass.__del__()) before a new one is created. Now multiple threads will not be created when hot reloading and therefore - the issue is solved. Code:

import logging
import threading
from flask_cors import CORS
from flask import Flask, request
# Other imports

def get_db():
    '''
    Function inserts db into Flask global variable namespace
    which shutdowns after appcontext dies
    '''
    if 'db' not in g:
        g.db = MyDB()  # create a new database connection
    return g.db


# Create Flask app
def create_app():
    app = Flask(__name__)


    # Register FLASK ROUTES:
    @app.route('/')
    def index():

        name = g.db.get_name({"id": 123}) # < How to use db class in your code
        return f"Hurray {name}" 
    
    @app.before_request
    def before_request():
        g.db = get_db()

    @app.teardown_appcontext
    def teardown_db(exception):
        db: MyDB | None = g.pop('db', None)
        if db is not None:
            db.close()
            

        return app

if __name__ == '__main__':
    app = create_app()
    app.run(host='0.0.0.0', port=5500, debug=True)

Please consider

An issue that might pop up now is effeciency as database class instance is created for each request. This overhead can be reduced via Postgresql connection pooling (reusing db connections). Currently my database receives few enough connections to cause this error, so I will leave it for now.