When repeatedly allocating and deallocating memory in a C# Job (not necessarily using burst) Unity can sometimes crash with a stack trace ending at the DynamicHeapAllocator::Allocate method (though I have seen various stacktraces). This crash is non-deterministic but I can reliably recreate it within one minute usually.
Is allocation in C# jobs actually supported? It seems to be since even the NativeList depends on this.
However these crashes seem to indicate there’s either a bug in my code or there is a bug in Unity’s code.
Below is a code sample that reproduces this. Using an IJobParallelFor is not strictly necessary, however I have found that it triggers the crash much more quickly.
using Unity.Jobs;
using UnityEngine;
using Unity.Mathematics;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs.LowLevel.Unsafe;
public class Repro : MonoBehaviour {
struct AllocationJob : IJobParallelFor {
[NativeDisableParallelForRestriction]
public NativeArray<UnsafeAppendBuffer> buffers;
[NativeSetThreadIndex]
private int threadIndex;
public void Execute(int index) {
// Safe since we index by thread index
var tmpBuffer = buffers[threadIndex];
// Simulate appending to the buffer
int dummySize = 200;
if (tmpBuffer.Size + dummySize > tmpBuffer.Capacity) {
tmpBuffer.SetCapacity(math.max(tmpBuffer.Size + dummySize, tmpBuffer.Capacity * 2));
}
tmpBuffer.Size += dummySize;
// Write back the buffer struct to the array
buffers[threadIndex] = tmpBuffer;
}
}
public void Update() {
// Allocate buffers
var commandBuffers = new NativeArray<UnsafeAppendBuffer>(JobsUtility.MaxJobThreadCount, Allocator.Persistent, NativeArrayOptions.ClearMemory);
for (int i = 0; i < commandBuffers.Length; i++) commandBuffers[i] = new UnsafeAppendBuffer(0, 4, Allocator.Persistent);
// Run the job
new AllocationJob {
buffers = commandBuffers,
}.Schedule(100 * 1000, 100).Complete();
// Free all buffers
for (int i = 0; i < commandBuffers.Length; i++) {
var cmd = commandBuffers[i];
cmd.Dispose();
commandBuffers[i] = cmd;
}
commandBuffers.Dispose();
}
}
In the script memory is allocated inside an UnsafeAppendBuffer with the Persistent allocator which internally uses Malloc and Free. Every frame a IJobParallelFor job is started which appends to some buffers multiple times. Note that multiple buffers are used and the job writes to the correct buffer depending on the thread index (set using the NativeSetThreadIndex attribute) to avoid different threads writing to the same memory locations. The memory is freed every frame after the job is complete.
I have verified that this happens in a few different versions of Unity 2019.3.
Having the profiler enabled seems to make the crash trigger more often since the profiler also allocates a lot of memory.
Is there anyone who knows if allocation inside C# jobs is definitely supposed to be supported?