I use this attribute to disable multiple jobs queued with same parameters at the same time. One “issue” I have with it is that cancelled jobs are also put in succeeded tab on dashboard. Is it possible to have them go to deleted tab?
public class DisableMultipleQueuedItemsAttribute : JobFilterAttribute, IServerFilter, IElectStateFilter
{
private static readonly TimeSpan _lockTimeout = TimeSpan.FromSeconds(10);
private static readonly List<string> _releaseStates = new List<string> { DeletedState.StateName, FailedState.StateName };
public void OnPerforming(PerformingContext filterContext)
{
if (!AddFingerprintIfNotExists(filterContext.Connection, filterContext.BackgroundJob))
{
filterContext.Canceled = true;
}
}
public void OnPerformed(PerformedContext filterContext)
{
// If job finished with success we remove lock.
if (filterContext.Exception == null || filterContext.ExceptionHandled)
{
RemoveFingerprint(filterContext.Connection, filterContext.BackgroundJob);
}
}
public void OnStateElection(ElectStateContext context)
{
// If job is going to release state and it isn't in failed state we release the lock.
// if job is already in failed state we don't release lock because lock can be from a running job, failed job already had it's lock removed.
if (_releaseStates.Contains(context.CandidateState.Name) && context.CurrentState != FailedState.StateName)
{
RemoveFingerprint(context.Connection, context.BackgroundJob);
}
}
private static bool AddFingerprintIfNotExists(IStorageConnection connection, BackgroundJob job)
{
using (connection.AcquireDistributedLock(GetFingerprintLockKey(job), _lockTimeout))
{
var fingerprint = connection.GetAllEntriesFromHash(GetFingerprintKey(job));
if (fingerprint != null && fingerprint.ContainsKey("Timestamp"))
{
// Actual fingerprint found, returning.
return false;
}
// Fingerprint does not exist, it is invalid (no `Timestamp` key),
// or it is not actual (timeout expired).
connection.SetRangeInHash(
GetFingerprintKey(job),
new Dictionary<string, string>
{
{ "Timestamp", DateTimeOffset.UtcNow.ToString("o") }
});
return true;
}
}
private static string GetFingerprint(BackgroundJob job)
{
var parameters = string.Empty;
if (job.Job.Args != null)
{
parameters = string.Join(".", job.Job.Args);
}
if (job.Job.Type == null || job.Job.Method == null)
{
return string.Empty;
}
var fingerprint = $"{job.Job.Type.Name}.{job.Job.Method.Name}.{parameters}";
return Helpers.GetSHA256(fingerprint);
}
private static string GetFingerprintKey(BackgroundJob job)
{
return $"Fingerprint:{GetFingerprint(job)}";
}
private static string GetFingerprintLockKey(BackgroundJob job)
{
return $"{GetFingerprintKey(job)}:lock";
}
private static void RemoveFingerprint(IStorageConnection connection, BackgroundJob job)
{
using (connection.AcquireDistributedLock(GetFingerprintLockKey(job), _lockTimeout))
{
using (var transaction = connection.CreateWriteTransaction())
{
transaction.RemoveHash(GetFingerprintKey(job));
transaction.Commit();
}
}
}
}