runtime: Empty ArrayPool not returning zero-inited array in .NET Core 3.0

In .NET Core 2.2 following code could run indefinitely (simple console app):

            var pool = ArrayPool<byte>.Create();

            var iteration = 0;
            while (true)
            {
                Console.WriteLine(iteration);

                var buffer = pool.Rent(32 * 1024);

                for (int i = 0; i < buffer.Length; i++)
                {
                    if (buffer[i] != 0)
                        throw new InvalidOperationException();
                }

                iteration++;
            }

In .NET Core 3.0 it fails around 50-100 iteration. Is this by design? I was assuming that if I’m not returning anything, then the pool would have to init new arrays and those would have zeros?

I’m using .NET Core 3.0 Preview 8 on Win10 x64

dotnet --version
3.0.100-preview8-013656

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 16 (12 by maintainers)

Most upvoted comments

In private pools, users can clear data at a point of their choosing. It does not have to be part of the pool API.

Such functionality could be added as opt-in. But code has already been written (as outlined in this issue) that relies on the clearing already being done.

I hadn’t comprehended/internalized that dotnet/coreclr#24504 changed both the shared and isolated pools; in my mind it only did it for the former. I’d be supportive of backing out this change for the latter… with the former, you have no control over who is returning what to the pool you’re using, but you do with the latter, and so the argument that the contents of the rented arrays is arbitrary is on shakier ground.

This is by design and is an optimization. See the PR https://github.com/dotnet/coreclr/pull/24504 for more information.

Summary of that PR: Since the contents of the array returned by ArrayPool<T>.Rent are undefined anyway, the ArrayPool<T> implementation is free to tell the GC to allocate the array from any available block of memory, including memory that contains old data which has not yet been zeroed by the GC. (I’m handwaving a bit, but I hope this conveys the general idea.)