Example #1
0
        private static JobQueueDto CreateJobQueueDto(HangfireDbContext connection, string queue, bool isFetched)
        {
            var state = new StateDto();

            AsyncHelper.RunSync(() => connection.State.InsertOneAsync(state));

            var job = new JobDto
            {
                CreatedAt = connection.GetServerTimeUtc(),
                StateId   = state.Id
            };

            AsyncHelper.RunSync(() => connection.Job.InsertOneAsync(job));

            var jobQueue = new JobQueueDto
            {
                Queue = queue,
                JobId = job.Id
            };

            if (isFetched)
            {
                jobQueue.FetchedAt = connection.GetServerTimeUtc().AddDays(-1);
            }

            AsyncHelper.RunSync(() => connection.JobQueue.InsertOneAsync(jobQueue));

            return(jobQueue);
        }
Example #2
0
        public IFetchedJob Dequeue(string[] queues, CancellationToken cancellationToken)
        {
            if (queues == null)
            {
                throw new ArgumentNullException(nameof(queues));
            }

            if (queues.Length == 0)
            {
                throw new ArgumentException("Queue array must be non-empty.", nameof(queues));
            }

            JobQueueDto fetchedJob = null;

            var fetchConditions = new[]
            {
                Builders <JobQueueDto> .Filter.Eq(_ => _.FetchedAt, null),
                Builders <JobQueueDto> .Filter.Lt(_ => _.FetchedAt, _connection.GetServerTimeUtc().AddSeconds(_options.InvisibilityTimeout.Negate().TotalSeconds))
            };
            var currentQueryIndex = 0;

            do
            {
                cancellationToken.ThrowIfCancellationRequested();

                FilterDefinition <JobQueueDto> fetchCondition = fetchConditions[currentQueryIndex];

                foreach (var queue in queues)
                {
                    fetchedJob = _connection.JobQueue.FindOneAndUpdate(
                        fetchCondition & Builders <JobQueueDto> .Filter.Eq(_ => _.Queue, queue),
                        Builders <JobQueueDto> .Update.Set(_ => _.FetchedAt, _connection.GetServerTimeUtc()),
                        new FindOneAndUpdateOptions <JobQueueDto>
                    {
                        IsUpsert       = false,
                        ReturnDocument = ReturnDocument.After
                    }, cancellationToken);
                    if (fetchedJob != null)
                    {
                        break;
                    }
                }

                if (fetchedJob == null)
                {
                    if (currentQueryIndex == fetchConditions.Length - 1)
                    {
                        cancellationToken.WaitHandle.WaitOne(_options.QueuePollInterval);
                        cancellationToken.ThrowIfCancellationRequested();
                    }
                }

                currentQueryIndex = (currentQueryIndex + 1) % fetchConditions.Length;
            }while (fetchedJob == null);

            return(new MongoFetchedJob(_connection, fetchedJob.Id, fetchedJob.JobId.ToString(CultureInfo.InvariantCulture), fetchedJob.Queue));
        }
Example #3
0
        public IFetchedJob Dequeue(string[] queues, CancellationToken cancellationToken)
        {
            if (queues == null)
            {
                throw new ArgumentNullException("queues");
            }

            if (queues.Length == 0)
            {
                throw new ArgumentException("Queue array must be non-empty.", "queues");
            }

            JobQueueDto fetchedJob;

            var fetchConditions = new[]
            {
                Query <JobQueueDto> .EQ(_ => _.FetchedAt, null),
                Query <JobQueueDto> .LT(_ => _.FetchedAt, _connection.GetServerTimeUtc().AddSeconds(_options.InvisibilityTimeout.Negate().TotalSeconds))
            };
            var currentQueryIndex = 0;

            do
            {
                cancellationToken.ThrowIfCancellationRequested();

                fetchedJob = _connection.JobQueue
                             .FindAndModify(new FindAndModifyArgs
                {
                    Query           = Query.And(fetchConditions[currentQueryIndex], Query <JobQueueDto> .In(_ => _.Queue, queues)),
                    Update          = Update <JobQueueDto> .Set(_ => _.FetchedAt, _connection.GetServerTimeUtc()),
                    VersionReturned = FindAndModifyDocumentVersion.Modified,
                    Upsert          = false
                })
                             .GetModifiedDocumentAs <JobQueueDto>();

                if (fetchedJob == null)
                {
                    if (currentQueryIndex == fetchConditions.Length - 1)
                    {
                        cancellationToken.WaitHandle.WaitOne(_options.QueuePollInterval);
                        cancellationToken.ThrowIfCancellationRequested();
                    }
                }

                currentQueryIndex = (currentQueryIndex + 1) % fetchConditions.Length;
            }while (fetchedJob == null);

            return(new MongoFetchedJob(_connection, fetchedJob.Id, fetchedJob.JobId.ToString(CultureInfo.InvariantCulture), fetchedJob.Queue));
        }
