There's a controller action in my Rails app that contacts a user via text-message and email. For reasons I won't go into, the text-message needs to complete before the email can be sent successfully. I originally had something like this:
controller:
class MyController < ApplicationController
def contact_user
ContactUserWorker.perform_async(@user.id)
end
end
workers:
class ContactUserWorker
include Sidekiq::Worker
def perform(user_id)
SendUserTextWorker.perform_async(user_id)
SendUserEmailWorker.perform_async(user_id)
end
end
class SendUserTextWorker
include Sidekiq::Worker
def perform(user_id)
user = User.find(user_id)
user.send_text
end
end
class SendUserEmailWorker
include Sidekiq::Worker
def perform(user_id)
user = User.find(user_id)
user.send_email
end
end
This was unreliable; sometimes the email would fail, sometimes both would fail. I'm trying to determine whether perform_async
was the cause of the problem. Was the async
part allowing the email to fire off before the text had completed? I'm a little fuzzy on how exactly perform_async
works, but that sounded like a reasonable guess.
At first, I refactored ContactUserWorker
to:
class ContactUserWorker
include Sidekiq::Worker
def perform(user_id)
user = User.find(user_id)
User.send_text
SendUserEmailWorker.perform_async(user_id)
end
end
Eventually though, I just moved the call to send_text
out of the workers altogether and into the controller:
class MyController < ApplicationController
def contact_user
@user.send_text
SendUserEmailWorker.perform_async(@user.id)
end
end
This is a simplified version of the real code, but that's the gist of it. It seems to be working fine now, though I still wonder whether the problem was Sidekiq-related or if something else was going on.
I'm curious whether my original structure would've worked if I'd used perform
instead of perform_async
for all the calls except the email call. Like this:
class MyController < ApplicationController
def contact_user
ContactUserWorker.perform(@user.id)
end
end
class ContactUserWorker
include Sidekiq::Worker
def perform(user_id)
SendUserTextWorker.perform(user_id)
SendUserEmailWorker.perform_async(user_id)
end
end
If the email can only be sent after the text message has been sent, then send the email after successful completion of sending the text.
class ContactUserWorker
include Sidekiq::Worker
def perform(user_id)
SendUserTextWorker.perform_async(user_id)
end
end
class SendUserTextWorker
include Sidekiq::Worker
def perform(user_id)
user = User.find(user_id)
text_sent = user.send_text
SendUserEmailWorker.perform_async(user_id) if text_sent
end
end
class SendUserEmailWorker
include Sidekiq::Worker
def perform(user_id)
user = User.find(user_id)
user.send_email
end
end
In user.send_text you need to handle the fact that neither the text or the email has been sent.