Custom themes in the Thin, Sinatra app on Heroku

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 # Is this safe enough? if ObjectSpace._id2ref(session[:data_thread]).alive? data_thread = ObjectSpace._id2ref(session[:data_thread]) data_thread.join end rescue RangeError => range_err # session[:data_thread] is not id value # direct access to /calculate without session rescue TypeError => type_err # session[:data_thread] is nil end # do calculations based on state in database # and show results to user "<p>Under construction</p>" end 

To find the right thread for which a specific user should wait to join, I am currently using ObjectSpace._id2ref(session[:data_thread]) .

  • How safe is it?

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:

  • User A access '/' [now A starts with object_id a ]
  • Topic A is completed [it is no longer active and it is object_id )
  • User B access '/' [now B begins with the same object_id a (* * is this possible?)]
  • User A access '/calculate' [ session[:data_thread] is a , so ObjectSpace._id2ref(session[:data_thread]) is actually B. ]
  • Inconsistent state - user A expects thread B.

    • Is this scenario possible in Sinatra, Tin, Geroku?

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.

+5
source share
1 answer

I'm not sure if this design makes sense. Why does each user need their own thread? Why does one request join a thread created by another request? Even if it was possible (using only one dynamometer), I do not think that this is a good way to do what you want to do.

Based on the description of the application in the question, you want to run some calculate method after completion of the write_collections method. So why can't the write_collections method call the calculate method? Or, why can't you use a filter or an observer to calculate?

All in all, you seem to be mixing two separate functions:

  • calculate functionality
  • GET /calculate

I think that to call the functionality of calculate must be only one trigger. It is either after write_collections completed , or when the user requests it ( GET /calculate ).

A more general solution is to have the calculate function run in the background and store the results in a database. Later, when the user makes a request, he is ready and can be quickly returned.

+2
source

Source: https://habr.com/ru/post/1210309/


All Articles