I need to make some slow operations on controller action. But it's not necessary to wait this operation for response rendering.
class ProductController < ActionController
def update
slow_operations()
render json: {status: 'ok'}
end
end
Even I move my code after render in Product#update action, it's not reduces response time.
class ProductController < ActionController
def update
render json: {status: 'ok'}
slow_operations()
end
end
How to force to return complete response before executing of slow operations?
Because of the way Rails works, it's still going to do the actual "rendering" after the action is complete - so, as you found out, moving "render" higher in your action doesn't help. What you need to do is shuffle off the long-running operation into a background process. There's lots of gems to do this, including BackgroundRb, Delayed Job and Sidekiq (my personal favourite, largely because it is multi-threaded, cutting down on the number of processes you need to start, and because of its nice web-based admin/monitoring interface).
There's even a Railscast to get you started for most of these, like this one: http://railscasts.com/episodes/366-sidekiq
You need to integrate something like Resque or Girl Friday to offload the slow process to a background task.
Related
I have made a rails app. Users can upload images. Once the images are saved into database. An algorithm being called to process those pictures.
For now, it was realized within a controller action like this:
def create
#post = current_user.posts.build(params)
if #post.save
flash[:success]="post created"
redirect_to root_url
image_names = []
#post.picture.each do |imgs|
image_names << imgs.url
end
my_algorithm(image_names)
else
render 'static_pages/home'
end
end
It works correctly. The problem is the page didn't show until the algorithm finishing. And the algorithm took long time. How to fix it. Or maybe call my_algorithm other places? Or delay_job?
I think you should use Active Job for that it will make your job background job
You should try the background jobs to perform that action in particular time.
You may use sidekiq gem to trigger the events in background.
Or You can refer the following URL to, do the active jobs,
http://edgeguides.rubyonrails.org/active_job_basics.html#enqueue-the-job
Refer the above url and configure the app and perform the operations in background.
I have additionally mention the one more link for scheduled jobs,
How to perform a background job now?
First of all, this should be done in a call back, here in the after_create callback of the Post model. And from the callback you can en queue one delayed job or other background job which will fire the algorithm. In front end you can show some messages like under processing in the place where you want to show the processed information.
Thanks
(I know it's not best practice, but I'm still wondering if there's a way to do it)
I have a controller action that has a moderately long-running part (~30 seconds). I'd like to respond to the HTTP request (so that it closes), run the part that takes time, then use something like Pusher to notify that it's successful (last part is done, but here for context).
Something like this:
def some_action
if action_should_succeed?
head :no_content
else
render text: "Some error message", status: :unproccessable_entity
end
# Connection is now closed and the client has received the HTTP response
do_long_thing()
send_notification()
end
I know that the "correct" way to do this is to have a job queue and a worker process, but while this is the only action with this kind of delayed processing, I'd prefer not to add all that. Any ideas?
I want to save information about requests to a certain action in a model named Impression.
I assume it's benificial for the visitor's response time to save this info in an after_filter, e.g:
after_filter :save_impression
private
def save_impression
Impression.create!(ip_address: request.remote_ip, controller_name: params[:controller], action_name: params[:action], referer: request.referer)
end
Can this code be optimized or am I doing it right?
A good solution for that would typically involve using a worker. Anything that is not mission critical to the request and that involves complex computing can be deferred and run later by a background job.
Two common implementations of workers are delayed_job and resque.
For example, with resque, you would have a job class in app/jobs/impression_creation_job.rb, containing something like that :
class ImpressionJob
#queue = :impression
def self.perform( attrs )
Impression.create!( attrs )
end
end
And you can call it in your controller like that :
after_filter :save_impression
private
def save_impression
Resque.enqueue( ImpressionJob, ip_address: request.remote_ip, controller_name: params[:controller], action_name: params[:action], referer: request.referer)
end
This will ensure a fast handling on the request part (it just loads data in redis) and will then be processed by a background process (see resque documentation for how to set it up and start workers).
Please note that this will be useful in your case in only two cases :
Your app is always under heavy load or need especially good response time
You do big computations in Impression#before_create or other callbacks
If not matching one of those conditions, it's probably more effective to just let your impression creation in a controller filter : accessing database has a cost, but not that much that a user will feel when you make a single insertion in database.
This will still run before render. To run after the render/redirect, you need to spawn a separate thread.
See this question
I have an action that takes a long time. I want to be able to provide updates during the process so the user is not confused as to whether he lost the connection or something. Can I do something like this:
class HeavyLiftingController < ApplicationController
def data_mine
render_update :js=>"alert('Just starting!')"
# do some complicated find etc.
render_update :js=>"alert('Found the records!')"
# do some processing ...
render_update :js=>"alert('Done processig')"
# send #results to view
end
end
No, you can only issue ONE render within a controller action. The render does NOTHING until the controller terminates. When data_mine terminates, there will be THREE renders, which will result in an error.
UPDATE:
You'll likely have to set up a JavaScript (jquery) timer in the browser, then periodically send an AJAX request to the server to determine the current status of your long running task.
For example the long running task could write a log as it progresses, and the periodic AJAX request would read that log and create some kind of status display, and return that to the browser for display.
It is impossible to handle the request that way. For each request, you have just one answer.
If your action takes a long time, then maybe it should be performed asynchronously. You could send user e-mails during the process to notify him of the progress.
I suggest that you to take a look on DelayedJob gem:
http://rubygems.org/gems/delayed_job
It will handle most difficult parts of dealing with assync stuff for you (serializing / deserializing your objects, storage, so on...).
Hope it helps you!
I have a time taking task in my controller for which I want to trigger a simple progress bar/progress status in my view.
In my controller I have something like
threads = []
total.times do |x|
Thread.new {
the_time_taking_task(x)
#Something here to trigger the progressbar in the view
}
end
threads.each { |aThread| aThread.join }
render :action => 'some_action'
I can't put a render in there because it throws a double render error.
I tried putting a render and return there and I got 'return can't jump across threads' error
I am trying render as it seems to be the only way my controller can talk to view. Is my approach wrong ?
Would want something simple like this in the view which applies the progress bar width through that call
<div style="width: <%= (percent_val)%>px; ">
Please advice.
Yes it's a wrong approach.
The Web is stateless, and you can't block a process to render view and get it after by another request.
The good way is :
1) Generate a queue tasking instead of your thread. You can use delayed_job, resque or queue system, like rabbitMQ or beanstalkD
2) Add a controller to check if the process in your queue is end and how is progress. Inside, you put the queue id and check if is end or not and return the progress percent.
The easiest way is do that by Ajax.
Yes, your approach is wrong.
shingara explains this well.
I'm offering a slightly more concrete solution for you.
To get the effect you want, you basically need the following steps:
Offload the thread into a background job server. Checkout resque or delayed_job.
Have the background job write the progress to database or some persistant medium.
In Rails, write an action that returns the current progress (as json or xml)
In your view, write a periodic ajax request that calls the action written in step 3.
update the view accordingly.
You're done! :D