Example #1
0
        /// <summary>
        /// Checks if any of the neighbors expired.
        /// If so, it starts the process of their removal.
        /// </summary>
        public void CheckExpiredNeighbors()
        {
            log.Trace("()");

            // If a neighbor server's LastRefreshTime is lower than this limit, it is expired.
            DateTime limitLastRefreshTime = DateTime.UtcNow.AddSeconds(-Base.Configuration.NeighborProfilesExpirationTimeSeconds);

            using (UnitOfWork unitOfWork = new UnitOfWork())
            {
                bool           success     = false;
                DatabaseLock[] lockObjects = new DatabaseLock[] { UnitOfWork.NeighborLock, UnitOfWork.NeighborhoodActionLock };
                using (IDbContextTransaction transaction = unitOfWork.BeginTransactionWithLock(lockObjects))
                {
                    try
                    {
                        List <Neighbor> expiredNeighbors = unitOfWork.NeighborRepository.Get(n => n.LastRefreshTime < limitLastRefreshTime, null, true).ToList();
                        if (expiredNeighbors.Count > 0)
                        {
                            log.Debug("There are {0} expired neighbors.", expiredNeighbors.Count);
                            foreach (Neighbor neighbor in expiredNeighbors)
                            {
                                // This action will cause our profile server to erase all profiles of the neighbor that has been removed.
                                NeighborhoodAction action = new NeighborhoodAction()
                                {
                                    ServerId         = neighbor.NeighborId,
                                    Timestamp        = DateTime.UtcNow,
                                    Type             = NeighborhoodActionType.RemoveNeighbor,
                                    TargetIdentityId = null,
                                    AdditionalData   = null
                                };
                                unitOfWork.NeighborhoodActionRepository.Insert(action);
                            }

                            unitOfWork.SaveThrow();
                            transaction.Commit();
                        }
                        else
                        {
                            log.Debug("No expired neighbors found.");
                        }

                        success = true;
                    }
                    catch (Exception e)
                    {
                        log.Error("Exception occurred: {0}", e.ToString());
                    }

                    if (!success)
                    {
                        log.Warn("Rolling back transaction.");
                        unitOfWork.SafeTransactionRollback(transaction);
                    }

                    unitOfWork.ReleaseLock(lockObjects);
                }
            }

            log.Trace("(-)");
        }
