Class: Session

Inherits:
Object
  • Object
show all
Defined in:
backend/app/model/session.rb

Constant Summary collapse

SESSION_ID_LENGTH =
32
UPDATE_FREQUENCY_SECONDS =

If it’s worth doing it’s worth overdoing!

For really small AJAX-driven lookups, like nodes and waypoints, sometimes touching the user’s session (with its associated database commit) was adding 5-10x to the response time. Upsetting!

Since touching sessions is very common, but not really mission critical, offload the work to a background thread that will periodically update them.

5

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(sid = nil, store = nil, system_mtime = nil) ⇒ Session

Returns a new instance of Session.



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'backend/app/model/session.rb', line 58

def initialize(sid = nil, store = nil, system_mtime = nil)
  now = Time.now

  if not sid
    # Create a new session in the DB
    DB.open do |db|

      while true
        sid = SecureRandom.hex(SESSION_ID_LENGTH)

        completed = DB.attempt {
          db[:session].insert(:session_id => Digest::SHA1.hexdigest(sid),
                              :session_data => [Marshal.dump({})].pack("m*"),
                              :system_mtime => now)
          true
        }.and_if_constraint_fails {
          # Retry with a different session ID.
          false
        }

        break if completed
      end

      @id = sid
      @system_mtime = now
      @store = {}
    end
  else
    @id = sid
    @store = store
    @system_mtime = system_mtime
  end
end

Instance Attribute Details

#idObject (readonly)

Returns the value of attribute id



56
57
58
# File 'backend/app/model/session.rb', line 56

def id
  @id
end

#system_mtimeObject (readonly)

Returns the value of attribute system_mtime



56
57
58
# File 'backend/app/model/session.rb', line 56

def system_mtime
  @system_mtime
end

Class Method Details

.expire(sid) ⇒ Object



109
110
111
112
113
# File 'backend/app/model/session.rb', line 109

def self.expire(sid)
  DB.open do |db|
    db[:session].filter(:session_id => Digest::SHA1.hexdigest(sid)).delete
  end
end

.expire_old_sessionsObject



116
117
118
119
120
121
122
123
124
# File 'backend/app/model/session.rb', line 116

def self.expire_old_sessions
  max_expirable_age = AppConfig[:session_expire_after_seconds] || (7 * 24 * 60 * 60)
  max_nonexpirable_age = AppConfig[:session_nonexpirable_force_expire_after_seconds] || (7 * 24 * 60 * 60)

  DB.open do |db|
    db[:session].where {system_mtime < (Time.now - max_expirable_age)}.filter(:expirable => 1).delete
    db[:session].where {system_mtime < (Time.now - max_nonexpirable_age)}.filter(:expirable => 0).delete
  end
end

.find(sid) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'backend/app/model/session.rb', line 93

def self.find(sid)
  DB.open do |db|
    row = db[:session]
      .filter(:session_id => Digest::SHA1.hexdigest(sid))
      .select(:session_data, :system_mtime)
      .first

    if row
      Session.new(sid, Marshal.load(row[:session_data].unpack("m*").first), row[:system_mtime])
    else
      nil
    end
  end
end

.initObject



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'backend/app/model/session.rb', line 19

def self.init
  @sessions_to_update = Queue.new

  @session_touch_thread = Thread.new do
    while true
      begin
        self.touch_pending_sessions
      rescue
        Log.exception($!)
      end

      sleep UPDATE_FREQUENCY_SECONDS
    end
  end
end

.touch_pending_sessions(now = Time.now) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'backend/app/model/session.rb', line 35

def self.touch_pending_sessions(now = Time.now)
  sessions = []

  while !@sessions_to_update.empty?
    sessions << @sessions_to_update.pop
  end

  unless sessions.empty?
    DB.open do |db|
      db[:session]
        .filter(:session_id => sessions.map {|id| Digest::SHA1.hexdigest(id) }.uniq)
        .update(:system_mtime => now)
    end
  end
end

.touch_session(id) ⇒ Object



51
52
53
# File 'backend/app/model/session.rb', line 51

def self.touch_session(id)
  @sessions_to_update << id
end

Instance Method Details

#[](key) ⇒ Object



132
133
134
# File 'backend/app/model/session.rb', line 132

def [](key)
  return @store[key]
end

#[]=(key, val) ⇒ Object



127
128
129
# File 'backend/app/model/session.rb', line 127

def []=(key, val)
  @store[key] = val
end

#ageObject



153
154
155
# File 'backend/app/model/session.rb', line 153

def age
  (Time.now - system_mtime).to_i
end

#saveObject



137
138
139
140
141
142
143
144
145
# File 'backend/app/model/session.rb', line 137

def save
  DB.open do |db|
    db[:session]
      .filter(:session_id => Digest::SHA1.hexdigest(@id))
      .update(:session_data => [Marshal.dump(@store)].pack("m*"),
              :expirable => @store[:expirable] ? 1 : 0,
              :system_mtime => Time.now)
  end
end

#touchObject



148
149
150
# File 'backend/app/model/session.rb', line 148

def touch
  self.class.touch_session(@id)
end