Example #4
0
        public override void AnnounceServer(string serverId, ServerContext context)
        {
            if (serverId == null)
            {
                throw new ArgumentNullException("serverId");
            }

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

            var data = new ServerDataDto
            {
                WorkerCount = context.WorkerCount,
                Queues      = context.Queues,
                StartedAt   = _database.GetServerTimeUtc(),
            };

            _database.Server.UpdateManyAsync(Builders <ServerDto> .Filter.Eq(_ => _.Id, serverId),
                                             Builders <ServerDto> .Update.Combine(Builders <ServerDto> .Update.Set(_ => _.Data, JobHelper.ToJson(data)), Builders <ServerDto> .Update.Set(_ => _.LastHeartbeat, _database.GetServerTimeUtc())),
                                             new UpdateOptions {
                IsUpsert = true
            });
        }
Example #5
0
        private Dictionary <DateTime, long> GetHourlyTimelineStats(HangfireDbContext connection, string type)
        {
            var endDate = connection.GetServerTimeUtc();
            var dates   = new List <DateTime>();

            for (var i = 0; i < 24; i++)
            {
                dates.Add(endDate);
                endDate = endDate.AddHours(-1);
            }

            var keys = dates.Select(x => $"stats:{type}:{x:yyyy-MM-dd-HH}").ToList();

            var valuesMap = connection.Counter.Find(Builders <CounterDto> .Filter.In(_ => _.Key, keys))
                            .ToList()
                            .GroupBy(x => x.Key, x => x)
                            .ToDictionary(x => x.Key, x => (long)x.Count());

            foreach (var key in keys.Where(key => !valuesMap.ContainsKey(key)))
            {
                valuesMap.Add(key, 0);
            }

            var result = new Dictionary <DateTime, long>();

            for (var i = 0; i < dates.Count; i++)
            {
                var value = valuesMap[valuesMap.Keys.ElementAt(i)];
                result.Add(dates[i], value);
            }

            return(result);
        }
