Version 1.34.2+ | Production-Ready Architecture | Thread-Safe Implementation
This page covers the comprehensive memory management system implemented in the Supabase UE5 Plugin, ensuring zero memory leaks and optimal performance in production environments.
The Supabase UE5 Plugin implements a robust memory management system designed to handle high-frequency async operations without memory leaks or resource exhaustion. The system is built around automatic cleanup patterns, proper object lifecycle management, and thread-safe resource handling.
// Centralized resource management through subsystem
USupabaseSubsystem* Subsystem = USupabaseManager::GetSupabaseSubsystem(WorldContext);
if (!Subsystem)
{
// Graceful fallback with error handling
return nullptr;
}
The plugin uses a Game Instance Subsystem (USupabaseSubsystem) as the central authority for all resource management:
USupabaseClient per game instanceEvery async operation in the plugin follows the same memory-safe pattern:
class SUPABASE_API UAsyncOperation : public UBlueprintAsyncActionBase
{
public:
// Automatic cleanup on completion
virtual void BeginDestroy() override
{
SafeCleanup();
Super::BeginDestroy();
}
private:
void SafeCleanup()
{
// Unbind all delegates
// Cancel pending HTTP requests
// Clear object references
// Mark for garbage collection
}
};
All async operations (UAsyncLogin, UAsyncQuery, UAsyncInsertRow, etc.) implement:
void UAsyncLogin::OnLoginComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
// Process response
OnSuccess.Broadcast(Result);
// Automatic cleanup
SafeCleanup();
SetReadyToDestroy(); // Marks for GC
}
void UAsyncLogin::SafeCleanup()
{
FScopeLock Lock(&CleanupMutex);
if (bIsCleanedUp) return;
// Cancel pending requests
if (HttpRequest.IsValid())
{
HttpRequest->CancelRequest();
HttpRequest.Reset();
}
// Clear delegates
OnSuccess.Clear();
OnFailure.Clear();
// Release references
SupabaseClient = nullptr;
Connection = nullptr;
bIsCleanedUp = true;
}
TSharedPtr<IHttpRequest>)// Optimized request creation
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> HttpRequest =
FHttpModule::Get().CreateRequest();
// Automatic cleanup via shared pointer semantics
// No manual memory management required
// Reuse existing connections when possible
if (ExistingWebSocket && ExistingWebSocket->IsConnected())
{
return ExistingWebSocket; // Reuse connection
}
// Create new connection only when needed
TSharedPtr<IWebSocket> NewWebSocket = FWebSocketsModule::Get()
.CreateWebSocket(ServerURL, Protocol);
void UAsyncRealtime::SafeCleanup()
{
if (WebSocket.IsValid())
{
// Remove all event handlers
WebSocket->OnConnected().RemoveAll(this);
WebSocket->OnConnectionError().RemoveAll(this);
WebSocket->OnClosed().RemoveAll(this);
WebSocket->OnMessage().RemoveAll(this);
// Close connection gracefully
WebSocket->Close();
WebSocket.Reset();
}
}
class SUPABASE_API USupabaseSubsystem : public UGameInstanceSubsystem
{
private:
mutable FCriticalSection ClientMutex;
mutable FCriticalSection ConnectionMutex;
public:
USupabaseClient* GetClient() const
{
FScopeLock Lock(&ClientMutex);
return SupabaseClient;
}
};
// Thread-safe state management
TAtomic<bool> bIsConnected{false};
TAtomic<bool> bIsCleanedUp{false};
TAtomic<int32> ActiveRequestCount{0};
✅ Correct Usage:
// Subsystem method (recommended)
UAsyncQuery* QueryTask = UAsyncQuery::QueryAsync(
this, TableName, Columns, Filters
);
QueryTask->OnSuccess.AddDynamic(this, &AMyActor::OnQuerySuccess);
QueryTask->Activate();
// No manual cleanup needed - automatic!
❌ Avoid:
// Don't store references to async operations
UAsyncQuery* StoredQuery = UAsyncQuery::QueryAsync(...);
// This can prevent proper cleanup
✅ Recommended:
// Use the manager for all operations
USupabaseManager::InitializeSupabase(this, Connection);
// All subsequent operations use the same connection
UAsyncLogin::LoginAsync(this, Email, Password);
❌ Avoid:
// Don't create multiple client instances
USupabaseClient* Client1 = NewObject<USupabaseClient>();
USupabaseClient* Client2 = NewObject<USupabaseClient>();
// This causes connection conflicts and memory waste
void AMyActor::OnAsyncOperationFailure(const FString& Error)
{
UE_LOG(LogTemp, Warning, TEXT("Operation failed: %s"), *Error);
// Async operation automatically cleans up on failure
// No manual intervention required
}
| Operation Type | Memory Footprint | Cleanup Time | Thread Safety |
|---|---|---|---|
| Login | ~2-4 KB | Immediate | ✅ Full |
| Query | ~1-8 KB* | Immediate | ✅ Full |
| Insert/Update | ~1-5 KB | Immediate | ✅ Full |
| Realtime | ~5-10 KB | Graceful | ✅ Full |
| File Upload | ~Variable** | Progressive | ✅ Full |
*Depends on result set size
**Streaming upload with progressive cleanup
Add to your project’s DefaultEngine.ini:
[Core.Log]
LogMemory=Verbose
LogSupabase=Verbose
[/Script/Engine.GarbageCollectionSettings]
gc.VerifyGCObjectNames=1
// Solution: Use batch operations instead of individual calls
TArray<FJsonObjectWrapper> BatchData;
UAsyncBatchInsert::BatchInsertAsync(this, TableName, BatchData);
// Ensure proper cleanup in BeginPlay/EndPlay
void AMyActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
// Subsystem handles cleanup automatically
Super::EndPlay(EndPlayReason);
}
If upgrading from older versions, ensure you:
// Old way - remove this
USupabaseClient* ManualClient = NewObject<USupabaseClient>();
// New way - use manager
USupabaseManager::InitializeSupabase(this, Connection);
// Old way
UAsyncLogin* Login = NewObject<UAsyncLogin>();
Login->SetupLoginRequest(Client, Email, Password);
// New way
UAsyncLogin* Login = UAsyncLogin::LoginAsync(this, Email, Password);
// This is no longer needed - remove it
if (AsyncOperation)
{
AsyncOperation->ConditionalBeginDestroy();
AsyncOperation = nullptr;
}
The plugin provides console commands for memory monitoring:
supabase.memory.stats # Show current memory usage
supabase.memory.gc # Force garbage collection
supabase.connections.list # List active connections
supabase.debug.cleanup # Trigger manual cleanup
Use the Supabase Debug Widget (available in development builds) to monitor:
Add to DefaultGame.ini:
[/Script/Supabase.SupabaseSettings]
; Maximum concurrent async operations
MaxConcurrentOperations=50
; HTTP connection pool size
HTTPConnectionPoolSize=10
; WebSocket connection timeout (seconds)
WebSocketTimeoutSeconds=30
// In shipping builds, enable aggressive cleanup
#if UE_BUILD_SHIPPING
// Reduce cleanup delay for immediate release
SetCleanupDelay(0.0f);
// Enable connection pooling optimizations
EnableConnectionPooling(true);
#endif
This page is part of the Supabase UE5 Plugin Documentation by Seven Mountains Labs.