/// <summary>
        /// Track SignalDispatches that are responsible for consolidation.
        /// Required even if single Sender instance is running and no lock is set in database.
        /// Tracking pending consolidations allows to choose only single consolidation root that will attach other dispatches to itself.
        /// </summary>
        /// <param name="signal"></param>
        /// <returns></returns>
        public virtual ConsolidationLock <TKey> GetOrAddLock(SignalDispatch <TKey> signal)
        {
            //Lock for signal group and this signal as ConsolidationRoot
            ConsolidationLock <TKey> groupLock = ToConsolidationLock(signal);
            bool isNewLock = _locksCache.AddIfUnique(groupLock);

            //Lock with same group key can be added in parallel
            //First added lock will be set ConsolidationRootId.
            groupLock = _locksCache.GetMatchingGroup(groupLock);
            bool isConsolidationRoot = EqualityComparer <TKey> .Default.Equals(
                groupLock.ConsolidationRootId, signal.SignalDispatchId);

            if (!isConsolidationRoot)
            {
                //Signal will not start consolidation, but will be attached to ConsolidationRoot
                return(groupLock);
            }

            if (!_settings.IsDbLockStorageEnabled)
            {
                return(groupLock);
            }

            if (isNewLock)
            {
                bool isDuplicate = _consolidationLockQueries.InsertOneHandleDuplicate(groupLock).Result;
                if (isDuplicate)
                {
                    //get latest value with same group key to cache it
                    //db can return null if some other process will clear ConsolidationLocks collection in the middle
                    groupLock = _consolidationLockQueries.FindExistingMatch(groupLock).Result;
                    _locksCache.ReplaceGroup(groupLock);
                }
            }

            if (groupLock == null)
            {
                return(null);
            }

            bool isLockedByCurrentSenderInstance = groupLock.LockedBy == _settings.LockedByInstanceId;
            bool isLockExpired = CheckIsExpired(groupLock, expireBeforehand: isLockedByCurrentSenderInstance);

            if (isLockExpired)
            {
                groupLock.LockedBy = _settings.LockedByInstanceId.Value;
                bool lockExtended = _consolidationLockQueries.ExtendLockTime(groupLock).Result;
                if (!lockExtended)
                {
                    //get latest value with same group key to store it in cache
                    //db can return null if some other process will clear ConsolidationLocks collection in the middle
                    groupLock = _consolidationLockQueries.FindExistingMatch(groupLock).Result;
                }
                //update LockedSinceUtc or LockedSinceUtc with LockedBy
                _locksCache.ReplaceGroup(groupLock);
            }

            return(groupLock);
        }
        //find methods
        public virtual Task <ConsolidationLock <ObjectId> > FindExistingMatch(ConsolidationLock <ObjectId> consolidationLock, CancellationToken token = default)
        {
            Expression <Func <ConsolidationLock <ObjectId>, bool> > filter =
                x => x.ReceiverSubscriberId == consolidationLock.ReceiverSubscriberId &&
                x.CategoryId == consolidationLock.CategoryId &&
                x.DeliveryType == consolidationLock.DeliveryType;

            return(FindOne(filter, token));
        }
Exemple #3
0
 public virtual ConsolidationLock <TKey> GetMatchingGroup(ConsolidationLock <TKey> item)
 {
     _itemsLock.EnterReadLock();
     try
     {
         return(_items.FirstOrDefault(x => x == item));
     }
     finally
     {
         _itemsLock.ExitReadLock();
     }
 }
