HangfireIO / Hangfire

An easy way to perform background job processing in .NET and .NET Core applications. No Windows Service or separate process required
https://www.hangfire.io
Other
9.36k stars 1.7k forks source link

Consider making attributes non-sealed #1932

Open LordJZ opened 3 years ago

LordJZ commented 3 years ago

This issue was previously discussed in #503. TL;DR: job filter attributes such as QueueAttribute are sealed and it makes it unnecessarily hard to use Hangfire in some cases.

There are many use-cases for inheritable job filters:

Note that it is of course still possible to implement all use cases without inheriting from existing filters. You could wrap (aggregate) attribute classes, or copy/paste code, or even recompile Hangfire itself (e.g. to fix internal references in ctors in Ace). Since the project is open-source and you also provide source code for closed-source components, everything is effectively public but inconvenient to use. It would be much easier to just have the filter classes un-sealed.

odinserj commented 3 years ago

Thanks for reporting this. The idea of using inheritance to provide common constructor arguments and change the ordering seems useful indeed. The sealed attribute is added simply because it's much easier to remove it than to add it. However there's a chance that one will face with an unexpected consequences of using inheritance.

There are different levels of filter's scope – method, type and global, and it's possible to specify the same filter on multiple levels. Each filter also have the AllowMultiple property that's usually disabled for job filters. And when it's disabled, all the duplicates are removed from the resulting filter chain, where most narrow scope gets the preference. But since duplicate detection is based on concrete type, we'll get subtle behaviour when inheritance is implemented.

The simplest example – consider you've overridden the AutomaticRetryAttribute with MyAutomaticRetryAttribute and applied it to some jobs. You expect that MyAutomaticRetryAttribute will be used now, but actually both AutomaticRetryAttribute and MyAutomaticRetryAttribute will be invoked, because the former is added globally by default. Of course you can tweak the Order property, but anyway there will be possible problems for other filters, e.g. ThrottlingAttribute.

So unfortunately there's no simple solution for this problem. For QueueAttribute it's relatively easy to make your own filter, since it's a one-line job filter. Can you tell me what logic you need to implement for other filters? It's easy to declare everything extensible, but it's much harder to make everything consistent after doing this step.

odinserj commented 3 years ago

But I don't see any negative consequences for filters that support multiple instances, like MutexAttribute, SemaphoreAttribute, SlidingWindowAttribute, FixedWindowAttribute and DynamicWindowAttribute, so we can remove the sealed keyword from them.

LordJZ commented 3 years ago

Thank you -- the type-uniqueness case is indeed interesting. One could argue that the unique filters should be job properties, like Queue in the newer version of Hangfire, but that's a long shot from now and out of scope of this issue.

Can you tell me what logic you need to implement for other filters?

I have my own Queue where I plan to add more complex logic for queue selection. I also provide my own AutomaticRetryAttribute with IsRetryAttempt(IState)/IsRetryAttemptReason(string) methods exposed to be used in a polling routine that lets one await a job for completion. Basically makes Hangfire a Task.Run on steroids.

But I don't see any negative consequences for filters that support multiple instances, like MutexAttribute, SemaphoreAttribute, SlidingWindowAttribute, FixedWindowAttribute and DynamicWindowAttribute, so we can remove the sealed keyword from them.

Thanks, that would be great. These are exactly the attributes with which I had the most difficulties.

odinserj commented 3 years ago

Thank you -- the type-uniqueness case is indeed interesting. One could argue that the unique filters should be job properties, like Queue in the newer version of Hangfire, but that's a long shot from now and out of scope of this issue.

Actually this is possible with a custom job filter provider. Perhaps I will write something to handle this out of box.

Thanks, that would be great. These are exactly the attributes with which I had the most difficulties.

Done, updates available in Hangfire.Throttling 1.3.1.

LordJZ commented 3 years ago

Done, updates available in Hangfire.Throttling 1.3.1.

Great thanks. I can confirm inheriting [Mutex} works for me, including GetDescription etc.