A race condition exists in GetFunctionSig_Write() and other places where FNiagaraFunctionSignature is lazily initialized because multiple worker threads can progress past if (!Sig.IsValid()) and stomp on each other writing to the static FNiagaraVariable EmitVar (and potentially the static FNiagaraFunctionSignature Sig). We have seen this manifest when many Effect Instances using Data Channel write are spawned at the same time from a single system.
To mitigate, the signature initialisation can be moved inside a static lambda.
static const FNiagaraFunctionSignature OutSig = []
{
FNiagaraFunctionSignature Sig;
static FNiagaraVariable EmitVar(FNiagaraTypeDefinition::GetBoolDef(), TEXT("Emit"));
EmitVar.SetValue(FNiagaraBool(true));
Sig.Name = TEXT("Write");
#if WITH_EDITORONLY_DATA
NIAGARA_ADD_FUNCTION_SOURCE_INFO(Sig)
Sig.Description = LOCTEXT("WriteFunctionDescription", "Writes data into the Data Channel at a specific index. Values in the DataChannel that are not written here are set to their defaults. Returns success if the index was valid and data was written into the Data Channel.");
#endif
Sig.bMemberFunction = true;
Sig.bRequiresExecPin = true;
Sig.bWriteFunction = true;
Sig.bSupportsGPU = false;//Cannot use direct index writes on GPU as we write into one shared buffer with all DIs using the same NDC data.
Sig.AddInput(FNiagaraVariable(FNiagaraTypeDefinition(UNiagaraDataInterfaceDataChannelWrite::StaticClass()), TEXT("DataChannel interface")));
Sig.AddInput(EmitVar, LOCTEXT("ExecuteWriteFlagTooltip", "If true then the write is executed, if false then this call is ignored and no write occurs."));
Sig.AddInput(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Index")));
Sig.AddOutput(FNiagaraVariable(FNiagaraTypeDefinition::GetBoolDef(), TEXT("Success")));
Sig.RequiredInputs = IntCastChecked<int16>(Sig.Inputs.Num());//The user defines what we write in the graph.
return Sig;
}();
return OutSig;
This issue is difficult to reproduce. The most reliable way would be to add test only hook that waits for the init condition:
if (!Sig.IsValid())
{
#if NIAGARA_TEST
NiagaraDataChannelTestHooks::WaitAfterSignatureInvalidCheck();
#endif
Initialize many Niagara systems or instances at the same time such that multiple worker threads create and bind their script execution contexts concurrently and call GetVMExternalFunction(...) -> GetFunctionSig_Write();
Verify if multiple workers get into the initializer.
VCRUNTIME140!memcpy() [D:\a\_work\1\s\src\vctools\crt\vcruntime\src\string\amd64\memcpy.asm:220]
Game!FGenericPlatformMemory::Memcpy() [D:\p4\Game_Main_CI\Engine\Source\Runtime\Core\Public\GenericPlatform\GenericPlatformMemory.h:654]
Game!FMemory::Memcpy() [D:\p4\Game_Main_CI\Engine\Source\Runtime\Core\Public\HAL\UnrealMemory.h:161]
Game!FNiagaraVariable::SetValue() [D:\p4\Game_Main_CI\Engine\Plugins\FX\Niagara\Source\Niagara\Public\NiagaraTypes.h:1516]
Game!NDIDataChannelWriteLocal::GetFunctionSig_Write() [D:\p4\Game_Main_CI\Engine\Plugins\FX\Niagara\Source\Niagara\Private\DataInterface\NiagaraDataInterfaceDataChannelWrite.cpp:128]
There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-396123 in the post.
| 0 |
| Component | UE - Rendering - Architecture - Niagara |
|---|---|
| Affects Versions | 5.8 |
| Created | Sep 11, 2026 |
|---|---|
| Updated | Sep 12, 2026 |