Example #6
0
        private JobDto CreateJobInState(HangfireDbContext database, int jobId, string stateName)
        {
            var job = Job.FromExpression(() => SampleMethod("wrong"));

            var jobState = new StateDto
            {
                CreatedAt = database.GetServerTimeUtc(),
                Data      =
                    stateName == EnqueuedState.StateName
                                        ? new Dictionary <string, string> {
                    ["EnqueuedAt"] = $"{database.GetServerTimeUtc():o}"
                }
                                        : new Dictionary <string, string>(),
                JobId = jobId
            };

            database.State.InsertOne(jobState);

            var jobDto = new JobDto
            {
                Id             = jobId,
                InvocationData = JobHelper.ToJson(InvocationData.Serialize(job)),
                Arguments      = "['Arguments']",
                StateName      = stateName,
                CreatedAt      = database.GetServerTimeUtc(),
                StateId        = jobState.Id
            };

            database.Job.InsertOne(jobDto);

            var jobQueueDto = new JobQueueDto
            {
                FetchedAt = null,
                Id        = jobId * 10,
                JobId     = jobId,
                Queue     = DefaultQueue
            };

            if (stateName == FetchedStateName)
            {
                jobQueueDto.FetchedAt = database.GetServerTimeUtc();
            }

            database.JobQueue.InsertOne(jobQueueDto);

            return(jobDto);
        }
        /// <summary>
        /// Starts database heartbeat
        /// </summary>
        private void StartHeartBeat()
        {
            TimeSpan timerInterval = TimeSpan.FromMilliseconds(_options.DistributedLockLifetime.TotalMilliseconds / 5);

            _heartbeatTimer = new Timer(state =>
            {
                try
                {
                    _database.DistributedLock.FindOneAndUpdate(
                        Builders <DistributedLockDto> .Filter.Eq(_ => _.Resource, _resource) & Builders <DistributedLockDto> .Filter.Eq(_ => _.ClientId, _options.ClientId),
                        Builders <DistributedLockDto> .Update.Set(_ => _.Heartbeat, _database.GetServerTimeUtc()));
                }
                catch (Exception ex)
                {
                    Logger.ErrorFormat("Unable to update heartbeat on the resource '{0}'", ex, _resource);
                }
            }, null, timerInterval, timerInterval);
        }
        private JobDto CreateJobInState(HangfireDbContext database, int jobId, string stateName)
        {
            var job = Job.FromExpression(() => SampleMethod("wrong"));

            var jobState = new StateDto
            {
                CreatedAt = database.GetServerTimeUtc(),
                Data      = stateName == EnqueuedState.StateName
                           ? string.Format(" {{ 'EnqueuedAt': '{0}' }}", database.GetServerTimeUtc())
                           : "{}",
                JobId = jobId
            };

            AsyncHelper.RunSync(() => database.State.InsertOneAsync(jobState));

            var jobDto = new JobDto
            {
                Id             = jobId,
                InvocationData = JobHelper.ToJson(InvocationData.Serialize(job)),
                Arguments      = "['Arguments']",
                StateName      = stateName,
                CreatedAt      = database.GetServerTimeUtc(),
                StateId        = jobState.Id
            };

            AsyncHelper.RunSync(() => database.Job.InsertOneAsync(jobDto));

            var jobQueueDto = new JobQueueDto
            {
                FetchedAt = null,
                Id        = jobId * 10,
                JobId     = jobId,
                Queue     = DefaultQueue
            };

            if (stateName == FetchedStateName)
            {
                jobQueueDto.FetchedAt = database.GetServerTimeUtc();
            }

            AsyncHelper.RunSync(() => database.JobQueue.InsertOneAsync(jobQueueDto));

            return(jobDto);
        }
Example #9
0
        private void Acquire(TimeSpan timeout)
        {
            try
            {
                // If result is null, then it means we acquired the lock
                var isLockAcquired  = false;
                var now             = DateTime.Now;
                var lockTimeoutTime = now.Add(timeout);

                while (!isLockAcquired && (lockTimeoutTime >= now))
                {
                    // Acquire the lock if it does not exist - Notice: ReturnDocument.Before
                    var filter = Builders <DistributedLockDto> .Filter.Eq(_ => _.Resource, _resource);

                    var update = Builders <DistributedLockDto> .Update.SetOnInsert(_ => _.ExpireAt, _database.GetServerTimeUtc().Add(_options.DistributedLockLifetime));

                    var options = new FindOneAndUpdateOptions <DistributedLockDto>
                    {
                        IsUpsert       = true,
                        ReturnDocument = ReturnDocument.Before
                    };
                    var result = _database.DistributedLock.FindOneAndUpdate(filter, update, options);

                    // If result is null, then it means we acquired the lock
                    if (result == null)
                    {
                        isLockAcquired = true;
                    }
                    else
                    {
                        // Wait on the event. This allows us to be "woken" up sooner rather than later.
                        // We wait in chunks as we need to "wake-up" from time to time and poll mongo,
                        // in case that the lock was acquired on another machine or instance.
                        var eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, EventWaitHandleName);
                        eventWaitHandle.WaitOne((int)timeout.TotalMilliseconds / 10);
                        now = DateTime.Now;
                    }
                }

                if (!isLockAcquired)
                {
                    throw new DistributedLockTimeoutException($"Could not place a lock on the resource \'{_resource}\': The lock request timed out.");
                }
            }
            catch (DistributedLockTimeoutException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new MongoDistributedLockException($"Could not place a lock on the resource \'{_resource}\': Check inner exception for details.", ex);
            }
        }
        private static int CreateJobQueueRecord(HangfireDbContext connection, string jobId, string queue)
        {
            var jobQueue = new JobQueueDto
            {
                JobId     = int.Parse(jobId),
                Queue     = queue,
                FetchedAt = connection.GetServerTimeUtc()
            };

            connection.JobQueue.InsertOne(jobQueue);

            return(jobQueue.Id);
        }
