-
-
Notifications
You must be signed in to change notification settings - Fork 306
Open
Labels
bugSomething isn't workingSomething isn't workingpythonPull requests that update Python codePull requests that update Python code
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
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't workingpythonPull requests that update Python codePull requests that update Python code