In the project that I am involved in we recently had a situation were we needed to manage a certain amount of user accounts to be used for a back end system. The back end system runs on a number of sites. Our system receives jobs where each job is to be performed on a site. Based on the current pressure on our system, sessions to these sites will be opened as needed. There is one constraint posed on us from this back end system, and that is that each session must be opened with a unique user account. So, we needed a way to manage those user accounts in a way so that they are served when a session is about to be opened. However, the system must also make sure that each site can get at least one user account, so one active site may not drain the pool of user accounts, even if there is no current activity at other sites.

So given some though, we came up with the following solution, that I call ParitionedPool. The way it works is in short that it is initialized with a pool of items to manage, and a list of keys, partitions, amongst which the items are to be distributed. When an item is requested from a pool, the request is done for a certain partition. The pool will then serve the request if the following rules are fulfilled:

  • The pool is not empty
  • The number of items in the pool is greater than the number of partitions that are currently without items

Here is a somewhat reworked version of what we came up with:

public class PartitionedPool<TItem, TPartitionKey>
{

    private readonly Stack<TItem> _poolStack;

    private readonly Dictionary<TPartitionKey, IList<TItem>> _itemsPerKey;


    public PartitionedPool(IEnumerable<TItem> poolItems, IEnumerable<TPartitionKey> partitionKeys)
    {
        _poolStack = new Stack<TItem>(poolItems);
        _itemsPerKey = new Dictionary<TPartitionKey, IList<TItem>>();
        foreach (TPartitionKey item in partitionKeys)
        {
            _itemsPerKey.Add(item, new List<TItem>());
        }
    }


    /// <summary>
    /// Gets an item from the pool and associates it with the given partition.
    /// </summary>
    /// <param name="partitionKey">The partition to which to associate the item.</param>
    public TItem GetItem(TPartitionKey partitionKey)
    {

        if (partitionKey == null)
        {
            throw new ArgumentNullException("partitionKey");
        }

        if (!_itemsPerKey.ContainsKey(partitionKey))
        {
            throw new KeyNotFoundException();
        }

        TItem result = default(TItem);

        var emtpyPartitions = from pair in _itemsPerKey
                              where pair.Value.Count == 0 && !pair.Key.Equals(partitionKey)
                              select pair;

        if (_poolStack.Count - emtpyPartitions.Count() > 0)
        {
            // it's OK to serve the request, pop an item off the stack
            // and put it into the corresponding partition list
            result = _poolStack.Pop();
            _itemsPerKey[partitionKey].Add(result);
        }

        // return the result
        return result;
    }

    public void ReleaseItem(TItem item)
    {
        if (item == null)
        {
            throw new ArgumentNullException("item");
        }

        var partitionFromItem = from pair in _itemsPerKey
                                where pair.Value.Contains(item)
                                select pair;

        if (partitionFromItem.Any())
        {
            if (partitionFromItem.Count() == 1)
            {
                // remove the item from the partition list and push it
                // to the pool stack
                partitionFromItem.First().Value.Remove(item);
                _poolStack.Push(item);
            }
        }
        else
        {
            throw new InvalidOperationException("The given item is not managed by this pool");
        }
    }
}

Now we have prepared a generic pool that can manage resources of any type, connected to partition keys of any type, and do so in a type-safe manner.

Using the pool is very simple. First let's look at how to set it up:

// create a list of user accounts (assuming we have a UserAccount class 
// defined in our system, having Login and Password properties
IEnumerable<UserAccount> userAccounts = new List<UserAccount>
{
    new UserAccount { Login = "login 1", Password = "password 1" },
    new UserAccount { Login = "login 2", Password = "password 2" },
    new UserAccount { Login = "login 3", Password = "password 3" },
    new UserAccount { Login = "login 4", Password = "password 4" }
};

// create a list of site names
IEnumerable<string> sites = new List<string>
{
    "site 1",
    "site 2",
    "site 3"
};

// create a pool managing the 4 user accounts for the 3 sites. This means that no site can
// have more than 2 accounts associated to it at any time
PartitionedPool<UserAccount , string> pool = new PartitionedPool<UserAccount , string>(userAccounts, sites);

Then we just need to put it to good use:

// get a user account associated with "site 1"
UserAccount userAccount = pool.GetItem("site 1");

If an item request results in the pool deciding to not serve the request (either because the partition making the request has reached the maximum number of items, or if the pool is empty) it will return the default value for the item type (which would be a null reference for reference types).

Bookmark and Share