I have a set of expensive operations specific to each individual user of my application, they are all encapsulated in a method (for example, the write_collections method). In this method, the program communicates with Facebook and MongoDB. I want to run this method in a thread for each user.
This stream is called in the get '/' Sinatra route, but the result of the stream (state in the database) is only required on get '/calculate' . My idea is to run the stream on get '/' and join it on get '/calculate' to ensure that all user data is correctly written to the database before calculating the results for the user.
To illustrate:
Approach i
get "/" do @user = @graph.get_object("me") data_thread = Thread.new do write_collections(@user) end session[:data_thread] = data_thread.object_id erb :index end get "/calculate" do begin
To find the right thread for which a specific user should wait to join, I am currently using ObjectSpace._id2ref(session[:data_thread]) .
Detailed:
From the official Ruby docs for Object#object_id :
object_id -> fixnum: Returns the integer identifier for obj. The same number will be returned for all id calls for this object, and two active objects will not share the identifier.
and for ObjectSpace :
The ObjectSpace module contains a number of routines that interact with the garbage collector and allow you to move all living objects using an iterator.
- Is the "active object" from the first quote the same as the "living object" from the second?
Suppose the following situation:
Approach II
configure do # map user_id to corresponding user thread data_threads_hash = {} set :data_threads_hash, data_threads_hash end get "/" do @user = @graph.get_object("me") data_thread = Thread.new do write_collections(@user) end session[:user_id] = @user['id'] settings.data_threads_hash[session[:user_id]] = data_thread erb :index end get "/calculate" do if settings.data_threads_hash[session[:user_id]].alive? data_thread = settings.data_threads_hash[session[:user_id]] data_thread.join settings.data_threads_hash.delete session[:user_id] end # do calculations based on state in database # and show results to user "<p>Under construction</p>" end
Detailed:
I tried this after reading Sinatra: README . In the Configuration section:
Run once, at startup, in any environment ... You can access these settings through the settings ...
And in the Scopes and bindings section, Scope / class:
Each Sinatra application corresponds to a subclass of Sinatra :: Base. If you use a top-level DSL (requires a "sinatra"), then this class is Sinatra :: Application, otherwise it is a subclass that you created explicitly. At the class level, you have methods like get or before, but you cannot access the request or session objects, since there is only one application class for all requests.
I am using top level DSL.
Parameters created using the set are class-level methods ... You can reach the scope object (class) as follows: settings from within the query scope
- Considering what @FrederickCheung said in the comments and quotes from Scopes and Binding, will this approach work if I need more than one dyno / worker (currently this application uses only one dyno)?
Summary
- How should I handle the described situation with users and their respective threads in Sinatra, how good or bad are the approaches from the above examples?
Any comments or links are welcome.