Search code examples
fastapiuvicornlmdb

Uvicorn with reload=True not running against database in FastApi app


The program module is a fastapi app running on uvicorn with access to an lmdb database. For development (on Windows 11), reload=True is set on uvicorn. It appears uvicorn calls the database handle creation statement twice which leads to an lmdb error (as it can only be called once). Note, this takes place before any changes are made to the code for reload to be triggered.

With reload=False, uvicorn runs as expected and access to the database via fastapi works. Output of both options are shown below. How is this problem solved?

reload=True

db = lmdb.Environment(dbname, dbpath)

if __name__ == "__main__":
    import uvicorn
    uvicorn.run('myprogram:app', host="0.0.0.0", port=8000, log_level="debug", reload=True,)

INFO:     Will watch for changes in these directories: ['C:\\Users\\dines']
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [15480] using StatReload

lmdb.Error: C:/Users/dines/data: The requested operation cannot be performed on a file with a user-mapped section open.

reload=False

db = lmdb.Environment(dbname, dbpath)

if __name__ == "__main__":
    import uvicorn
    uvicorn.run('myprogram:app', host="0.0.0.0", port=8000, log_level="debug", reload=False,)

INFO:     Started server process [16860]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

Solution

  • When you're starting uvicorn inside your module file, the file will be read twice - once when you're invoking the module yourself through python, and once when uvicorn starts up (it'll also have to read the module you've given it).

    You can use FastAPI's startup event to trigger code to only run when the server itself is starting up:

    databases = {}
    
    @app.on_event("startup")
    async def startup_event():
        databases["db"] = lmdb.Environment(dbname, dbpath)
    

    Another option is to invoke uvicorn from the CLI instead of using the same Python module. Most of your options will transfer over directly:

    uvicorn myprogram:app --reload --port 8000 --log-level debug --host 0.0.0.0
    

    This will also only invoke the code once, since you're now launching uvicorn directly without first loading your module.