Example #2
0
        /// <summary>
        /// Checks if any of the follower servers need refresh.
        /// If so, a neighborhood action is created.
        /// </summary>
        public async Task CheckFollowersRefreshAsync()
        {
            log.Trace("()");

            // If a follower server's LastRefreshTime is lower than this limit, it should be refreshed.
            DateTime limitLastRefreshTime = DateTime.UtcNow.AddSeconds(-Config.Configuration.FollowerRefreshTimeSeconds);

            using (UnitOfWork unitOfWork = new UnitOfWork())
            {
                DatabaseLock[] lockObjects = new DatabaseLock[] { UnitOfWork.FollowerLock, UnitOfWork.NeighborhoodActionLock };
                await unitOfWork.AcquireLockAsync(lockObjects);

                try
                {
                    List <Follower> followersToRefresh = (await unitOfWork.FollowerRepository.GetAsync(f => f.LastRefreshTime < limitLastRefreshTime, null, true)).ToList();
                    if (followersToRefresh.Count > 0)
                    {
                        log.Debug("There are {0} followers that need refresh.", followersToRefresh.Count);
                        foreach (Follower follower in followersToRefresh)
                        {
                            NeighborhoodAction action = new NeighborhoodAction()
                            {
                                ServerId         = follower.FollowerId,
                                Type             = NeighborhoodActionType.RefreshProfiles,
                                Timestamp        = DateTime.UtcNow,
                                ExecuteAfter     = DateTime.UtcNow,
                                TargetIdentityId = null,
                                AdditionalData   = null
                            };

                            await unitOfWork.NeighborhoodActionRepository.InsertAsync(action);

                            log.Debug("Refresh neighborhood action for follower ID '{0}' will be inserted to the database.", follower.FollowerId.ToHex());
                        }

                        await unitOfWork.SaveThrowAsync();

                        log.Debug("{0} new neighborhood actions saved to the database.", followersToRefresh.Count);
                    }
                    else
                    {
                        log.Debug("No followers need refresh now.");
                    }
                }
                catch (Exception e)
                {
                    log.Error("Exception occurred: {0}", e.ToString());
                }

                unitOfWork.ReleaseLock(lockObjects);
            }

            log.Trace("(-)");
        }
        /// <summary>
        /// Processes NeighbourhoodChangedNotificationRequest message from LOC server.
        /// <para>Adds, changes, or deletes neighbor and possibly adds new neighborhood action to the database.</para>
        /// </summary>
        /// <param name="Client">TCP client who received the message.</param>
        /// <param name="RequestMessage">Full request message.</param>
        /// <returns>Response message to be sent to the client.</returns>
        public async Task <LocProtocolMessage> ProcessMessageNeighbourhoodChangedNotificationRequestAsync(LocClient Client, LocProtocolMessage RequestMessage)
        {
            log.Trace("()");

            LocProtocolMessage res     = Client.MessageBuilder.CreateErrorInternalResponse(RequestMessage);
            bool signalActionProcessor = false;

            NeighbourhoodChangedNotificationRequest neighbourhoodChangedNotificationRequest = RequestMessage.Request.LocalService.NeighbourhoodChanged;

            using (UnitOfWork unitOfWork = new UnitOfWork())
            {
                DatabaseLock[] lockObjects = new DatabaseLock[] { UnitOfWork.NeighborLock, UnitOfWork.NeighborhoodActionLock };
                using (IDbContextTransaction transaction = await unitOfWork.BeginTransactionWithLockAsync(lockObjects))
                {
                    bool success = false;
                    bool saveDb  = false;
                    try
                    {
                        int neighborhoodSize = await unitOfWork.NeighborRepository.CountAsync();

                        foreach (NeighbourhoodChange change in neighbourhoodChangedNotificationRequest.Changes)
                        {
                            // We do ignore errors here for each individual change and just continue processing the next item from the list.
                            log.Trace("Neighborhood change type is {0}.", change.ChangeTypeCase);
                            switch (change.ChangeTypeCase)
                            {
                            case NeighbourhoodChange.ChangeTypeOneofCase.AddedNodeInfo:
                            case NeighbourhoodChange.ChangeTypeOneofCase.UpdatedNodeInfo:
                            {
                                bool     isAdd    = change.ChangeTypeCase == NeighbourhoodChange.ChangeTypeOneofCase.AddedNodeInfo;
                                NodeInfo nodeInfo = isAdd ? change.AddedNodeInfo : change.UpdatedNodeInfo;

                                // Check whether a proximity server is running on this node.
                                // If not, it is not interesting for us at all, skip it.
                                int    proximityServerPort;
                                byte[] proximityServerId;
                                if (!HasProximityServerService(nodeInfo, out proximityServerPort, out proximityServerId))
                                {
                                    break;
                                }

                                NodeContact            contact   = nodeInfo.Contact;
                                IPAddress              ipAddress = new IPAddress(contact.IpAddress.ToByteArray());
                                Iop.Locnet.GpsLocation location  = nodeInfo.Location;
                                int latitude  = location.Latitude;
                                int longitude = location.Longitude;

                                AddOrChangeNeighborResult addChangeRes = await AddOrChangeNeighborAsync(unitOfWork, proximityServerId, ipAddress, proximityServerPort, latitude, longitude, neighborhoodSize);

                                neighborhoodSize = addChangeRes.NeighborhoodSize;

                                if (addChangeRes.SaveDb)
                                {
                                    saveDb = true;
                                }

                                if (addChangeRes.SignalActionProcessor)
                                {
                                    signalActionProcessor = true;
                                }

                                break;
                            }

                            case NeighbourhoodChange.ChangeTypeOneofCase.RemovedNodeId:
                            {
                                byte[] serverId = change.RemovedNodeId.ToByteArray();

                                bool serverIdValid = serverId.Length == ProtocolHelper.NetworkIdentifierLength;
                                if (!serverIdValid)
                                {
                                    log.Error("Received invalid neighbor server ID '{0}' from LOC server.", serverId.ToHex());
                                    break;
                                }

                                // Data processing.
                                Neighbor existingNeighbor = (await unitOfWork.NeighborRepository.GetAsync(n => n.NetworkId == serverId)).FirstOrDefault();
                                if (existingNeighbor != null)
                                {
                                    log.Trace("Creating neighborhood action to deleting neighbor ID '{0}' from the database.", serverId.ToHex());

                                    string neighborInfo = JsonConvert.SerializeObject(existingNeighbor);

                                    // Delete neighbor completely.
                                    // This will cause our proximity server to erase all activities of the neighbor that has been removed.
                                    bool deleted = await unitOfWork.NeighborRepository.DeleteNeighborAsync(serverId, -1, true);

                                    if (deleted)
                                    {
                                        // Add action that will contact the neighbor and ask it to stop sending updates.
                                        // Note that the neighbor information will be deleted by the time this action
                                        // is executed and this is why we have to fill in AdditionalData.
                                        NeighborhoodAction stopUpdatesAction = new NeighborhoodAction()
                                        {
                                            ServerId              = serverId,
                                            Timestamp             = DateTime.UtcNow,
                                            Type                  = NeighborhoodActionType.StopNeighborhoodUpdates,
                                            TargetActivityId      = 0,
                                            TargetActivityOwnerId = null,
                                            ExecuteAfter          = DateTime.UtcNow,
                                            AdditionalData        = neighborInfo
                                        };
                                        await unitOfWork.NeighborhoodActionRepository.InsertAsync(stopUpdatesAction);

                                        signalActionProcessor = true;
                                        saveDb = true;
                                    }
                                    else
                                    {
                                        log.Error("Failed to remove neighbor ID '{0}' from the database.", serverId.ToHex());
                                        // This is actually bad, we failed to remove a record from the database, which should never happen.
                                        // We try to insert action to remove this neighbor later, but adding the action might fail as well.
                                        NeighborhoodAction action = new NeighborhoodAction()
                                        {
                                            ServerId              = serverId,
                                            Timestamp             = DateTime.UtcNow,
                                            Type                  = NeighborhoodActionType.RemoveNeighbor,
                                            TargetActivityId      = 0,
                                            TargetActivityOwnerId = null,
                                            AdditionalData        = null
                                        };
                                        await unitOfWork.NeighborhoodActionRepository.InsertAsync(action);

                                        signalActionProcessor = true;
                                        saveDb = true;
                                    }
                                }
                                else
                                {
                                    log.Debug("Neighbor ID '{0}' not found, can not be removed.", serverId.ToHex());
                                    // It can be the case that this node has not an associated proximity server, so in that case we should ignore it.
                                    // If the node has an associated proximity server, then nothing bad really happens here if we have activities
                                    // of such a neighbor in NeighborActivity table. Those entries will expire and will be deleted.
                                }
                                break;
                            }

                            default:
                                log.Error("Invalid neighborhood change type '{0}'.", change.ChangeTypeCase);
                                break;
                            }
                        }

                        if (saveDb)
                        {
                            await unitOfWork.SaveThrowAsync();

                            transaction.Commit();
                        }
                        success = true;
                        res     = Client.MessageBuilder.CreateNeighbourhoodChangedNotificationResponse(RequestMessage);
                    }
                    catch (Exception e)
                    {
                        log.Error("Exception occurred: {0}", e.ToString());
                    }

                    if (!success)
                    {
                        log.Warn("Rolling back transaction.");
                        unitOfWork.SafeTransactionRollback(transaction);
                    }

                    unitOfWork.ReleaseLock(lockObjects);
                }
            }

            if (signalActionProcessor)
            {
                NeighborhoodActionProcessor neighborhoodActionProcessor = (NeighborhoodActionProcessor)Base.ComponentDictionary[NeighborhoodActionProcessor.ComponentName];
                neighborhoodActionProcessor.Signal();
            }

            log.Trace("(-):*.Response.Status={0}", res.Response.Status);
            return(res);
        }
        /// <summary>
        /// Processes update received from LOC server that informs the proximity server about a new neighbor server or a change in existing neighbor server contact information.
        /// </summary>
        /// <param name="UnitOfWork">Unit of work instance.</param>
        /// <param name="ServerId">Network identifier of the neighbor server.</param>
        /// <param name="IpAddress">IP address of the neighbor server.</param>
        /// <param name="Port">Primary interface port of the neighbor server.</param>
        /// <param name="Latitude">GPS location latitude of the neighbor server.</param>
        /// <param name="Longitude">GPS location longitude of the neighbor server.</param>
        /// <param name="NeighborhoodSize">Size of the proximity server's neighborhood at the moment the function is called.</param>
        /// <returns>Information related to how should the caller proceed further, described in AddOrChangeNeighborResult structure.</returns>
        /// <remarks>The caller is responsible for calling this function within a database transaction with NeighborLock and NeighborhoodActionLock locks.</remarks>
        public async Task <AddOrChangeNeighborResult> AddOrChangeNeighborAsync(UnitOfWork UnitOfWork, byte[] ServerId, IPAddress IpAddress, int Port, int Latitude, int Longitude, int NeighborhoodSize)
        {
            log.Trace("(ServerId:'{0}',IpAddress:{1},Port:{2},Latitude:{3},Longitude:{4},NeighborhoodSize:{5})", ServerId.ToHex(), IpAddress, Port, Latitude, Longitude, NeighborhoodSize);

            AddOrChangeNeighborResult res = new AddOrChangeNeighborResult();

            res.NeighborhoodSize = NeighborhoodSize;

            // Data validation.
            bool serverIdValid = ServerId.Length == ProtocolHelper.NetworkIdentifierLength;

            if (!serverIdValid)
            {
                log.Error("Received invalid neighbor server ID '{0}' from LOC server.", ServerId.ToHex());
                res.Error = true;
                log.Trace("(-):*.Error={0},*.SaveDb={1},*.SignalActionProcessor={2},*.NeighborhoodSize={3}", res.Error, res.SaveDb, res.SignalActionProcessor, res.NeighborhoodSize);
                return(res);
            }

            bool portValid = (0 < Port) && (Port <= 65535);

            if (!portValid)
            {
                log.Error("Received invalid neighbor server port '{0}' from LOC server.", Port);
                res.Error = true;
                log.Trace("(-):*.Error={0},*.SaveDb={1},*.SignalActionProcessor={2},*.NeighborhoodSize={3}", res.Error, res.SaveDb, res.SignalActionProcessor, res.NeighborhoodSize);
                return(res);
            }

            IopProtocol.GpsLocation location = new IopProtocol.GpsLocation(Latitude, Longitude);
            if (!location.IsValid())
            {
                log.Error("Received invalid neighbor server location '{0}' from LOC server.", location);
                res.Error = true;
                log.Trace("(-):*.Error={0},*.SaveDb={1},*.SignalActionProcessor={2},*.NeighborhoodSize={3}", res.Error, res.SaveDb, res.SignalActionProcessor, res.NeighborhoodSize);
                return(res);
            }

            // Data processing.
            Neighbor existingNeighbor = (await UnitOfWork.NeighborRepository.GetAsync(n => n.NetworkId == ServerId)).FirstOrDefault();

            if (existingNeighbor == null)
            {
                // New neighbor server.
                if (NeighborhoodSize < Config.Configuration.MaxNeighborhoodSize)
                {
                    // We have not reached the maximal size of the neighborhood yet, the server can be added.
                    log.Trace("New neighbor ID '{0}' detected, IP address {1}, port {2}, latitude {3}, longitude {4}.", ServerId.ToHex(), IpAddress, Port, Latitude, Longitude);

                    // Add neighbor to the database of neighbors.
                    // The neighbor is not initialized, so we will not allow it to send us
                    // any updates. First, we need to contact it and start the neighborhood initialization process.
                    Neighbor neighbor = new Neighbor()
                    {
                        NetworkId         = ServerId,
                        IpAddress         = IpAddress.GetAddressBytes(),
                        PrimaryPort       = Port,
                        NeighborPort      = null,
                        LocationLatitude  = location.Latitude,
                        LocationLongitude = location.Longitude,
                        LastRefreshTime   = DateTime.UtcNow,
                        Initialized       = false,
                        SharedActivities  = 0
                    };
                    await UnitOfWork.NeighborRepository.InsertAsync(neighbor);

                    res.NeighborhoodSize++;

                    // This action will cause our proximity server to contact the new neighbor server and ask it to share its activity database,
                    // i.e. the neighborhood initialization process will be started.
                    // We set a delay depending on the number of neighbors, so that a new server joining a neighborhood is not overwhelmed with requests.
                    int delay = RandomSource.Generator.Next(0, 3 * res.NeighborhoodSize);

                    NeighborhoodAction action = new NeighborhoodAction()
                    {
                        ServerId              = ServerId,
                        Timestamp             = DateTime.UtcNow,
                        Type                  = NeighborhoodActionType.AddNeighbor,
                        ExecuteAfter          = DateTime.UtcNow.AddSeconds(delay),
                        TargetActivityId      = 0,
                        TargetActivityOwnerId = null,
                        AdditionalData        = null,
                    };
                    await UnitOfWork.NeighborhoodActionRepository.InsertAsync(action);

                    res.SignalActionProcessor = true;
                    res.SaveDb = true;
                }
                else
                {
                    log.Error("Unable to add new neighbor ID '{0}', the proximity server reached its neighborhood size limit {1}.", ServerId.ToHex(), Config.Configuration.MaxNeighborhoodSize);
                }
            }
            else
            {
                // This is a neighbor we already know about. Just check that its information is up to date and if not, update it.
                IPAddress existingNeighborIpAddress = new IPAddress(existingNeighbor.IpAddress);
                if (!existingNeighborIpAddress.Equals(IpAddress))
                {
                    log.Trace("Existing neighbor ID '{0}' changed its IP address from {1} to {2}.", ServerId.ToHex(), existingNeighborIpAddress, IpAddress);
                    existingNeighbor.IpAddress = IpAddress.GetAddressBytes();
                }

                if (existingNeighbor.PrimaryPort != Port)
                {
                    // Primary port was change, so we also expect that the neighbors interface port was changed as well.
                    log.Trace("Existing neighbor ID '{0}' changed its primary port from {1} to {2}, invalidating neighbors interface port as well.", ServerId.ToHex(), existingNeighbor.PrimaryPort, Port);
                    existingNeighbor.PrimaryPort  = Port;
                    existingNeighbor.NeighborPort = null;
                }

                if (existingNeighbor.LocationLatitude != location.Latitude)
                {
                    log.Trace("Existing neighbor ID '{0}' changed its latitude from {1} to {2}.", ServerId.ToHex(), existingNeighbor.LocationLatitude, location.Latitude);
                    existingNeighbor.LocationLatitude = Latitude;
                }

                if (existingNeighbor.LocationLongitude != location.Longitude)
                {
                    log.Trace("Existing neighbor ID '{0}' changed its longitude from {1} to {2}.", ServerId.ToHex(), existingNeighbor.LocationLongitude, location.Longitude);
                    existingNeighbor.LocationLongitude = Longitude;
                }

                // We consider a fresh LOC info to be accurate, so we do not want to delete the neighbors received here
                // and hence we update their refresh time.
                existingNeighbor.LastRefreshTime = DateTime.UtcNow;

                UnitOfWork.NeighborRepository.Update(existingNeighbor);
                res.SaveDb = true;
            }

            log.Trace("(-):*.Error={0},*.SaveDb={1},*.SignalActionProcessor={2},*.NeighborhoodSize={3}", res.Error, res.SaveDb, res.SignalActionProcessor, res.NeighborhoodSize);
            return(res);
        }
        /// <summary>
        /// Checks if any of the neighbors expired.
        /// If so, it starts the process of their removal.
        /// </summary>
        public async Task CheckExpiredNeighborsAsync()
        {
            log.Trace("()");

            // If a neighbor server's LastRefreshTime is lower than this limit, it is expired.
            DateTime limitLastRefreshTime = DateTime.UtcNow.AddSeconds(-Config.Configuration.NeighborExpirationTimeSeconds);

            List <byte[]> neighborsToDeleteIds = new List <byte[]>();

            using (UnitOfWork unitOfWork = new UnitOfWork())
            {
                bool           success     = false;
                DatabaseLock[] lockObjects = new DatabaseLock[] { UnitOfWork.NeighborLock, UnitOfWork.NeighborhoodActionLock };
                using (IDbContextTransaction transaction = await unitOfWork.BeginTransactionWithLockAsync(lockObjects))
                {
                    try
                    {
                        List <Neighbor> expiredNeighbors = (await unitOfWork.NeighborRepository.GetAsync(n => n.LastRefreshTime < limitLastRefreshTime, null, true)).ToList();
                        if (expiredNeighbors.Count > 0)
                        {
                            bool saveDb = false;

                            log.Debug("There are {0} expired neighbors.", expiredNeighbors.Count);
                            foreach (Neighbor neighbor in expiredNeighbors)
                            {
                                int unprocessedRemoveNeighborActions = await unitOfWork.NeighborhoodActionRepository.CountAsync(a => (a.ServerId == neighbor.NetworkId) && (a.Type == NeighborhoodActionType.RemoveNeighbor));

                                if (unprocessedRemoveNeighborActions == 0)
                                {
                                    // This action will cause our proximity server to erase all profiles of the neighbor that has been removed.
                                    NeighborhoodAction action = new NeighborhoodAction()
                                    {
                                        ServerId              = neighbor.NetworkId,
                                        Timestamp             = DateTime.UtcNow,
                                        Type                  = NeighborhoodActionType.RemoveNeighbor,
                                        TargetActivityId      = 0,
                                        TargetActivityOwnerId = null,
                                        AdditionalData        = null
                                    };
                                    await unitOfWork.NeighborhoodActionRepository.InsertAsync(action);

                                    saveDb = true;
                                }
                                else
                                {
                                    log.Debug("There is an unprocessed RemoveNeighbor neighborhood action for neighbor ID '{0}'. Neighbor will be deleted.", neighbor.NetworkId.ToHex());
                                    neighborsToDeleteIds.Add(neighbor.NetworkId);
                                }
                            }

                            if (saveDb)
                            {
                                await unitOfWork.SaveThrowAsync();

                                transaction.Commit();
                            }
                        }
                        else
                        {
                            log.Debug("No expired neighbors found.");
                        }

                        success = true;
                    }
                    catch (Exception e)
                    {
                        log.Error("Exception occurred: {0}", e.ToString());
                    }

                    if (!success)
                    {
                        log.Warn("Rolling back transaction.");
                        unitOfWork.SafeTransactionRollback(transaction);
                    }

                    unitOfWork.ReleaseLock(lockObjects);
                }


                if (neighborsToDeleteIds.Count > 0)
                {
                    log.Debug("There are {0} neighbors to be deleted.", neighborsToDeleteIds.Count);
                    foreach (byte[] neighborToDeleteId in neighborsToDeleteIds)
                    {
                        if (await unitOfWork.NeighborRepository.DeleteNeighborAsync(neighborToDeleteId))
                        {
                            log.Debug("Neighbor ID '{0}' deleted.", neighborToDeleteId.ToHex());
                        }
                        else
                        {
                            log.Warn("Unable to delete neighbor ID '{0}'.", neighborToDeleteId.ToHex());
                        }
                    }
                }
                else
                {
                    log.Debug("No neighbors to delete now.");
                }
            }

            log.Trace("(-)");
        }
        /// <summary>
        /// Checks if any of the follower servers need refresh. If so, a neighborhood action is created.
        /// <para>This function also checks if there are unprocessed refresh neighborhood actions
        /// and if there are 3 such requests already, the follower is deleted as it is considered as unresponsive for too long.</para>
        /// </summary>
        public async Task CheckFollowersRefreshAsync()
        {
            log.Trace("()");

            // If a follower server's LastRefreshTime is lower than this limit, it should be refreshed.
            DateTime limitLastRefreshTime = DateTime.UtcNow.AddSeconds(-Config.Configuration.FollowerRefreshTimeSeconds);

            List <byte[]> followersToDeleteIds = new List <byte[]>();

            using (UnitOfWork unitOfWork = new UnitOfWork())
            {
                DatabaseLock[] lockObjects = new DatabaseLock[] { UnitOfWork.FollowerLock, UnitOfWork.NeighborhoodActionLock };
                await unitOfWork.AcquireLockAsync(lockObjects);

                try
                {
                    List <Follower> followersToRefresh = (await unitOfWork.FollowerRepository.GetAsync(f => f.LastRefreshTime < limitLastRefreshTime, null, true)).ToList();
                    if (followersToRefresh.Count > 0)
                    {
                        bool saveDb          = false;
                        int  actionsInserted = 0;

                        log.Debug("There are {0} followers that need refresh.", followersToRefresh.Count);
                        foreach (Follower follower in followersToRefresh)
                        {
                            int unprocessedRefreshProfileActions = await unitOfWork.NeighborhoodActionRepository.CountAsync(a => (a.ServerId == follower.NetworkId) && (a.Type == NeighborhoodActionType.RefreshNeighborStatus));

                            if (unprocessedRefreshProfileActions < 3)
                            {
                                NeighborhoodAction action = new NeighborhoodAction()
                                {
                                    ServerId              = follower.NetworkId,
                                    Type                  = NeighborhoodActionType.RefreshNeighborStatus,
                                    Timestamp             = DateTime.UtcNow,
                                    ExecuteAfter          = DateTime.UtcNow,
                                    TargetActivityId      = 0,
                                    TargetActivityOwnerId = null,
                                    AdditionalData        = null
                                };

                                await unitOfWork.NeighborhoodActionRepository.InsertAsync(action);

                                log.Debug("Refresh neighborhood action for follower ID '{0}' will be inserted to the database.", follower.NetworkId.ToHex());
                                saveDb = true;
                                actionsInserted++;
                            }
                            else
                            {
                                log.Debug("There are {0} unprocessed RefreshNeighborStatus neighborhood actions for follower ID '{1}'. Follower will be deleted.", unprocessedRefreshProfileActions, follower.NetworkId.ToHex());
                                followersToDeleteIds.Add(follower.NetworkId);
                            }
                        }

                        if (saveDb)
                        {
                            await unitOfWork.SaveThrowAsync();

                            log.Debug("{0} new neighborhood actions saved to the database.", actionsInserted);
                        }
                    }
                    else
                    {
                        log.Debug("No followers need refresh now.");
                    }
                }
                catch (Exception e)
                {
                    log.Error("Exception occurred: {0}", e.ToString());
                }

                unitOfWork.ReleaseLock(lockObjects);


                if (followersToDeleteIds.Count > 0)
                {
                    log.Debug("There are {0} followers to be deleted.", followersToDeleteIds.Count);
                    foreach (byte[] followerToDeleteId in followersToDeleteIds)
                    {
                        Iop.Shared.Status status = unitOfWork.FollowerRepository.DeleteFollowerAsync(followerToDeleteId).Result;
                        if (status == Iop.Shared.Status.Ok)
                        {
                            log.Debug("Follower ID '{0}' deleted.", followerToDeleteId.ToHex());
                        }
                        else
                        {
                            log.Warn("Unable to delete follower ID '{0}', error code {1}.", followerToDeleteId.ToHex(), status);
                        }
                    }
                }
                else
                {
                    log.Debug("No followers to delete now.");
                }
            }

            log.Trace("(-)");
        }