Example #11
0
        public void AnnounceServer(string serverId, ServerContext context)
        {
            if (serverId == null)
            {
                throw new ArgumentNullException("serverId");
            }

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

            var data = new ServerDataDto
            {
                WorkerCount = context.WorkerCount,
                Queues      = context.Queues,
                StartedAt   = _database.GetServerTimeUtc(),
            };

            _database.Server.Update(Query <ServerDto> .EQ(_ => _.Id, serverId),
                                    Update.Combine(Update <ServerDto> .Set(_ => _.Data, JobHelper.ToJson(data)), Update <ServerDto> .Set(_ => _.LastHeartbeat, _database.GetServerTimeUtc())),
                                    UpdateFlags.Upsert);
        }
        /// <summary>
        /// Run expiration manager to remove outdated records
        /// </summary>
        /// <param name="cancellationToken">Cancellation token</param>
        public void Execute(CancellationToken cancellationToken)
        {
            using (HangfireDbContext connection = _storage.CreateAndOpenConnection())
            {
                DateTime now = connection.GetServerTimeUtc();

                RemoveExpiredRecord(connection.AggregatedCounter, _ => _.ExpireAt, now);
                RemoveExpiredRecord(connection.Counter, _ => _.ExpireAt, now);
                RemoveExpiredRecord(connection.Job, _ => _.ExpireAt, now);
                RemoveExpiredRecord(connection.List, _ => _.ExpireAt, now);
                RemoveExpiredRecord(connection.Set, _ => _.ExpireAt, now);
                RemoveExpiredRecord(connection.Hash, _ => _.ExpireAt, now);
            }

            cancellationToken.WaitHandle.WaitOne(_checkInterval);
        }
        public void Dispose()
        {
            if (_completed)
            {
                return;
            }

            try
            {
                // Remove dead locks
                _database.DistributedLock.Remove(Query.And(Query <DistributedLockDto> .EQ(_ => _.Resource, _resource),
                                                           Query <DistributedLockDto> .LT(_ => _.Heartbeat, _database.GetServerTimeUtc().Subtract(_options.DistributedLockLifetime))));

                // Remove resource lock
                _database.DistributedLock.FindAndModify(new FindAndModifyArgs
                {
                    Query = Query.And(Query <DistributedLockDto> .EQ(_ => _.Resource, _resource),
                                      Query <DistributedLockDto> .EQ(_ => _.ClientId, _options.ClientId)),
                    Update = Update.Combine(
                        Update <DistributedLockDto> .Inc(_ => _.LockCount, -1),
                        Update <DistributedLockDto> .Set(_ => _.Heartbeat, _database.GetServerTimeUtc())
                        ),
                    Upsert = false
                });

                _database.DistributedLock.Remove(Query <DistributedLockDto> .LTE(_ => _.LockCount, 0));

                if (_heartbeatTimer != null)
                {
                    _heartbeatTimer.Dispose();
                    _heartbeatTimer = null;
                }

                _completed = true;
            }
            catch (Exception ex)
            {
                throw new MongoDistributedLockException(String.Format("Could not release a lock on the resource '{0}': {1}.", _resource, "Check inner exception for details"), ex);
            }
        }
