module CanvasSync::JobUniqueness module OnConflict extend ActiveSupport::Autoload autoload :Base autoload :Log autoload :NullStrategy autoload :Raise autoload :Reject autoload :Reschedule # Replace is... hard. It involves hacking into some of the inner workings of Sidekiq and/or ActiveJob. # The best solution would be to somehow mark the previously-scheduled job as dead and let it be picked up by workers but not invoke # its perform method (technically this would mess with success numbers, but does anybody really care about those?). # Replace will also conflict with the UntilExecuted strategy as it wouldn't be able to replace a job that has begun executing. # # Replace is useful for two cases: # 1. Moving the job to the back of the queue, eg for debouncing purposes # 2. Replacing non-hashed params with updated values # A solution to (2) would be to store the latest params outside of the Job params - eg in the DB or in Redis directly. # (1) is solvebale in a similar out-of-band way, but other, better solutions may exist as well # # If the param in question is a (eg) a list that you want to append to, you could: # Push to the list in Redis # Queue the job # (Push another param to the list in Redis) # (Re-enqueue the job, but it won't enqueue because it is locked) # When the job begins, it pulls all values from the field in Redis an processes # Adding more params from this point would allow another job to enqueue # # We may implement some tooling for this at some point, but for now the recommendation is to handle this yourself, # autoload :Replace class << self def lookup(strategy) matching_strategy(strategy.to_s.camelize) || CanvasSync::JobUniqueness.config.conflict_strategies[strategy] || raise(StrategyNotFound, "on_conflict: #{strategy} is not found. Is it declared in the configuration?") end def validate!(on_conflict, lock_strategy) on_conflict = { enqueue: on_conflict, perform: on_conflict } unless on_conflict.is_a?(Hash) lock_strategy = Strategy.lookup(lock_strategy) if lock_strategy.is_a?(Symbol) on_conflict.each do |origin, strategy| strategy = OnConflict.lookup(strategy) if strategy.is_a?(Symbol) if lock_strategy.locks_on.include?(origin) && !strategy.valid_for.include?(origin) raise ArgumentError, "(#{origin.to_s.titleize}) conflict strategy #{strategy.name.underscore} is not valid for lock strategy #{lock_strategy.name.underscore}" end end end private def matching_strategy(const) const_get(const, false) if const_defined?(const, false) end end end end