How does Sidekiq passes arguments to the perform method?

Peter Butterworth picture Peter Butterworth · Apr 3, 2013 · Viewed 10.5k times · Source

I have this Sidekiq worker:

class DealNoteWorker
  include Sidekiq::Worker
  sidekiq_options queue: :email

  def perform(options = {})
    if options[:type] == "deal_watch_mailer"
      deal_watchers = DealWatcher.where("deal_id = ?", options[:deal_id])

      deal_note = DealNote.find(options[:deal_note_id])

      current_user = User.find(options[:current_user_id])

      deal_watchers.each do |deal_watcher|
        unless deal_watcher.user_id == options[:current_user_id]
          # Tell the DealWatchMailer to send emails to ALL watchers advising of change to deal
          if deal_watcher.user.active
            DealWatchMailer.deal_watch_email(deal_watcher, nil, deal_note, current_user, options[:url]).deliver
          end
        end
      end
    elsif options[:type] == "deal_note_mailer"
      options[:user_ids].each do |id|
        if DealWatcher.where("deal_id = ? and user_id =?", options[:deal_id], id).count == 0
          deal_note = Deal.find(options[:deal_note_id])
          user = User.find_by_id(id)
          DealNoteMailer.deal_note_email(deal_note, user, options[:url]).deliver
        end
      end
    end
  end
end

I pass a hash to the perform_async method, but I think that the parameters transfered to the perform method are not of the same type as the ones passed to perform_async. I tried to user logger.info and p to debug my issue but nothing gets outputted...

The issue is that the job is added to the email queue but never gets processed. I even tried to raise an exception in the perform method (on the first line of the method) but nothing was outputted either...

I know as a fact that the following worker works:

class DealNoteWorker
  include Sidekiq::Worker

  def perform(deal_id, deal_note_id, current_user_id, url)
    deal_watchers = DealWatcher.where("deal_id = ?", deal_id)

    deal_note = DealNote.find(deal_note_id)

    current_user = User.find(current_user_id)

    deal_watchers.each do |deal_watcher|
      unless deal_watcher.user_id == current_user_id
        # Tell the DealWatchMailer to send emails to ALL watchers advising of change to deal
        if deal_watcher.user.active
          DealWatchMailer.deal_watch_email(deal_watcher, nil, deal_note, current_user, url).deliver
        end
      end
    end
  end
end

So the problem lies in the hash parameter (options). What am I doing wrong please?

Answer

mertonium picture mertonium · Mar 28, 2014

From the Sidekiq documentation:

The arguments you pass to perform_async must be composed of simple JSON datatypes: string, integer, float, boolean, null, array and hash. The Sidekiq client API uses JSON.dump to send the data to Redis. The Sidekiq server pulls that JSON data from Redis and uses JSON.load to convert the data back into Ruby types to pass to your perform method. Don't pass symbols or complex Ruby objects (like Date or Time!) as those will not survive the dump/load round trip correctly.

You can see this on the console:

> options = { :a => 'b' }
> how_sidekiq_stores_the_options = JSON.dump(options)
> how_sidekiq_loads_the_options = JSON.load(how_sidekiq_stores_the_options)
> how_sidekiq_stores_the_options == how_sidekiq_loads_the_options
  false

It looks like you are using symbols as the keys for your options hash. If you switch to string keys, it should work.