Example #14
0
        private Dictionary <DateTime, long> GetTimelineStats(HangfireDbContext connection, string type)
        {
            var endDate   = connection.GetServerTimeUtc().Date;
            var startDate = endDate.AddDays(-7);
            var dates     = new List <DateTime>();

            while (startDate <= endDate)
            {
                dates.Add(endDate);
                endDate = endDate.AddDays(-1);
            }

            var stringDates = dates.Select(x => x.ToString("yyyy-MM-dd")).ToList();
            var keys        = stringDates.Select(x => $"stats:{type}:{x}").ToList();

            var valuesMap = connection.AggregatedCounter
                            .Find(Builders <AggregatedCounterDto> .Filter.In(_ => _.Key, keys))
                            .ToList()
                            .GroupBy(x => x.Key)
                            .ToDictionary(x => x.Key, x => (long)x.Count());

            foreach (var key in keys)
            {
                if (!valuesMap.ContainsKey(key))
                {
                    valuesMap.Add(key, 0);
                }
            }

            var result = new Dictionary <DateTime, long>();

            for (var i = 0; i < stringDates.Count; i++)
            {
                var value = valuesMap[valuesMap.Keys.ElementAt(i)];
                result.Add(dates[i], value);
            }

            return(result);
        }
Example #15
0
        public void Execute(CancellationToken cancellationToken)
        {
            using (HangfireDbContext connection = _storage.CreateAndOpenConnection())
            {
                MongoCollection[] processedTables =
                {
                    connection.Counter,
                    connection.Job,
                    connection.List,
                    connection.Set,
                    connection.Hash
                };

                DateTime now = connection.GetServerTimeUtc();
                foreach (var table in processedTables)
                {
                    Logger.DebugFormat("Removing outdated records from table '{0}'...", table.Name);

                    table.Remove(Query.LT("ExpireAt", now));
                }
            }

            cancellationToken.WaitHandle.WaitOne(_checkInterval);
        }
        /// <summary>
        /// Creates MongoDB distributed lock
        /// </summary>
        /// <param name="resource">Lock resource</param>
        /// <param name="timeout">Lock timeout</param>
        /// <param name="database">Lock database</param>
        /// <param name="options">Database options</param>
        /// <exception cref="MongoDistributedLockException"></exception>
        public MongoDistributedLock(string resource, TimeSpan timeout, HangfireDbContext database, MongoStorageOptions options)
        {
            if (String.IsNullOrEmpty(resource))
            {
                throw new ArgumentNullException("resource");
            }

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

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

            _resource = resource;
            _database = database;
            _options  = options;

            try
            {
                // Remove dead locks
                database.DistributedLock.DeleteMany(
                    Builders <DistributedLockDto> .Filter.Eq(_ => _.Resource, resource) &
                    Builders <DistributedLockDto> .Filter.Lt(_ => _.Heartbeat, database.GetServerTimeUtc().Subtract(options.DistributedLockLifetime)));

                // Check lock
                DateTime lockTimeoutTime = DateTime.Now.Add(timeout);
                bool     isLockedBySomeoneElse;
                bool     isFirstAttempt = true;
                do
                {
                    isLockedBySomeoneElse = database.DistributedLock
                                            .Find(Builders <DistributedLockDto> .Filter.Eq(_ => _.Resource, resource) &
                                                  Builders <DistributedLockDto> .Filter.Ne(_ => _.ClientId, _options.ClientId))
                                            .FirstOrDefault() != null;

                    if (isFirstAttempt)
                    {
                        isFirstAttempt = false;
                    }
                    else
                    {
                        Thread.Sleep((int)timeout.TotalMilliseconds / 10);
                    }
                }while (isLockedBySomeoneElse && (lockTimeoutTime >= DateTime.Now));

                // Set lock
                if (isLockedBySomeoneElse == false)
                {
                    database.DistributedLock.FindOneAndUpdate(
                        Builders <DistributedLockDto> .Filter.Eq(_ => _.Resource, resource),
                        Builders <DistributedLockDto> .Update.Combine(
                            Builders <DistributedLockDto> .Update.Set(_ => _.ClientId, _options.ClientId),
                            Builders <DistributedLockDto> .Update.Inc(_ => _.LockCount, 1),
                            Builders <DistributedLockDto> .Update.Set(_ => _.Heartbeat, database.GetServerTimeUtc())
                            ),
                        new FindOneAndUpdateOptions <DistributedLockDto> {
                        IsUpsert = true
                    });

                    StartHeartBeat();
                }
                else
                {
                    throw new MongoDistributedLockException(
                              String.Format("Could not place a lock on the resource '{0}': {1}.", _resource,
                                            "The lock request timed out"));
                }
            }
            catch (Exception ex)
            {
                if (ex is MongoDistributedLockException)
                {
                    throw;
                }
                else
                {
                    throw new MongoDistributedLockException(
                              String.Format("Could not place a lock on the resource '{0}': {1}.", _resource,
                                            "Check inner exception for details"), ex);
                }
            }
        }
