/// <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); }