1
\$\begingroup\$

i've recently released a from scratch set of library Astron, and I wanted to get my memory policy logic reviewed.(you can find a little doc about it here).

My first goal was to provide an extandable API to allow the user to implement his own memory policy, but I've also provided some base implementations especially one that use the ArrayPool<byte>.Shared instance in order to handle the IMemoryOwner<byte> logic. Thanks to M. Gravell and its implementation, I didn't had much work to do about it.

According to the specifications, I first defined my memory policy interface :

public interface IMemoryPolicy
{
    Memory<T> GetArray<T>(int size);
    IMemoryOwner<T> GetOwnedArray<T>(int size);
}

You may have noticed that this interface can return an IMemoryOwner<T> which is an interface from the BCL, therefore I then had to implement it.

As the memory policy may be used in a multi-threaded context, its behavior must be handled in a thread-safe way, and this is where M.Gravell's implementation come in action. He used the Interlocked class to implement thread-safety, which I did accordingly. So I just had to abstract its implementation to fit the specifications :

    public abstract class PoolOwner<T> : IMemoryOwner<T>
    {
        private readonly int _length;
        private T[] _oversized;

        public Memory<T> Memory => new Memory<T>(GetArray(), 0, _length);

        protected PoolOwner(T[] oversized, int length)
        {
            if (length > oversized.Length) throw new ArgumentOutOfRangeException(nameof(length));

            _length = length;
            _oversized = oversized;
        }

        protected abstract void ReturnToPool(T[] array);

        protected T[] GetArray() =>
            Interlocked.CompareExchange(ref _oversized, null, null)
            ?? throw new ObjectDisposedException(ToString());

        public void Dispose()
        {
            var arr = Interlocked.Exchange(ref _oversized, null);
            if (arr != null) ReturnToPool(arr);
        }
    }

Now the user only have to implement this class with the abstract void ReturnToPool(T[] array); method to have a thread-safe IMemoryOwner<T>. Also, this class was unit-tested you can find the tests on my repo here. Then I just had to implement it and create the relative policy which make usage of my IMemoryOwner<T> implementation :

    internal sealed class SharedPoolOwner<T> : PoolOwner<T>
    {
        public SharedPoolOwner(T[] oversized, int length) : base(oversized, length)
        {
        }

        protected override void ReturnToPool(T[] array) => ArrayPool<T>.Shared.Return(array);
    }
    public class HeapAllocWithSharedPoolPolicy : IMemoryPolicy
    {
        private static IMemoryOwner<T> EmptyOwner<T>() => SimpleMemoryOwner<T>.Empty;
        private static T[] Empty<T>() => Array.Empty<T>();

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Memory<T> GetArray<T>(int size)
        {
            if (size == 0) return Empty<T>();
            if (size < 0) throw new ArgumentOutOfRangeException(nameof(size));

            return new T[size];
        }

        public IMemoryOwner<T> GetOwnedArray<T>(int size)
        {
            if (size == 0) return EmptyOwner<T>();
            if (size < 0) throw new ArgumentOutOfRangeException(nameof(size));

            var arr = ArrayPool<T>.Shared.Rent(size);
            return new SharedPoolOwner<T>(arr, size);
        }
    }

(unit-tests here)

Finally, here are my questions :

  • How relevant is the usage of the shared ArrayPool in production context ? How about implementing its own pool ? Allocating its own instance of the pool ?
  • Should I had more methods to the memory policy with some constraints on T in order to handle more behaviors ? I assume this could also be used to pool objects in the future, i've made it for networking context in order to get buffers but it could also pool Socket objects
  • M.Gravell is monitoring the leak count with its implementation, I don't see any point on doing that because if they're leaked they can't be unleaked then who cares ?

Any suggestions is welcome, thanks you very much for reading me.

\$\endgroup\$
  • \$\begingroup\$ Could you add some info about what such a memory-policy is good for? \$\endgroup\$ – t3chb0t May 21 at 10:23
  • 1
    \$\begingroup\$ Yeah for sure, I mean, the code is pretty straightforward, what kind of info do you want me to add ? This kind of thing allow you to modify the behavior of the memory allocation as you want, also if standards array allocations are replaced by the policy, I assume the app becomes more maintainable and also this could helps on tracing. I also believe this is a good practice to have a memory policy to ensure to use of a pool, even if there is a GC or a static Shared pool \$\endgroup\$ – NamelessK1NG May 21 at 10:46
  • \$\begingroup\$ This kind of thing allow you to modify the behavior of the memory allocation as you want - this is exactly the piece of information I was looking for. I was't aware of that and didn't know it was possible. Maybe you even have a link to some interesting article about it? \$\endgroup\$ – t3chb0t May 21 at 10:50
  • \$\begingroup\$ Sorry I didn't meant that, you can't modify the way the GC allocate on the heap you can just alter the behavior on top of that with such an implementation (also add logging, multi types of pool support according to T etc...). I don't have any link to go deeper on the subject and provide you some more behaviors the policy could handle, but if you make any don't mind publishing it ;-P \$\endgroup\$ – NamelessK1NG May 21 at 10:54
1
\$\begingroup\$
  1. it is usually a good idea to use the shared pool to maximize re-use; however, if you're specifically worried about code incorrectly (or maliciously) snooping into your arrays (without even needing a memory debugger - just by "returning" things to the pool, but keeping a reference to them), then you might want to have your own pool; of course, this is not a true security barrier, as if someone is malicious enough, they can just use a memory debugger anyway
  2. (no opinion)
  3. for testing purposes and making sure that you aren't routinely accidentally dropping them on the floor
\$\endgroup\$
  • \$\begingroup\$ 1. By snooping code maliciously into my arrays you mean by injecting something into my app like reverse engineer do to alter the behavior within an application ? Therefore making its own pool is only relevant for security and customization ? Yeah a security is never perfect anyway :/ 3. So i should monitor them and make some checks wherever I want to use the owner to ensure nothing is lost ? \$\endgroup\$ – NamelessK1NG May 21 at 10:15
  • \$\begingroup\$ ...as if you had a script for watching SE for questions mentioning your tools ;-P \$\endgroup\$ – t3chb0t May 21 at 10:26
  • \$\begingroup\$ @t3chb0t no, they emailed me... \$\endgroup\$ – Marc Gravell May 21 at 10:42
  • \$\begingroup\$ @NamelessK1NG let's put it this way: if I was doing crypto, I'd probably use my own pool \$\endgroup\$ – Marc Gravell May 21 at 10:43
  • \$\begingroup\$ I wasn't expecting such a fast answer either ._. Good thing to know, thanks a lot for your time and answers ! \$\endgroup\$ – NamelessK1NG May 21 at 10:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.