Exemple #4
0
        //methods
        public bool Execute(SignalWrapper <SignalDispatch <TKey> > item)
        {
            ConsolidationLock <TKey> groupLock = _consolidationLockTracker.GetOrAddLock(item.Signal);

            if (groupLock == null)
            {
                //other process cleared consolidation locks table in the middle
                //just repeat processing later
                _dispatchQueue.ApplyResult(item, ProcessingResult.Repeat);
                return(false);
            }

            bool isLockedByCurrentInstance = groupLock.LockedBy == _settings.LockedByInstanceId;

            if (!isLockedByCurrentInstance)
            {
                //this consolidation is handled by other Sender instance.
                //databaes Signal will be removed after consolidation finished but responsible Sender instance.
                return(false);
            }

            bool isConsolidationRoot = EqualityComparer <TKey> .Default.Equals(
                groupLock.ConsolidationRootId, item.Signal.SignalDispatchId);

            if (isConsolidationRoot)
            {
                //lock was created and this dispatch is now consolidation root.
                //can start consolidation.
                return(true);
            }

            bool willBeConsolidatedWithCurrentBatch = item.Signal.CreateDateUtc < groupLock.ConsolidationRootSendDateUtc;

            if (willBeConsolidatedWithCurrentBatch)
            {
                //this dispatch will be atteched to ConsolidationRoot.
                //databaes Signal will be removed after consolidation finished.
                return(false);
            }

            //next consolidation batch should start after current batch is finished.
            //because need to prevent selecting extra items from previous batch and attach them twice to different consolidation batches.
            //repeat processing item later.
            _dispatchQueue.ApplyResult(item, ProcessingResult.Repeat);
            return(false);
        }
        //update methods
        public virtual async Task <bool> ExtendLockTime(ConsolidationLock <ObjectId> lockToExtend, CancellationToken token = default)
        {
            Expression <Func <ConsolidationLock <ObjectId>, bool> > filter =
                x => x.ReceiverSubscriberId == lockToExtend.ReceiverSubscriberId &&
                x.CategoryId == lockToExtend.CategoryId &&
                x.DeliveryType == lockToExtend.DeliveryType &&
                x.LockedSinceUtc == lockToExtend.LockedSinceUtc;    //make sure it was not updated by some other Sender instance already

            var updates = Updates <ConsolidationLock <ObjectId> > .Empty()
                          .Set(x => x.LockedSinceUtc, DateTime.UtcNow)
                          .Set(x => x.LockedBy, lockToExtend.LockedBy);

            long docsUpdates = await UpdateOne(filter, updates, token).ConfigureAwait(false);

            bool lockExtended = docsUpdates > 0;

            return(lockExtended);
        }
Exemple #6
0
        //methods
        public bool AddIfUnique(ConsolidationLock <TKey> item)
        {
            _itemsLock.EnterWriteLock();
            bool added = false;

            try
            {
                ConsolidationLock <TKey> sameGroupLock = _items.FirstOrDefault(x => x == item);
                if (sameGroupLock == null)
                {
                    added = true;
                    _items.Add(item);
                }
            }
            finally
            {
                _itemsLock.ExitWriteLock();
            }

            return(added);
        }
        protected virtual bool CheckIsExpired(ConsolidationLock <TKey> consolidationLock, bool expireBeforehand)
        {
            bool isLockingEnabled = _settings.IsDbLockStorageEnabled;

            if (!isLockingEnabled)
            {
                //if only single Sender instance and database locking is disabled, then can not expire.
                return(false);
            }

            DateTime lockExpirationTime = consolidationLock.LockedSinceUtc.Value
                                          .Add(_settings.LockDuration);

            if (expireBeforehand)
            {
                //if lock is close to expiration, update it's value in database, so it is not expired during consolidation.
                //but do not treat as expired locks set by other Sender instances.
                lockExpirationTime = lockExpirationTime.Subtract(_expireBeforehandInterval);
            }

            return(lockExpirationTime < DateTime.UtcNow);
        }
Exemple #8
0
        public void WhenEqualSignIsUsedForNotMatchingLocks_ReturnsFalse()
        {
            //prepare
            var item1 = new ConsolidationLock <long>
            {
                CategoryId           = 1,
                DeliveryType         = 1,
                ReceiverSubscriberId = 1
            };
            var item2 = new ConsolidationLock <long>
            {
                CategoryId           = 1,
                DeliveryType         = 1,
                ReceiverSubscriberId = 2
            };

            //act
            bool areEqual = item1 == item2;

            //assert
            areEqual.ShouldBeFalse();
        }
Exemple #9
0
        public void ReplaceGroup(ConsolidationLock <TKey> item)
        {
            if (item == null)
            {
                return;
            }

            _itemsLock.EnterWriteLock();
            try
            {
                ConsolidationLock <TKey> sameGroupLock = _items.FirstOrDefault(x => x == item);
                if (sameGroupLock != null)
                {
                    _items.Remove(sameGroupLock);
                }

                _items.Add(item);
            }
            finally
            {
                _itemsLock.ExitWriteLock();
            }
        }
 public Task <bool> InsertOneHandleDuplicate(ConsolidationLock <long> entity, CancellationToken token = default)
 {
     throw new NotImplementedException();
 }
 public Task <ConsolidationLock <long> > FindExistingMatch(ConsolidationLock <long> consolidationLock, CancellationToken token = default)
 {
     throw new NotImplementedException();
 }
 public Task <bool> ExtendLockTime(ConsolidationLock <long> lockToExtend, CancellationToken token = default)
 {
     throw new NotImplementedException();
 }