supabase-py
supabase-py copied to clipboard
Supabase Client Requires Explicit `sign_out()` to Terminate Properly
Summary
The Supabase client currently requires an explicit call to client.auth.sign_out() for processes to terminate correctly. Without this, background WebSocket connections and other resources may remain active, leading to incomplete shutdowns and potential resource leaks.
Problem Explanation:
The current behavior of the Supabase client involves establishing WebSocket connections and listening for authentication events. These processes, especially those involving real-time functionality, do not automatically terminate upon the program’s end. Explicitly calling client.auth.sign_out() is necessary to clean up these resources and ensure proper process termination.
# From SyncClient class in SyncClient.py
class SyncClient:
def __init__(self, ...):
# ...
self.realtime = self._init_realtime_client(
realtime_url=self.realtime_url,
supabase_key=self.supabase_key,
options=options.realtime if options else None,
)
# ...
@staticmethod
def _init_realtime_client(
realtime_url: str, supabase_key: str, options: Optional[Dict[str, Any]]
) -> SyncRealtimeClient:
"""Private method for creating an instance of the realtime-py client."""
return SyncRealtimeClient(
realtime_url, token=supabase_key, params=options or {}
)
def _listen_to_auth_events(
self, event: AuthChangeEvent, session: Union[Session, None]
):
# ...
self.realtime.set_auth(access_token)
# From SyncRealtimeClient in realtime-py
class SyncRealtimeClient:
def __init__(self, ...):
# ...
self._endpointWebSocket = None
# ...
def connect(self):
# ...
self._endpointWebSocket = websocket.WebSocketApp(
# ...
)
# ...
def set_auth(self, token):
# ...
self.connect() # This might create a new WebSocket connection
# From GoTrueClient in gotrue-py
class SyncGoTrueClient:
def sign_out(self, options: SignOutOptions = {"scope": "global"}) -> None:
# ...
self._remove_session()
self._notify_all_subscribers("SIGNED_OUT", None)
Key points:
- Real-time Connections: The WebSocket connections created by SyncRealtimeClient continue running in the background and need to be manually terminated.
- Authentication Events: Sign-out triggers an event that helps reset real-time client authentication, which won't occur unless sign_out() is called.
- Resource Management: The sign_out() function ensures proper cleanup of sessions and network connections, preventing potential memory leaks or resource hogging.
- Daemon Threads: Real-time connections might be running as daemon threads, which do not automatically terminate, leading to hanging processes unless explicitly stopped with sign_out().
Given this behavior, the necessity of an explicit client.auth.sign_out() call should be clearly documented and potentially re-evaluated for a more intuitive shutdown process.
from typing import Optional, Dict, Any, Union import websocket
From SyncClient class in SyncClient.py
class SyncClient: """ SyncClient class for managing real-time connections and authentication.
...
Methods:
sign_out():
Sign out and clean up resources. This method must be called to
terminate WebSocket connections and prevent resource leaks.
"""
def __init__(self, realtime_url: str, supabase_key: str, options: Optional[Dict[str, Any]] = None):
self.realtime_url = realtime_url
self.supabase_key = supabase_key
self.options = options
self.realtime = self._init_realtime_client(
realtime_url=self.realtime_url,
supabase_key=self.supabase_key,
options=options.realtime if options else None,
)
@staticmethod
def _init_realtime_client(
realtime_url: str, supabase_key: str, options: Optional[Dict[str, Any]]
) -> 'SyncRealtimeClient':
"""Private method for creating an instance of the realtime-py client."""
return SyncRealtimeClient(
realtime_url, token=supabase_key, params=options or {}
)
def _listen_to_auth_events(
self, event: 'AuthChangeEvent', session: Union['Session', None]
):
# ...
access_token = session.access_token if session else None
self.realtime.set_auth(access_token)
def sign_out(self):
"""Sign out and clean up resources."""
# Terminate WebSocket connections
if self.realtime:
self.realtime.disconnect()
# Perform other cleanup tasks
# ...
def __enter__(self):
"""Enter the runtime context related to this object."""
return self
def __exit__(self, exc_type, exc_value, traceback):
"""Exit the runtime context related to this object."""
self.sign_out()
From SyncRealtimeClient in realtime-py
class SyncRealtimeClient: def init(self, realtime_url: str, token: str, params: Optional[Dict[str, Any]] = None): self.realtime_url = realtime_url self.token = token self.params = params self._endpointWebSocket = None
def connect(self):
# ...
self._endpointWebSocket = websocket.WebSocketApp(
self.realtime_url,
header={"Authorization": f"Bearer {self.token}"},
on_message=self.on_message,
on_error=self.on_error,
on_close=self.on_close,
)
self._endpointWebSocket.run_forever()
def disconnect(self):
"""Disconnect the WebSocket connection."""
if self._endpointWebSocket:
self._endpointWebSocket.close()
self._endpointWebSocket = None
def set_auth(self, access_token: Optional[str]):
"""Set the authentication token for the WebSocket connection."""
self.token = access_token
if self._endpointWebSocket:
self.disconnect()
self.connect()
def on_message(self, ws, message):
# Handle incoming messages
pass
def on_error(self, ws, error):
# Handle errors
pass
def on_close(self, ws, close_status_code, close_msg):
# Handle connection close
pass
Example usage with context manager
if name == "main": with SyncClient(realtime_url="wss://example.com/socket", supabase_key="your-supabase-key") as client: # Perform operations with the client # ... pass # The sign_out() method will be called automatically when exiting the context
Full Disclosure, I'm currently learning. Hope the code I provided above helps. Feel free to comment with any advice etc.
Yes I am willing to submit a PR! Please assign me this issue.
@Diya910 no need to assign, you can just submit a PR once your PR is ready.
Hi! :) Is there any update on this?
I'm having an issue because after a couple of minutes/hours the Python client connection dies (complains about JWT, and apparently it is not auto-refreshing). As a quick fix, I'm creating new connections every once in a while (short span) and my server memory is rapidly increasing. :/
Thank you! 🙏
@miguel-arrf you need to provide code when mentioning issue to do with code. Provide a reproducible example repo. I have not experienced what you are mentioning here nor am I able to reproduce it.
I created an issue (#1034) detailing the same problem I'm facing as @miguel-arrf.