-
Notifications
You must be signed in to change notification settings - Fork 74
Description
I know this repo is a simple example for educational purposes, and in the accompanying video Arjan mentions the global dictionary items is a stand-in for a proper database. But using global variables in multi-threaded or multi-process server code is a good way to create subtle bugs (that will only be seen in production, if the dev, staging and test environments only used a single worker). If fastAPI is revisited by the channel in future (I'd love to see that one about OAuth2 flows in FastAPI), I think avoiding global variables should be mentioned (once again).
Explanation
Using global variables in the main:app module for other coding purposes, does not scale beyond one single worker serving all requests (using multiple workers is a big advantage of using uvicorn). Each worker is its own separate Python process. As ever, avoid global variables. Even mutable ones. In particular, if one end point in the custom FastAPI code did app.foo = 'bar', Another worker serving a different request will throw an error if it did assert app.foo == 'bar', just as if they were two entirely separate Python applications.
Otherwise, immutable global constants can be fine as long as each constant really does have the same value in each process (i.e. if each one's value doesn't vary between worker processes somehow).
Reproduction
To demonstrate this two terminal windows are required:
(with uvicorn and fastAPI installed in the venv).
Note, the bug will not occur with uvicorn's `--reload' option, as:
WARNING: "workers" flag is ignored when reloading is enabled.
\venvs\Arjan_FastAPI\2023-fastapi>uvicorn 3_more_routing:app --workers 2
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started parent process [14104]
INFO: Started server process [13420]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Started server process [13576]
INFO: Waiting for application startup.
INFO: Application startup complete.
(with requests installed)
(requests) \venvs\Arjan_FastAPI\2023-fastapi>python 1_main.py
{'items': {'0': {'name': 'Hammer', 'price': 9.99, 'count': 20, 'id': 0, 'category': 'tools'}, '1': {'name': 'Pliers', 'price': 5.99, 'count': 20, 'id': 1, 'category': 'tools'}, '2': {'name': 'Nails', 'price': 1.99, 'count': 100, 'id': 2, 'category': 'consumables'}}}
(requests) \venvs\Arjan_FastAPI\2023-fastapi>python 3_main.py
Adding an item:
{'added': {'name': 'Screwdriver', 'price': 3.99, 'count': 10, 'id': 4, 'category': 'tools'}}
{'items': {'0': {'name': 'Hammer', 'price': 9.99, 'count': 20, 'id': 0, 'category': 'tools'}, '1': {'name': 'Pliers', 'price': 5.99, 'count': 20, 'id': 1, 'category': 'tools'}, '2': {'name': 'Nails', 'price': 1.99, 'count': 100, 'id': 2, 'category': 'consumables'}, '4': {'name': 'Screwdriver', 'price': 3.99, 'count': 10, 'id': 4, 'category': 'tools'}}}
Updating an item:
{'updated': {'name': 'Hammer', 'price': 9.99, 'count': 9001, 'id': 0, 'category': 'tools'}}
{'items': {'0': {'name': 'Hammer', 'price': 9.99, 'count': 9001, 'id': 0, 'category': 'tools'}, '1': {'name': 'Pliers', 'price': 5.99, 'count': 20, 'id': 1, 'category': 'tools'}, '2': {'name': 'Nails', 'price': 1.99, 'count': 100, 'id': 2, 'category': 'consumables'}, '4': {'name': 'Screwdriver', 'price': 3.99, 'count': 10, 'id': 4, 'category': 'tools'}}}
Deleting an item:
{'deleted': {'name': 'Hammer', 'price': 9.99, 'count': 9001, 'id': 0, 'category': 'tools'}}
{'items': {'1': {'name': 'Pliers', 'price': 5.99, 'count': 20, 'id': 1, 'category': 'tools'}, '2': {'name': 'Nails', 'price': 1.99, 'count': 100, 'id': 2, 'category': 'consumables'}, '4': {'name': 'Screwdriver', 'price': 3.99, 'count': 10, 'id': 4, 'category': 'tools'}}}
(requests) \venvs\Arjan_FastAPI\2023-fastapi>python 1_main.py
{'items': {'1': {'name': 'Pliers', 'price': 5.99, 'count': 20, 'id': 1, 'category': 'tools'}, '2': {'name': 'Nails', 'price': 1.99, 'count': 100, 'id': 2, 'category': 'consumables'}, '4': {'name': 'Screwdriver', 'price': 3.99, 'count': 10, 'id': 4, 'category': 'tools'}}}
(requests) \venvs\Arjan_FastAPI\2023-fastapi>python 1_main.py
{'items': {'0': {'name': 'Hammer', 'price': 9.99, 'count': 20, 'id': 0, 'category': 'tools'}, '1': {'name': 'Pliers', 'price': 5.99, 'count': 20, 'id': 1, 'category': 'tools'}, '2': {'name': 'Nails', 'price': 1.99, 'count': 100, 'id': 2, 'category': 'consumables'}}}
Note on the third request, the "deleted" item, Hammer seems to have reappeared, because that request was served by the other worker, with its own separate never-mutated global variable, items.