Open
Description
Bug report
- I confirm this is a bug with Supabase, not with my own application.
- I confirm I have searched the Docs, GitHub Discussions, and Discord.
Describe the bug
Creating a new Supabase async client per request in FastAPI causes 'Too many open files (OSError: [Errno 24])'
To Reproduce
- Start a FastAPI app with the following code:
I used poetry for the project, this is what my pyproject looks like:
[tool.poetry]
name = "test-supabase"
version = "0.1.0"
description = ""
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.12"
fastapi = "^0.115.11"
supabase = "^2.13.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
This is the code to repro:
from fastapi import FastAPI, Depends
from supabase._async.client import AsyncClient as Client, create_client
SUPABASE_URL = "SUPABASE_URL"
SUPABASE_ANON_KEY = "SUPABASE_ANON_KEY"
app = FastAPI()
# Dependency that creates a new Supabase client per request
async def get_supabase_client():
supabase = await create_client(SUPABASE_URL, SUPABASE_ANON_KEY) # Creates a new client per request
return supabase
@app.get("/")
async def test_supabase(supabase: Client = Depends(get_supabase_client)):
"""A route that uses Supabase."""
res = await supabase.table("sample_table").select("*").limit(1).execute()
return res
-
Run the FastAPI app:
uvicorn main:app --reload
-
Send concurrent requests using
wrk
:ab -n 5000 -c 100 http://127.0.0.1:8000/
-
Observe the error in logs:
OSError: [Errno 24] Too many open files
INFO: 127.0.0.1:59916 - "GET / HTTP/1.0" 500 Internal Server Error
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/uvicorn/protocols/http/h11_impl.py", line 403, in run_asgi
result = await app( # type: ignore[func-returns-value]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 60, in __call__
return await self.app(scope, receive, send)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/fastapi/applications.py", line 1054, in __call__
await super().__call__(scope, receive, send)
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/starlette/applications.py", line 112, in __call__
await self.middleware_stack(scope, receive, send)
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/starlette/middleware/errors.py", line 187, in __call__
raise exc
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/starlette/middleware/errors.py", line 165, in __call__
await self.app(scope, receive, _send)
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 62, in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
raise exc
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
await app(scope, receive, sender)
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/starlette/routing.py", line 714, in __call__
await self.middleware_stack(scope, receive, send)
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/starlette/routing.py", line 734, in app
await route.handle(scope, receive, send)
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/starlette/routing.py", line 288, in handle
await self.app(scope, receive, send)
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/starlette/routing.py", line 76, in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
raise exc
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
await app(scope, receive, sender)
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/starlette/routing.py", line 73, in app
response = await f(request)
^^^^^^^^^^^^^^^^
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/fastapi/routing.py", line 291, in app
solved_result = await solve_dependencies(
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/fastapi/dependencies/utils.py", line 638, in solve_dependencies
File "/Users/crro/dev/test-supabase/test_supabase/main.py", line 15, in get_supabase_client
supabase = await create_client(SUPABASE_URL, SUPABASE_ANON_KEY) # Creates a new client per request
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/supabase/_async/client.py", line 337, in create_client
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/supabase/_async/client.py", line 103, in create
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/supabase/_async/client.py", line 81, in __init__
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/supabase/_async/client.py", line 246, in _init_supabase_auth_client
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/supabase/_async/auth_client.py", line 47, in __init__
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/gotrue/_async/gotrue_client.py", line 101, in __init__
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/gotrue/_async/gotrue_base_api.py", line 28, in __init__
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/httpx/_client.py", line 1402, in __init__
self._transport = self._init_transport(
^^^^^^^^^^^^^^^^^^^^^
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/httpx/_client.py", line 1445, in _init_transport
return AsyncHTTPTransport(
^^^^^^^^^^^^^^^^^^^
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/httpx/_transports/default.py", line 297, in __init__
ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/crro/Library/Caches/pypoetry/virtualenvs/test-supabase-bTT9SAhI-py3.12/lib/python3.12/site-packages/httpx/_config.py", line 40, in create_ssl_context
File "/opt/homebrew/Cellar/[email protected]/3.12.6/Frameworks/Python.framework/Versions/3.12/lib/python3.12/ssl.py", line 707, in create_default_context
OSError: [Errno 24] Too many open files
Expected behavior
The supabase client should clean up resources after itself to prevent the leaking of file descriptors. I think the fix is to add support so that we can create an async supabase client with async with
so that the get_supbase_client dependency looks like this:
async def get_supabase_client():
async with await create_client(SUPABASE_URL, SUPABASE_ANON_KEY) as supabase:
yield supabase
I think the fix is to:
- Implement the
__aenter__
and__aexit__
methods in the AsyncSupabaseAuthClient class to ensure the HTTP client is properly closed. - Implement the
__aenter__
and__aexit__
methods in the AsyncClient class to manage the lifecycle of the auth and realtime clients.
Changes to AsyncSupabaseAuthClient
Add the following methods to AsyncSupabaseAuthClient:
class AsyncSupabaseAuthClient(AsyncGoTrueClient):
# Existing __init__ method
async def __aenter__(self):
return self
# since https://github.com/supabase/supabase-py/blob/main/supabase/_async/auth_client.py#L9
# https://github.com/supabase/auth-py/blob/4194347d2c6891fae1e5f7eb6cdbebc1054064cf/supabase_auth/http_clients.py#L3
# https://github.com/encode/httpx/blob/9e8ab40369bd3ec2cc8bff37ab79bf5769c8b00f/httpx/_client.py#L2008
async def __aexit__(self, exc_type, exc_value, traceback):
if not self._http_client_provided and self.http_client:
await self.http_client.__aexit__(exc_type, exc_value, traceback)
Changes to AsyncClient
Add the following methods to AsyncClient:
class AsyncClient:
# Existing __init__ method
async def __aenter__(self):
await self.auth.__aenter__()
return self
async def __aexit__(self, exc_type, exc_value, traceback):
await self.auth.__aexit__(exc_type, exc_value, traceback)
await self.realtime.close() #https://github.com/supabase/realtime-py/blob/628f1e688dd2339efff560a314cbef9472df363c/realtime/_async/client.py#L199
Screenshots
It first runs out of file descriptors
then it just fails on subsequent connections
System information
- OS: macOS
- Browser (if applies) N/A
- Version of supabase-py: ^2.13.0
- Version of Node.js: v20.3.1
Additional context
Happy to contribute this fix but don't know if I'm missing something