I have an application in Rails and what I'm trying to do is execute a database stored procedure asynchronously when a user navigates from one particular page to another - the user must continue his navigation while the stored procedure runs.
I don't really need to have a callback when the procedure finishes, I just need to run it in background.
I'm trying to use the following code:
require 'eventmachine'
require 'em-http'
require 'fiber'
def async_fetch(url)
f = Fiber.current
http = EventMachine::HttpRequest.new(url).get :timeout => 10
http.callback { f.resume(http) }
http.errback { f.resume(http) }
return Fiber.yield
end
EventMachine.run do
Fiber.new{
url = url_for :controller => 'common', :action => 'execute_stored_procedure'
data = async_fetch(url)
EventMachine.stop
}.resume
end
The problem here is that when the stored procedure starts, the user must be redirected to another page, but the next page remains "pending" and renders only when the procedure finishes.
I tried to use thin (in my development environment) as my server with --threaded option with no success, and now I'm thinking about using Phusion Passenger Enterprise in multithreaded mode in the production server, but it's a commercial version and it does not have any trials, I'm afraid that it's not what I need.
Does anybody know a good solution to achieve this? To execute the stored procedure I have to make a request to the same webserver my application is running, so my webserver must accept multiple connections at a time (multithreaded), is it correct?
Some useful information:
Development:
Production:
I really appreciate any help.
UPDATE #1
I tried celluloid as recommended by Jesse. Here is my code:
require 'celluloid/autostart'
class PropertyWorker
include Celluloid
def engage(args)
ActiveRecord::Base.execute_procedure("gaiainc.sp_ins_property_profiles", args[:id])
end
end
...
def create
@property = Property.new(params[:property])
respond_to do |format|
if @property.save
PropertyWorker.new.async.engage({:id => @property.id})
format.html { redirect_to new_enterprise_property_activation_url(@property.enterprise.id, @property.id) }
format.json { render json: @property, status: :created, location: @property }
else
format.html { render action: "new" }
format.json { render json: @property.errors, status: :unprocessable_entity }
end
end
end
When then action 'create' is called, the record is created, the stored procedure starts but the next page is not rendered, the request remains "pending" in the browser until the procedure finishes. As soon as the procedure finishes, the page is rendered.
I can't figure out what is going on. Was the procedure not supposed to run in background?
In something like this, I recommend either Sidekiq or Celluloid. What you want to do is spin off a thread and execute something, returning access to the calling process and having it continue on.
Sidekiq would require a separate process to run (and Redis), Celluloid would not. Otherwise, they are similar.
Sidekiq:
class AsyncProc
include Sidekiq::Worker
def perform(args)
CodeToExecuteStoredProcedure.engage! args
end
end
You'd call it with:
AsyncProc.perform_async {whatever: arguments, you: want}
This would schedule a job in Redis and get executed when a spare Sidekiq worker has time
Celluloid:
require 'celluloid/autostart'
class AsyncProc
include Celluloid
def engage(args)
CodeToExecuteStoredProcedure.engage! args
end
end
And to call it:
AsyncProc.new.async.engage {whatever: arguments, you: want}
This would execute asynchronously, pretty much right away.