Add support for a discard
option in concurrency controls #594
Add this suggestion to a batch that can be applied as a single commit. This suggestion is invalid because no changes were made to the code. Suggestions cannot be applied while the pull request is closed. Suggestions cannot be applied while viewing a subset of changes. Only one suggestion per line can be applied in a batch. Add this suggestion to a batch that can be applied as a single commit. Applying suggestions on deleted lines is not supported. You must change the existing code in this line in order to create a valid suggestion. Outdated suggestions cannot be applied. This suggestion has been applied or marked resolved. Suggestions cannot be applied from pending reviews. Suggestions cannot be applied on multi-line comments. Suggestions cannot be applied while the pull request is queued to merge. Suggestion cannot be applied right now. Please check back later.
This PR builds on #523 and #586, after having great discussions in those with @joelzwarrington and @mhenrixon. Thanks a lot for your work on that and for bearing with me 🙏 In the beginning, I wasn't sure how to approach this because of the way Solid Queue enqueues jobs in two steps (first, the job is created, then an execution for it is created), but after going through those two PRs again and looking into how Sidekiq's unique jobs feature behaves, I think the behaviour here is the right one.
The way this works is quite simple: a new option,
on_conflict
, that defaults to:block
but can be set to:discard
.Basically, when a job needs to be discarded because of a conflict, we delete it in the same transaction where it was created. We don't mark it as finished as #586 does because I don't want to have a provider job ID in this case. I became convinced of this after reviewing how Sidekiq's unique jobs feature behaves. I was super lucky I got the chance to speak with @mperham yesterday at RailsConf (🙏 🙇♀️ ), so I could make sure I got this right. Chatting with him and other super nice and helpful attendees (I'm sorry I didn't catch your GitHub handle, but if you're reading this, you know who you are so let me know!), made me realise Active Job's behaviour with regards to
successfully_enqueued?
is inconsistent betweenperform_later
andperform_all_later
. Withperform_later
, unless the adapter raises, the job is considered successfully enqueued. This doesn't matchperform_all_later
where raising doesn't make sense because you need to enqueue multiple jobs, so you don't want the whole thing to fail if a single job fails to be enqueued. In Sidekiq's case, when a job fails to be enqueued an exception is not raised: the return result is not a job ID (jid
) butnil
instead. This is reflected in the bulk enqueue implementation but not in the individual enqueue. I think this should be fixed in Active Job, giving the adapter a chance to setsucessfully_enqueued
instead of having to raise an exception to prevent it from being set totrue
here. I think the inconsistency might simply be becauseperform_all_later
was added much later and the exception raising behaviour couldn't be used there 🤔Anyway, here I chose to do the same as Sidekiq, and just not enqueue the job at all (and not have a job ID) when we need to discard a job because of concurrency limits.