# Common ArrayPool Mistakes

In this post I review common mistakes I find (and sometimes make) when using `ArrayPool<T>` in .NET and C#. Examples target the .NET 10 runtime and the corresponding BCL.

`ArrayPool<T>` lets you rent arrays from a shared pool. It's typically used to obtain a temporary buffer for intermediate work before copying results to a final destination. When a method cannot update input data in place or requires a larger buffer for intermediate calculations, `ArrayPool<T>` is often beneficial: it avoids allocating a new array for every operation, reducing GC pressure and improving performance. The most common generic types used with `ArrayPool` are `byte` and `char`.

Typical usage involves three steps:

- Rent an array from the pool.
- Use the rented array for operations.
- Return the array to the pool.

An application can create a custom pool to control array counts and sizes in the pool, or it can use the `Shared` pool. For most scenarios the shared pool is sufficient.

In the rest of this blog post, I explore the common mistakes that developers make while using `ArrayPool<T>`.

The samples below can be copied into a *Program.cs* file and run as a top-level program. Add the `System.Buffers` namespace with a *using* directive.

```csharp
using System.Buffers;
```

## ArrayPool

### Mistake 1

A typical problem I observe (and make) is demonstrated in the `Problem1()` method:

```csharp
void Problem1()
{
    var buffer = ArrayPool<byte>.Shared.Rent(10);
    Enumerable.Sequence<byte>(0, 9, 1).ToArray().CopyTo(buffer);
    ArrayPool<byte>.Shared.Return(buffer, true);
    var rent = ArrayPool<byte>.Shared.Rent(10); // 👈
    Console.WriteLine(string.Join(' ', rent));
}
```

In the sample, a byte array is rented with a requested size of at least 10 bytes. The shared pool returns a 16-byte array in this case. The sequence `[0,1,2,...,9]` is copied to the array, which is then returned to the pool. Next, another array is rented and its contents are printed (all 16 bytes are zero):

```
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
```

The problem is that the second array is not returned to the pool. While this does not cause a memory leak, as arrays are eventually garbage collected, it defeats the purpose of using `ArrayPool`. If the pool exhausts its available arrays it must allocate new ones, which degrades performance.

### Mistake 2

The next code snippet shows another common mistake:

```csharp
void Problem2()
{
    var buffer = ArrayPool<byte>.Shared.Rent(10);
    Enumerable.Sequence<byte>(0, 9, 1).ToArray().CopyTo(buffer);
    ArrayPool<byte>.Shared.Return(buffer); // 👈
    var rent = ArrayPool<byte>.Shared.Rent(10);
    Console.WriteLine(string.Join(' ', rent));
}
```

The `Problem2()` method performs similar steps: it rents an array, writes data to it, and returns the array. Then it rents a new array and prints its contents to the console:

```
0 1 2 3 4 5 6 7 8 9 0 0 0 0 0 0
```

This is not always a problem in code that fully controls all `ArrayPool<T>` usage. However, if an application or library writes sensitive data to arrays (for example, API keys or passwords), other code paths might observe that data when renting an uncleared array. When sensitive data is involved, clear the array on return:

```csharp
ArrayPool<byte>.Shared.Return(rent, clearArray: true);
```

### Mistake 3

```csharp
void Problem3()
{
    var buffer = ArrayPool<byte>.Shared.Rent(1);
    ArrayPool<byte>.Shared.Return(buffer);
    ArrayPool<byte>.Shared.Return(buffer); // 👈
    var rent0 = ArrayPool<byte>.Shared.Rent(1);
    var rent1 = ArrayPool<byte>.Shared.Rent(1);
    Console.WriteLine(rent0 == rent1); // Writes 'True' on the console
}
```

In the `Problem3()` method, the rented buffer is returned twice. As a result, the pool contains two references to the same array instance. This means that subsequent rentals (*rent0* and *rent1*) may return the same object instance to different parts of the application simultaneously. Sharing the same array can lead to concurrency issues. Unfortunately, this error is difficult to detect or test.

## MemoryPool

`MemoryPool<T>` is a lesser-known alternative to `ArrayPool<T>`. It provides similar functionality and API surface as the array pool, but the rented memory comes with the concept of ownership. The object returned from the `Rent()` method is a disposable owner object.

```csharp
void MemPool()
{
    using (var pooled0 = MemoryPool<byte>.Shared.Rent(10))
    {
        Enumerable.Sequence<byte>(0, 9, 1).ToArray().CopyTo(pooled0.Memory);
        pooled0.Dispose();
        pooled0.Dispose();
    }
    using var pooled1 = MemoryPool<byte>.Shared.Rent(10);
    using var pooled2 = MemoryPool<byte>.Shared.Rent(10);
    Console.WriteLine(pooled1 == pooled2); // Writes 'False' on the console
}
```

`Rent()` returns a [memory owner](https://learn.microsoft.com/en-us/dotnet/api/system.buffers.imemoryowner-1?view=net-10.0) object with a *Memory* property and a *Dispose()* method. After using a rented memory from a `MemoryPool<T>`, the developer disposes the object either by explicitly calling `Dispose()` or using a `using` statement. This returns the object to the pool. Hence the lifetime of the callsite of renting the memory, and callsite returning the same memory may become detached. A developer, does not need to manually track the owning pool of the array, it is implicitly handled.

The `IMemoryOwner<T>` implementation [ArrayMemoryPoolBuffer](https://source.dot.net/#System.Memory/System/Buffers/ArrayMemoryPool.ArrayMemoryPoolBuffer.cs,3fa031cb76db3922) ensures the object is returned to the pool only once, even if disposed multiple times.

Note that while `MemoryPool<T>` helps prevent some mistakes (like double returns and missing returns), it does not clear the returned array.

## Conclusion

`ArrayPool<T>` and `MemoryPool<T>` provide allocation free access to temporary arrays, typically used for `byte` and `char` buffers. While the API is easy to use, developers must be aware of several caveats that are otherwise difficult to unit test. Good coding practices, code reviews can help avoid these mistakes and custom code analyzers can emit warnings for the above cases.

This post highlighted the most common errors and gave suggestions on how to address them.