Example #17
0
 public override void ExpireJob(string jobId, TimeSpan expireIn)
 {
     QueueCommand(x => x.Job.UpdateMany(Builders <JobDto> .Filter.Eq(_ => _.Id, int.Parse(jobId)),
                                        Builders <JobDto> .Update.Set(_ => _.ExpireAt, _connection.GetServerTimeUtc().Add(expireIn))));
 }
        private void Acquire(TimeSpan timeout)
        {
            try
            {
                // If result is null, then it means we acquired the lock
                var isLockAcquired  = false;
                var now             = DateTime.Now;
                var lockTimeoutTime = now.Add(timeout);

                while (!isLockAcquired && (lockTimeoutTime >= now))
                {
                    // Acquire the lock if it does not exist - Notice: ReturnDocument.Before
                    var filter = Builders <DistributedLockDto> .Filter.Eq(_ => _.Resource, _resource);

                    var update = Builders <DistributedLockDto> .Update.SetOnInsert(_ => _.ExpireAt, _database.GetServerTimeUtc().Add(_options.DistributedLockLifetime));

                    var options = new FindOneAndUpdateOptions <DistributedLockDto>
                    {
                        IsUpsert       = true,
                        ReturnDocument = ReturnDocument.Before
                    };
                    var result = _database.DistributedLock.FindOneAndUpdate(filter, update, options);

                    // If result is null, then it means we acquired the lock
                    if (result == null)
                    {
                        isLockAcquired = true;
                    }
                    else
                    {
                        try
                        {
                            // Wait on the event. This allows us to be "woken" up sooner rather than later.
                            // We wait in chunks as we need to "wake-up" from time to time and poll mongo,
                            // in case that the lock was acquired on another machine or instance.
                            var eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, EventWaitHandleName);
                            eventWaitHandle.WaitOne((int)timeout.TotalMilliseconds / 10);
                        }
                        catch (PlatformNotSupportedException)
                        {
                            // EventWaitHandle is not supported on UNIX systems
                            // https://github.com/dotnet/coreclr/pull/1387
                            // Instead of using a compiler directive, we catch the
                            // exception and handles it. This way, when EventWaitHandle
                            // becomes available on UNIX, we will start working.
                            // So in this case, we just sleep for a while and then
                            // check if the lock has been released.
                            Thread.Sleep((int)timeout.TotalMilliseconds / 10);
                        }
                        now = DateTime.Now;
                    }
                }

                if (!isLockAcquired)
                {
                    throw new DistributedLockTimeoutException($"Could not place a lock on the resource \'{_resource}\': The lock request timed out.");
                }
            }
            catch (DistributedLockTimeoutException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new MongoDistributedLockException($"Could not place a lock on the resource \'{_resource}\': Check inner exception for details.", ex);
            }
        }
Example #19
0
 public void ExpireJob(string jobId, TimeSpan expireIn)
 {
     QueueCommand(x => x.Job.Update(Query <JobDto> .EQ(_ => _.Id, int.Parse(jobId)),
                                    Update <JobDto> .Set(_ => _.ExpireAt, _connection.GetServerTimeUtc().Add(expireIn))));
 }