Issue when tracking is triggered from within a thread
emm26 opened this issue · comments
Hi, I am hoping you can help me with this. Please let me know if you need any more information.
I am using v '1.6.4' of the gem.
I have this in my model:
class MyModel
include PublicActivity::Model
tracked only: :create,
owner: proc { |controller, model| controller.current_user },
organization: proc { |controller, model| controller.current_user.organization }
and I have an action in the controller that triggers the tracking by creating an instance of the model above inside a thread:
class MyController
def my_action
Thread.new { MyModel.new } # triggers the tracking of the activity
end
end
The problem I am facing is that inside MyModel
, more specifically inside this block:
organization: proc { |controller, model| controller.current_user.organization }
is that controller
is set to nil
I believe the issue is to do with threads. This is how PublicActivity.get_controller
and PublicActivity.set_controller
are defined in store_controller.rb
:
module PublicActivity
class << self
# Setter for remembering controller instance
def set_controller(controller)
Thread.current[:public_activity_controller] = controller
end
# Getter for accessing the controller instance
def get_controller
Thread.current[:public_activity_controller]
end
end
...
end
I have created this monkey patch which seems to solve the issue for me:
module PublicActivity
class << self
# Getter for accessing the controller instance
def get_controller
Thread.current[:public_activity_controller] ||
# the next line is the monkey patch itself: when the tracking process gets triggered
# from inside a Thread, the newly created Thread does not contain :public_activity_controller
# The solution here looks for a Thread that contains :public_activity_controller
Thread.list.map { |thread| thread[:public_activity_controller] }.compact.first
end
end
end
My suspicion here is that the parent thread of the current thread contains :public_activity_controller
but the current one, does not. Hope this makes sense
Do you foresee any alternative solutions?
EDIT: It would be better in the monkey patch above, to look :public_activity_controller
in just the ancestors of the current thread. I was looking for ways of doing the former using built-in methods from the Thread class but could not find any
@emm26 Technically, what you’re asking for is a code review. I’m not quite sure this is the best place to ask as GH issues are a better fit for, well, issues as in bugs. However, this is not a bug in PublicActivity but in your code. Thus, I’ll try to provide some guidance. As you’ve asked code style related questions in other repos (e.g. Liquid) I still think you’d be better served asking that type of question on StackOverflow or a local meetup group. Anyway, let’s go:
Your issue stems from the following code:
Thread.new { MyModel.new } # triggers the tracking of the activity
Doing that, you effectively break some assumptions about regular requests, i.e. there’s just 1 thread (maybe even just 1 process if you use Unicorn as your app server). By using Thread.new { … }
you deliberately break out of this pattern, so default assumptions about 1 thread/request no longer holds true. In other words, you’re now in the driving seat, so you have to initialize your objects manually and can no longer rely on PublicActvity to get a reference of the controller. In your particular example the solution seems to be rather trivial though as you have everything in scope you need:
Thread.new { MyModel.new(owner: current_user) }
That’s it. You can still leave the following code in your model(s):
tracked only: :create,
owner: proc { |controller, model| controller.current_user },
organization: proc { |controller, model| controller.current_user.organization }
Based on the above you might want to use the following though:
Thread.new { MyModel.new(owner: current_user, organization: current_user.organization) }
Effectively, you’re simply overwriting the default proc
to dynamically setup owner
/organization
by being explicit.
I hope this helps; closing this issue as it’s not a bug in PublicActivity itself.
@ur5us Thank you for looking into it and your reply.
Is the following a rails convention/assumption or is it specific to this gem?
you effectively break some assumptions about regular requests, i.e. there’s just 1 thread (maybe even just 1 process if you use Unicorn as your app server)
I will keep in mind to ask in SO/other sources. I thought this was genuinely a bad generalisation in the gem’s code. The goal of attaching some code was to better exemplify the issue
Best,
Ed
@emm26 This is neither a PublicActivity nor a Ruby on Rails convention. All Ruby app servers (Puma, Unicorn, etc) work like that. Each request is handled by 1 thread/process by default. This is no different in other languages/frameworks. If you spin up extra threads then you have to manage that manually by passing in data explicitly which is what I did in my examples.