Powrót do Bloga

Naprawa błędu UBA Executor UBT w buildach ze źródeł Unreal Engine 5.8

Opublikowano 27 czerwca 2026
Naprawa błędu UBA Executor UBT w buildach ze źródeł Unreal Engine 5.8

W skrócie

Artykuł wyjaśnia przyczynę błędu UBA Executor w silniku Unreal Engine 5.8, który uniemożliwia kompilację kodu ze źródeł przez rekurencyjne wywołania UBT. Przedstawiono trzy metody rozwiązania problemu: globalną modyfikację pliku konfiguracyjnego XML, nałożenie poprawki w kodzie źródłowym fabryki executorów w C# oraz użycie flag w linii poleceń. Wskazano również sprawdzone praktyki optymalizacji procesu budowania silnika i zarządzania Dedicated Servers z użyciem platformy horizOn.

Mało co potrafi tak skutecznie zatrzymać pracę studia jak uszkodzony build silnika, szczególnie gdy kompilujesz Unreal Engine 5.8.0 ze źródeł, a Visual Studio zatrzymuje się z mało czytelnym wyjątkiem. Kompilacja kończy się niepowodzeniem z powodu nieobsłużonego wyjątku wskazującego bezpośrednio na UBAExecutor.cs. Ten konkretny problem, znany jako unreal engine uba executor ubt error, całkowicie wstrzymuje kompilację. Blokuje on generowanie przez build graph kluczowych programów pomocniczych, takich jak UnrealHeaderTool. W tym poradniku szczegółowo omówimy, dlaczego Unreal Build Accelerator (UBA) ma problemy z wywołaniami zagnieżdżonymi, jak zawodzi domyślna konfiguracja oraz jak przygotować patch do kodu źródłowego silnika, aby przywrócić sprawnie działający build pipeline.

Zrozumienie architektury systemu budowania

Aby zrozumieć, dlaczego ten błąd występuje, musimy przyjrzeć się, jak Unreal Build Tool (UBT) zarządza procesem kompilacji. Projekty oparte na Unreal Engine są ogromne i często zawierają dziesiątki tysięcy plików źródłowych. Aby kompilować je efektywnie, UBT działa jako meta-system budowania, generując grafy zależności i zlecając akcje kompilacji.

Czym jest Unreal Build Accelerator?

Epic Games wprowadziło Unreal Build Accelerator (UBA) jako domyślny executor kompilacji w celu zastąpienia starszych systemów dystrybucji zadań. UBA zostało zaprojektowane, aby przyspieszyć kompilację poprzez użycie lekkiego, wirtualizowanego systemu plików (VFS) do przechwytywania operacji wejścia/wyjścia (I/O) na plikach. Przekazuje ono zadania kompilatora do wielu lokalnych rdzeni procesora lub dystrybuuje je do Horde build nodes.

Na standardowej, 64-rdzeniowej maszynie budującej UBA pozwala skrócić czas pełnej kompilacji silnika (clean build) z około 90 minut do poniżej 25 minut. Jednak ze względu na to, że UBA opiera się na scentralizowanym lokalnym agencie przechwytującym operacje I/O, wymaga ono ścisłej kontroli nad sposobem uruchamiania procesów kompilatora.

Mechanizm rekurencyjnych wywołań UBT

Podczas procesu kompilacji UBT często napotyka targety, które muszą zostać zbudowane przed skompilowaniem głównych plików binarnych silnika. Na przykład przed kompilacją pliku wykonywalnego UnrealEditor, UBT must compile UnrealHeaderTool (UHT) w celu przeanalizowania nagłówków i wygenerowania metadanych refleksji.

Aby to osiągnąć, główny proces UBT uruchamia zagnieżdżony, pomocniczy proces UBT w celu zbudowania wymaganego targetu. To zagnieżdżone wywołanie określa się mianem rekurencyjnego wywołania UBT. Kiedy rekurencyjne wywołanie UBT jest aktywne, UBT ustawia wewnętrzną flagę (UnrealBuildTool.IsRecursive = true) i przekazuje zmienne środowiskowe, aby oznaczyć proces jako zagnieżdżony.

Dlaczego UBA Executor ulega awarii przy wywołaniach rekurencyjnych

Awaria następuje, ponieważ UBA executor nie został zaprojektowany do obsługi zagnieżdżonych pętli kompilacji. Przyjrzyjmy się typowemu stosowi wywołań (call stack) zgłaszanemu przez UBT, gdy ten problem występuje w Visual Studio:

Unhandled exception: Exception: UBA executor is not expected to be invoked from a recursive UBT call.
   at UnrealBuildTool.UBAExecutor.Init(IEnumerable`1 targetDescriptors, ILogger logger) in UnrealEngine\Engine\Source\Programs\UnrealBuildTool\Executors\UnrealBuildAccelerator\UBAExecutor.cs:line 315
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at UnrealBuildTool.UBAExecutor.ExecuteActionsAsync(IEnumerable`1 inputActions, ILogger logger) in UnrealEngine\Engine\Source\Programs\UnrealBuildTool\Executors\UnrealBuildAccelerator\UBAExecutor.cs:line 617
   at UnrealBuildTool.ActionGraph.InternalExecuteActions(ActionExecutor Executor, List`1 ActionsToExecute, ILogger Logger) in UnrealEngine\Engine\Source\Programs\UnrealBuildTool\Actions\ActionGraph.cs:line 435

Safety check w UBAExecutor.cs

Wewnątrz metody UBAExecutor.Init (około linii 315 w UBAExecutor.cs), silnik w jawny sposób wymusza weryfikację bezpieczeństwa pod kątem rekurencji:

// Engine/Source/Programs/UnrealBuildTool/Executors/UnrealBuildAccelerator/UBAExecutor.cs
public void Init(IEnumerable<TargetDescriptor> TargetDescriptors, ILogger Logger)
{
    if (UnrealBuildTool.IsRecursive)
    {
        throw new Exception("UBA executor is not expected to be invoked from a recursive UBT call.");
    }
    
    // Virtualized file system and network initialization follow...
}

Ta weryfikacja (safety check) istnieje z bardzo ważnego powodu. Lokalny agent UBA binduje się do określonych portów TCP w celu komunikacji z wirtualizowanymi procesami pomocniczymi kompilatora.

Gdyby rekurencyjne wywołanie UBT próbowało zainicjować drugą instancję UBA executora, obie instancje próbowałyby powiązać się z tymi samymi gniazdami sieciowymi (sockets) i konfliktowałyby w kwestii hooków wirtualizowanego systemu plików. Prowadziłoby to do błędów alokacji gniazd, uszkodzenia systemu plików lub permanentnego zakleszczenia (deadlock) procesu budowania. Ten wyjątek stanowi barierę ochronną.

Przyczyna główna: Błąd wyboru executora

Prawdziwym błędem w buildach ze źródeł Unreal Engine 5.8.0 nie jest ta weryfikacja bezpieczeństwa. Jest nim raczej błąd w logice fabryki executorów (executor factory), która nie obsługuje poprawnie wywołań rekurencyjnych.

Kiedy UBT decyduje, którego executora użyć, odpytuje klasę ExecutorFactory.cs. Fabryka sprawdza, czy UBA jest włączone i dostępne na maszynie hosta.

Nie weryfikuje jednak, czy bieżący proces UBT jest wykonywany rekurencyjnie. W rezultacie, gdy uruchamiany jest pomocniczy proces UBT w celu skompilowania UnrealHeaderTool, fabryka próbuje przypisać UBAExecutor do zagnieżdżonego builda, wywołując crash w metodzie Init.

Pułapka konfiguracji XML

Wielu programistów próbuje obejść ten problem poprzez modyfikację globalnego pliku BuildConfiguration.xml. Standardowa porada na starszych forach sugeruje wyłączenie UBA poprzez dezaktywację następującego tagu:

<?xml version="1.0" encoding="utf-8" ?>
<Configuration xmlns="https://www.unrealengine.com/BuildConfiguration">
    <BuildConfiguration>
        <bAllowUBA>false</bAllowUBA>
    </BuildConfiguration>
</Configuration>

Dlaczego bAllowUBA jest ignorowane w UE 5.8

Jeśli zastosujesz powyższą konfigurację XML w buildzie ze źródeł Unreal Engine 5.8.0, kompilator nadal będzie próbował uruchomić UBA i wyrzuci wyjątek. Wynika to z faktu, że system konfiguracji budowania silnika został zrefaktoryzowany.

Stara flaga bAllowUBA jest przestarzała (deprecated) i nie jest już powiązana z logiką wyboru executora. Zamiast tego zachowanie UBA jest kontrolowane przez dwie oddzielne właściwości: bAllowUBAExecutor oraz bAllowUBALocalExecutor.

Ponieważ UBT nie znajduje właściwości bAllowUBAExecutor w Twoim pliku XML, przyjmuje ona domyślną wartość true. To po cichu nadpisuje próbę wyłączenia UBA.

Prawidłowa struktura XML dla konfiguracji UBA

Aby skutecznie wyłączyć UBA za pomocą plików konfiguracyjnych, musisz użyć zaktualizowanych nazw właściwości XML. Poniżej znajduje się poprawna struktura, która zmusza UBT do powrotu do standardowych lokalnych executorów:

<?xml version="1.0" encoding="utf-8" ?>
<Configuration xmlns="https://www.unrealengine.com/BuildConfiguration">
    <BuildConfiguration>
        <bAllowUBAExecutor>false</bAllowUBAExecutor>
        <bAllowUBALocalExecutor>false</bAllowUBALocalExecutor>
    </BuildConfiguration>
</Configuration>

Ta konfiguracja skutecznie pomija UBA. Całkowite wyłączenie UBA oznacza jednak utratę znacznych korzyści w szybkości kompilacji, jakie oferuje ono dla głównych akcji budowania.

Instrukcja krok po kroku, jak naprawić ten błąd

W zależności od Twojego workflow, możesz zdecydować się na rozwiązanie tego problemu globalnie poprzez modyfikację konfiguracji XML lub poprzez bezpośrednie spatchowanie kodu źródłowego UBT, co pozwoli zachować akcelerację kompilacji dla wywołań nierekurencyjnych.

Metoda 1: Nadpisanie BuildConfiguration.xml

Jeśli nie chcesz modyfikować kodu źródłowego silnika, możesz wyłączyć UBA globalnie. To najszybszy sposób na uruchomienie kompilacji projektu, chociaż wydłuży czas pełnego budowania (clean build) o około 40% do 60% w zależności od sprzętu.

  1. Znajdź lub utwórz globalny plik BuildConfiguration.xml. W systemie Windows znajduje się on zazwyczaj w lokalizacji %AppData%\Roaming\Unreal Engine\UnrealBuildTool\BuildConfiguration.xml. W systemie Linux znajduje się on w ~/.config/Unreal Engine/UnrealBuildTool/BuildConfiguration.xml.
  2. Otwórz plik XML w edytorze tekstu.
  3. Zastąp zawartość zaktualizowanym schematem XML przedstawionym poniżej.
  4. Zapisz plik i uruchom ponownie proces budowania w Visual Studio.
<?xml version="1.0" encoding="utf-8" ?>
<Configuration xmlns="https://www.unrealengine.com/BuildConfiguration">
    <BuildConfiguration>
        <bAllowUBAExecutor>false</bAllowUBAExecutor>
        <bAllowUBALocalExecutor>false</bAllowUBALocalExecutor>
    </BuildConfiguration>
</Configuration>

Metoda 2: Spatchowanie kodu źródłowego UBT (Zalecane)

Ponieważ kompilujesz Unreal Engine 5.8 ze źródeł, zalecanym rozwiązaniem jest spatchowanie kodu źródłowego UBT napisanego w języku C#. Pozwoli to na działanie UBA dla głównego targetu kompilacji, jednocześnie wymuszając powrót do standardowego executora ParallelExecutor podczas wywołań rekurencyjnych.

  1. Przejdź do katalogu źródłowego UBT: Engine/Source/Programs/UnrealBuildTool/Executors/.
  2. Otwórz plik ExecutorFactory.cs w Visual Studio lub edytorze tekstu.
  3. Znajdź metodę Create. Odpowiada ona za ewaluację konfiguracji i zwrócenie odpowiedniego ActionExecutor.
  4. Zmodyfikuj warunek wyboru UBA, dodając sprawdzenie pod kątem rekurencyjnych wywołań UBT.
// Engine/Source/Programs/UnrealBuildTool/Executors/ExecutorFactory.cs

public static ActionExecutor Create(BuildConfiguration BuildConfiguration, List<TargetDescriptor> TargetDescriptors, ILogger Logger)
{
    // Check if UBA is allowed, available, and NOT running recursively
-   if (BuildConfiguration.bAllowUBAExecutor && UBAExecutor.IsAvailable())
+   if (BuildConfiguration.bAllowUBAExecutor && UBAExecutor.IsAvailable() && !UnrealBuildTool.IsRecursive)
    {
        return new UBAExecutor(BuildConfiguration, Logger);
    }

    // Fall back to IncrediBuild if configured
    if (BuildConfiguration.bAllowXGE)
    {
        return new XGEExecutor(BuildConfiguration, Logger);
    }

    // Fall back to standard ParallelExecutor
    return new ParallelExecutor(BuildConfiguration, Logger);
}

Ten patch zapobiega wyborowi UBA executora przez zagnieżdżoną instancję UBT. Zamiast tego budowanie rekurencyjne (takie jak kompilacja UnrealHeaderTool) zostanie bezpiecznie przeprowadzone z użyciem lokalnego executora ParallelExecutor, podczas gdy główny proces kompilacji silnika nadal będzie w pełni korzystał z możliwości UBA.

Metoda 3: Flagi linii poleceń

Jeśli uruchamiasz proces budowania za pomocą własnych skryptów w linii poleceń lub potoków CI/CD, możesz wyłączyć UBA indywidualnie dla każdego wywołania. Jest to szczególnie przydatne, jeśli chcesz zachować włączone UBA dla lokalnych programistów, ale wyłączyć je na zdalnych serwerach budujących.

Aby to zrobić, dodaj flagę -NoUBA do swojego polecenia budowania UBT:

# Example command to build the editor without UBA
Engine\Build\BatchFiles\Build.bat UnrealEditor Win64 Development -NoUBA

Alternatywnie możesz zmusić UBT do użycia standardowego executora równoległego, wskazując go bezpośrednio za pomocą flagi -Executor:

Engine\Build\BatchFiles\Build.bat UnrealEditor Win64 Development -Executor=Parallel

Czyszczenie i ponowne generowanie środowiska budowania

Po zastosowaniu któregokolwiek z powyższych rozwiązań, kompilacja UBT może się nie powieść ze względu na zbuforowane pliki zestawów (assembly files) lub nieaktualne metadane pośrednie. Aby upewnić się, że poprawka wejdzie w życie w czysty sposób, należy wyczyścić wygenerowane pliki binarne narzędzi budowania i wygenerować pliki projektu na nowo.

Dla środowisk Windows

Uruchom następujące polecenia w wierszu poleceń lub konsoli PowerShell w katalogu źródłowym Unreal Engine:

:: Delete the cached UBT assembly and build binaries
rd /s /q Engine\Intermediate\Build\UnrealBuildTool
rd /s /q Engine\Binaries\DotNET\UnrealBuildTool

:: Regenerate the project files
GenerateProjectFiles.bat

Dla środowisk Linux i macOS

Uruchom te polecenia w swoim terminalu:

# Remove cached build tool data
rm -rf Engine/Intermediate/Build/UnrealBuildTool
rm -rf Engine/Binaries/DotNET/UnrealBuildTool

# Regenerate project files
./GenerateProjectFiles.sh

Po wyczyszczeniu środowiska otwórz wygenerowany plik rozwiązania (UE5.sln) w Visual Studio i przebuduj target. Kompilacja powinna teraz pomyślnie przejść przez fazę rekurencyjnego budowania, nie wywołując wyjątku executora.

Sprawdzone praktyki przy budowaniu Unreal Engine ze źródeł

Kompilacja własnego forka silnika ze źródeł wiąże się z różnymi wyzwaniami w obszarze kompilacji, optymalizacji i pakowania. Zastosowanie się do poniższych dobrych praktyk pomoże Ci utrzymać stabilny i wysoce zoptymalizowany pipeline deweloperski.

1. Optymalizacja wielowątkowości w celu zapobiegania brakom pamięci

Współczesne kompilatory C++ wymagają znacznej ilości pamięci RAM na każdy wątek kompilacji. Podczas budowania dużych modułów silnika, UBT może uruchomić więcej równoległych zadań, niż pozwala na to pojemność pamięci RAM Twojego systemu, co prowadzi do intensywnego stronicowania (thrashingu pliku wymiany) lub awarii kompilatora.

Możesz ograniczyć maksymalną liczbę wątków/procesorów, konfigurując ustawienia ParallelExecutor w pliku BuildConfiguration.xml:

<Configuration xmlns="https://www.unrealengine.com/BuildConfiguration">
    <ParallelExecutor>
        <MaxProcessorCount>16</MaxProcessorCount>
        <ProcessorCountMultiplier>1.0</ProcessorCountMultiplier>
    </ParallelExecutor>
</Configuration>

Zaleca się ograniczenie liczby wątków do około 2 GB pamięci RAM na wątek. Na przykład na maszynie wyposażonej w 32 GB pamięci RAM należy ograniczyć MaxProcessorCount do 16.

2. Opanowanie asset strippingu dla Dedicated Server

Wdrażając grę w chmurze, należy unikać kompilowania zbędnych assetów klienckich (takich jak tekstury UI, pliki audio czy siatki geometryczne) w pliku binarnym dla Dedicated Server. Pozwala to zmniejszyć czas uruchamiania serwera oraz jego narzut pamięciowy (memory footprint), co jest kluczowe dla efektywnego skalowania floty serwerów.

Aby dowiedzieć się, jak skonfigurować skrypty budowania w celu wykluczenia tych assetów, zapoznaj się z naszym szczegółowym przewodnikiem Unreal Engine Dedicated Server Asset Stripping.

3. Wczesna weryfikacja spójności Blueprintów i pakietów

Pomyślne zbudowanie silnika nie gwarantuje, że proces pakowania (packaging) projektu zakończy się bez błędów. Przestarzałe blueprinty lub błędy serializacji często powodują awarie na końcowym etapie pakowania.

Aby zapobiec blokowaniu potoku wydań (release pipeline) przez te problemy, zapoznaj się z naszym artykułem Resolving the Unreal Package HasValidBlueprint Ensure Crash, w którym opisano konfigurację automatycznych testów walidacyjnych.

4. Prawidłowa konfiguracja buforowania UBA VFS

Jeśli zdecydujesz się zachować włączone UBA za pomocą patcha C#, skonfiguruj cache wirtualnego systemu plików tak, aby lokalnie zapisywał skompilowane pliki obiektowe. Pozwoli to uniknąć ponownej kompilacji niezmienionych plików źródłowych przy przełączaniu branchy.

Dodaj blok konfiguracyjny UnrealBuildAccelerator do pliku XML, aby włączyć cache'owanie:

<Configuration xmlns="https://www.unrealengine.com/BuildConfiguration">
    <UnrealBuildAccelerator>
        <WriteCache>true</WriteCache>
        <Cache>127.0.0.1</Cache>
    </UnrealBuildAccelerator>
</Configuration>

Upewnij się, że Twój firewall nie blokuje portów komunikacji lokalnej sieci UBA, które służą do zarządzania pętlą synchronizacji cache.

Wzniesienie architektury Backend na wyższy poziom

Rozwiązanie problemów z budowaniem, takich jak unreal engine uba executor ubt error, jest kluczowe dla zachowania płynności pracy zespołu programistów. Niemniej jednak konfiguracja lokalnego środowiska kompilacji to zaledwie pierwszy krok na drodze do stworzenia nowoczesnej gry multiplayer.

Gdy Twój spersonalizowany silnik jest już skompilowany, a Dedicated Servers są gotowe, musisz zmierzyć się ze skomplikowanymi wyzwaniami związanymi z infrastrukturą backendową. Samodzielne pisanie systemów orkiestracji serwerów, algorytmów Matchmaking oraz baz danych od zera pochłania całe tygodnie pracy programistów.

Konfiguracja Load Balancing, sharding baz danych oraz zarządzanie certyfikatami SSL mogą z łatwością zająć od 4 do 6 tygodni pracy. Z horizOn te usługi backendowe otrzymujesz już skonfigurowane i gotowe do skalowania.

Zamiast pisać kod infrastruktury, możesz zintegrować dane graczy, lobby w czasie rzeczywistym i skalowanie serwerów za pomocą kilku prostych wywołań API. Pozwala to na wdrażanie buildów Dedicated Server i zarządzanie stanem graczy bez żadnego narzutu konserwacyjnego. Twój zespół może skupić się na mechanice rozgrywki i funkcjonalnościach silnika, podczas gdy horizOn zajmuje się infrastrukturą chmurową.

Kolejne kroki

Aby rozwiązać ten błąd kompilacji, wybierz szybkie nadpisanie pliku XML lub zastosuj patch w kodzie źródłowym C# w pliku ExecutorFactory.cs. Gdy Twój build pipeline zacznie działać sprawnie, poszukaj sposobów na zoptymalizowanie workflow wdrażania projektu. Jeśli chcesz skalować grę multiplayer bez narzutu związanego z utrzymywaniem własnych serwerów, wypróbuj horizOn za darmo lub zapoznaj się z naszą dokumentacją API, aby dowiedzieć się więcej.


Źródło: Unhandled exception: Exception: UBA executor is not expected to be invoked from a recursive UBT call.