/// <summary>
        /// Must call this periodically to move work items from priority queue to pending queue
        /// </summary>
        public bool PrepareNextWorkItem()
        {
            //harvest zombies every 5 minutes
            var now = DateTime.UtcNow;
            var ts  = now - harvestTime;

            if (ts.TotalMinutes > 5)
            {
                HarvestZombies();
                harvestTime = now;
            }

            using (var disposableClient = clientManager.GetDisposableClient <SerializingRedisClient>())
            {
                var client = disposableClient.Client;

                //1. get next workItemId, or return if there isn't one
                var smallest = client.ZRangeWithScores(workItemIdPriorityQueue, 0, 0);
                if (smallest == null || smallest.Length <= 1 ||
                    RedisNativeClient.ParseDouble(smallest[1]) == CONVENIENTLY_SIZED_FLOAT)
                {
                    return(false);
                }
                var workItemId = client.Deserialize(smallest[0]) as string;

                // lock work item id
                var lockKey = queueNamespace.GlobalLockKey(workItemId);
                using (var disposableLock = new DisposableDistributedLock(client, lockKey, lockAcquisitionTimeout, lockTimeout))
                {
                    // if another client has queued this work item id,
                    // then the work item id score will be set to CONVENIENTLY_SIZED_FLOAT
                    // so we return false in this case
                    var score = client.ZScore(workItemIdPriorityQueue, smallest[0]);
                    if (score == CONVENIENTLY_SIZED_FLOAT)
                    {
                        return(false);
                    }

                    using (var pipe = client.CreatePipeline())
                    {
                        var rawWorkItemId = client.Serialize(workItemId);

                        // lock work item id in priority queue
                        pipe.QueueCommand(
                            r =>
                            ((RedisNativeClient)r).ZAdd(workItemIdPriorityQueue, CONVENIENTLY_SIZED_FLOAT, smallest[0]));

                        // track dequeue lock id
                        pipe.QueueCommand(r => ((RedisNativeClient)r).SAdd(dequeueIdSet, rawWorkItemId));

                        // push into pending set
                        pipe.QueueCommand(r => ((RedisNativeClient)r).LPush(pendingWorkItemIdQueue, rawWorkItemId));

                        pipe.Flush();
                    }
                }
            }
            return(true);
        }
 /// <summary>
 /// Queue incoming messages
 /// </summary>
 /// <param name="workItem"></param>
 /// <param name="workItemId"></param>
 public void Enqueue(string workItemId, T workItem)
 {
     using (var disposableClient = clientManager.GetDisposableClient <SerializingRedisClient>())
     {
         var client  = disposableClient.Client;
         var lockKey = queueNamespace.GlobalLockKey(workItemId);
         using (var disposableLock = new DisposableDistributedLock(client, lockKey, lockAcquisitionTimeout, lockTimeout))
         {
             using (var pipe = client.CreatePipeline())
             {
                 pipe.QueueCommand(r => ((RedisNativeClient)r).RPush(queueNamespace.GlobalCacheKey(workItemId), client.Serialize(workItem)));
                 pipe.QueueCommand(r => ((RedisNativeClient)r).ZIncrBy(workItemIdPriorityQueue, -1, client.Serialize(workItemId)));
                 pipe.Flush();
             }
         }
     }
 }
        /// <summary>
        /// Unlock work item id, so other servers can process items for this id
        /// </summary>
        /// <param name="workItemId"></param>
        private void Unlock(string workItemId)
        {
            if (workItemId == null)
            {
                return;
            }

            var key     = queueNamespace.GlobalCacheKey(workItemId);
            var lockKey = queueNamespace.GlobalLockKey(workItemId);

            using (var disposableClient = clientManager.GetDisposableClient <SerializingRedisClient>())
            {
                var client = disposableClient.Client;
                using (var disposableLock = new DisposableDistributedLock(client, lockKey, lockAcquisitionTimeout, lockTimeout))
                {
                    var len = client.LLen(key);
                    using (var pipe = client.CreatePipeline())
                    {
                        //untrack dequeue lock
                        pipe.QueueCommand(r => ((RedisNativeClient)r).SRem(dequeueIdSet, client.Serialize(workItemId)));

                        // update priority queue
                        if (len == 0)
                        {
                            pipe.QueueCommand(r => ((RedisNativeClient)r).ZRem(workItemIdPriorityQueue, client.Serialize(workItemId)));
                        }
                        else
                        {
                            pipe.QueueCommand(r => ((RedisNativeClient)r).ZAdd(workItemIdPriorityQueue, len, client.Serialize(workItemId)));
                        }


                        pipe.Flush();
                    }
                }
            }
        }