Description

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;
Steps to Reproduce

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.

Callstack

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]

Have Comments or More Details?

There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-396123 in the post.

0
Login to Vote

Unresolved
CreatedSep 11, 2026
UpdatedSep 12, 2026
View Jira Issue