I have an extremely simple setup that tests the rails3 ActiveSupport::Notifications
.
By reading the documentation, the ActiveSupport::Notifications.subscribe
bit should perform it's operations asynchronously. Apparently this is not the case.
Example:
ActiveSupport::Notifications.subscribe "some.channel" do |name, start, finish, id, payload|
# do expensive task
sleep(10)
end
ActiveSupport::Notifications.instrument "some.channel" #=> will return 10 seconds later
I was under the impression that ActiveSupport::Notifications.instrument "some.channel"
would return right away and let the expensive task do expensive stuff. Otherwise I could just call the expensive task directly, without using a subscriber.
The documentation is also stating that there could be multiple subscribers. In that case I would get blocked until all other subscribers executed their code.
Is this right? If it is, can someone please explain what does this line from http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html mean?
The block will be called asynchronously whenever someone instruments “render”:
You're right. ActiveSupport::Notifications.instrument
does not call the subscribers asynchronously / concurrently or anything of the sort.
If you follow the code, you'll find that #instrument
calls an instrumenter, and instrumenter calls a notifier. The notifier is an instance of ActiveSupport::Notifications::Fanout
, which sends notifications to all listeners via the #publish
method:
def publish(name, *args)
listeners_for(name).each { |s| s.publish(name, *args) }
end
The #publish
method of the listener/subscriber is not asynchronous either:
def publish(message, *args)
@delegate.call(message, *args)
end
All this does is call the block provided by the subscriber. Synchronously.
So why does the documentation claim that The block will be called asynchronously whenever someone instruments “render”
and Notifications ships with a queue implementation that consumes and publish events to log subscribers in a thread
?
It looks like the original implementation was asynchronous, and they forgot to update the documentation. If you look at the change history for fanout.rb, you can see some commits from 2010 where the implementation was changed to a synchronous queue. A threaded implementation was probably too complicated and error-prone. You can even see some vestiges left over:
# This is a sync queue, so there is no waiting.
def wait
end
This looks like a good candidate for a commit to docrails.
In any case, even if the implementation were asynchronous, you would probably want any long-running code to go into a queue (such as resque) and get processed by a background worker. This is so that your webapp worker processes aren't tied up processing long-running tasks, rather than serving requests.
UPDATE: Found a commit from 11 months ago that removes the incorrect information about notifications being asychronous. However, the incorrect information that the queue is running in a thread is still there.
UPDATE 2: I committed to docrails to fix the information about the queue running in a thread.
UPDATE 3: My commit has been merged into the official documentation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With