Skip to content

Instantly share code, notes, and snippets.

@odinserj
Created April 18, 2017 12:41
Show Gist options
  • Save odinserj/334fdb7d18bd63451f2f34546985f639 to your computer and use it in GitHub Desktop.
Save odinserj/334fdb7d18bd63451f2f34546985f639 to your computer and use it in GitHub Desktop.
DebounceAttribute
public sealed class DebounceAttribute : JobFilterAttribute, IElectStateFilter
{
private readonly string _resourceFormat;
private readonly int _milliseconds;
public DebounceAttribute(string resourceFormat, int milliseconds)
{
_resourceFormat = resourceFormat ?? throw new ArgumentNullException(nameof(resourceFormat));
_milliseconds = milliseconds;
}
public void OnStateElection(ElectStateContext context)
{
// We are intercepting only transitions to the Processing state that happen,
// when a worker picks up a background job and is about to process it.
if (!(context.CandidateState is ProcessingState)) return;
var connection = context.Connection as JobStorageConnection;
if (connection == null) throw new NotSupportedException();
var resource = String.Format(_resourceFormat, context.BackgroundJob.Job.Args.ToArray());
var shouldRun = false;
try
{
// Acquire a lock to prevent other workers to interfere between READ and WRITE operations
// It's better to use TimeSpan.Zero as a lock timeout, but Hangfire storages don't yet support
// zero timeouts.
using (connection.AcquireDistributedLock("debounce:lock", TimeSpan.FromMilliseconds(_milliseconds)))
{
var previousRun = JobHelper.DeserializeNullableDateTime(
connection.GetValueFromHash("debounce:resources", resource));
var now = DateTime.UtcNow;
if (previousRun == null || (now - previousRun.Value).TotalMilliseconds > _milliseconds)
{
// If previous run date is missing (firing for the first time), or was too long ago,
// we should update the last run date/time and execute a job.
connection.SetRangeInHash("debounce:resources", new[]
{
new KeyValuePair<string, string>(resource, JobHelper.SerializeDateTime(now)),
});
shouldRun = true;
}
}
}
catch (DistributedLockTimeoutException)
{
}
if (!shouldRun)
{
context.CandidateState = new DeletedState { Reason = "Deleted by the debounce filter" };
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment