current_tenant missing while using with GoodJob and CurrentAttribute
Sorry, this might not help with identifying the root cause.
Just wanted to share some ad-hoc workarounds I implemented for this bug(?).
This happens when I call perform_later on another job from within a job.
I don't fully understand the interactions between ActiveJob, GoodJob, CurrentAttributes, and ActsAsTenant::ActiveJobExtensions.
From what I understand, when you spawn a new thread, ActsAsTenant.current_tenant is not preserved. Sometimes current attributes are lost during the serialize call, even though I assumed serialization happens during the enqueue process, and retrying jobs also causes context loss.
So I suspect that GoodJob runs ActiveJob in new threads, which would explain the context loss we see when new threads are spawned. To work around this, I'm manually preserving the context by storing it as job attributes and ensuring it gets properly serialized and restored when the job runs. (I don't understand why this way it's preserved!)
I created these ad-hoc patches to maintain job context consistency. Note that this code is not fully tested.
It may broke on perform_now.
# class ApplicationJob < ActiveJob::Base
# include JobContext
# ...
# end
module JobContext
extend ActiveSupport::Concern
included do
attr_accessor :host_with_port
attr_accessor :tenant
prepend Prepends
before_enqueue { |job| job.set_context }
around_perform do |job, block|
with_context do
block.call
end
end
def set_context
self.host_with_port = CurrentContext.host_with_port
self.tenant = ActsAsTenant.current_tenant
end
def with_context
CurrentContext.with(host_with_port: host_with_port) do
ActsAsTenant.with_tenant(tenant) do
yield
end
end
end
end
module Prepends
def serialize
super.merge("host_with_port" => self.host_with_port)
.merge("tenant" => self.tenant&.to_global_id&.to_s)
end
def deserialize(job_data)
self.host_with_port = job_data["host_with_port"]
self.tenant = job_data["tenant"].present? ? GlobalID::Locator.locate(job_data["tenant"]) : nil
super(job_data)
end
end
end
# Not essential code, just showing what I was doing
class CurrentContext < ActiveSupport::CurrentAttributes
attribute :host_with_port
attribute :locale
attribute :timezone
def self.reset
self.host_with_port = nil
self.locale = nil
self.timezone = nil
end
end