ma11hew28
ma11hew28

Reputation: 126327

Ruby on Rails times out. How do I fork a process?

I have a page of a long list of items. Each has a check box next to it. There's a jQuery check-all function, but when I submit all of them at once, the request times out because it's doing a bunch of queries and inserting a bunch of records in the MySQL database for each item. If it were to not timeout, it'd probably take about 20 minutes. Instead, I just submit like 30 at a time.

I want to be able to just check all and submit and then just go on doing other work. My coworker (1) said I should just write a rake task. I did that, but I ended up duplicating code, and I prefer the user interface because what if I want to un-check a few? The rake task just submits them all.

Another coworker (2) recommended I use fork. He said that would spawn a new process that would run on the server but allow the server to respond before it's done. Then, since an item disappears after it's been submitted, I could just refresh the page to check if they're done.

I tried this on my local, however, it still seems that Rails is waiting for the process to finish before it responds to the POST request sent by the HTML form. The code I used looks like this:

def bulk_apply
  pid = fork do
    params[:ids].each do |id|
      Item.find(id).apply # takes a LONG time, esp. x 100
    end
  end
  Process.detach(pid) # reap child process automatically; don't leave running
  flash[:notice] = "Applying... Please wait... Then, refresh page. Only submit once. PID: #{pid}"
  redirect_to :back
end

Coworker 1 said that generally you don't want to fork Rails because fork creates a child process that is basically a copy of the Rails process. He said if you want to do it through the web GUI, use BackgroundJob (Bj) (because we're already using that in our Rails app). So, I'm looking into BackgroundJob now, but what do you recommend?

Upvotes: 1

Views: 1880

Answers (3)

Jon Snow
Jon Snow

Reputation: 11882

Try delayed_job gem. This is a database-based background job gem. We used it in an e-commerce website. For example, sending order confirmation email to the user is an ideal candidate for delayed job.

Additionally you can try multi-threading, which is supported by Ruby. This could make things run faster. Forking an entire process tends to be expensive due to memory usage.

Upvotes: 0

Travis Reeder
Travis Reeder

Reputation: 41103

you should check out IronWorker . It would be super easy to do what you want and it doesn't matter how long it takes.

In your action you'd just instantiate a worker which has the code that's doing all your database queries. Example worker:

Item.find(id).apply # takes a LONG time, esp. x 100

And here's how you'd queue up those jobs to run in parallel:

client = IronWorkerNG::Client.new
ids.each do |id|
  client.tasks.create("MyWorker", "id"=>id)
end

That's all you'd need to do and IronWorker takes care of the rest.

Upvotes: 2

Tim Stephenson
Tim Stephenson

Reputation: 830

I've had good success using background job. If you need rails you will be using script/runner which still starts up a new process with rails. The good thing is that Backround Job will make sure that there is never more than one running at a time.

You can also use script runner directly, or even run a rake task in the background like so:

system " RAILS_ENV=#{RAILS_ENV}   ruby  #{RAILS_ROOT}/script/runner   'CompositeGrid.calculate_values(#{self.id})'  & " unless RAILS_ENV == "test"

The ampersand tells it to start a new process. Be careful because you probably don't want a bunch of these running at the same time. I would definitely take advantage of background job if it is already available.

Upvotes: 2

Related Questions