Skip to content

Commit 3928b8f

Browse files
committed
Trying a concept, without tests yet
1 parent 9701a8d commit 3928b8f

File tree

2 files changed

+38
-6
lines changed

2 files changed

+38
-6
lines changed

.rubocop_todo.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@
99
# Offense count: 1
1010
# Configuration parameters: CountComments.
1111
Metrics/ClassLength:
12-
Max: 108
12+
Max: 135

lib/redis-session-store.rb

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class RedisSessionStore < ActionDispatch::Session::AbstractStore
2323
# * +:on_redis_down:+ - Called with err, env, and SID on Errno::ECONNREFUSED
2424
# * +:on_session_load_error:+ - Called with err and SID on Marshal.load fail
2525
# * +:serializer:+ - Serializer to use on session data, default is :marshal.
26+
# * +:handle_race_conditions:+ Boolean, saving of initial session state
2627
#
2728
# ==== Examples
2829
#
@@ -48,6 +49,7 @@ def initialize(app, options = {})
4849
@on_redis_down = options[:on_redis_down]
4950
@serializer = determine_serializer(options[:serializer])
5051
@on_session_load_error = options[:on_session_load_error]
52+
@handle_race_conditions = options[:handle_race_conditions]
5153
verify_handlers!
5254
end
5355

@@ -92,13 +94,23 @@ def get_session(env, sid)
9294
session = {}
9395
end
9496

95-
[sid, session]
97+
session_data(sid, session)
9698
rescue Errno::ECONNREFUSED, Redis::CannotConnectError => e
9799
on_redis_down.call(e, env, sid) if on_redis_down
98100
[generate_sid, {}]
99101
end
100102
alias find_session get_session
101103

104+
def session_data(sid, session)
105+
if @handle_race_conditions
106+
session_with_initial_state = session.clone
107+
session_with_initial_state['session_initial_state'] = session
108+
[sid, session_with_initial_state]
109+
else
110+
[sid, session]
111+
end
112+
end
113+
102114
def load_session_from_redis(sid)
103115
data = redis.get(prefixed(sid))
104116
begin
@@ -116,10 +128,9 @@ def decode(data)
116128

117129
def set_session(env, sid, session_data, options = nil)
118130
expiry = (options || env.fetch(ENV_SESSION_OPTIONS_KEY))[:expire_after]
119-
if expiry
120-
redis.setex(prefixed(sid), expiry, encode(session_data))
121-
else
122-
redis.set(prefixed(sid), encode(session_data))
131+
updated_session_data = encoded_session_data(sid, session_data)
132+
if updated_session_data
133+
write_session_to_redis sid, expiry, updated_session_data
123134
end
124135
return sid
125136
rescue Errno::ECONNREFUSED, Redis::CannotConnectError => e
@@ -132,6 +143,27 @@ def encode(session_data)
132143
serializer.dump(session_data)
133144
end
134145

146+
def encoded_session_data(sid, session_data)
147+
if @handle_race_conditions
148+
session_initial = session_data.delete 'session_initial_state'
149+
return false if session_initial == session_data
150+
151+
session_current = load_session_from_redis(sid)
152+
if session_current && session_current != session_initial
153+
session_data = session_current.deep_merge session_data
154+
end
155+
end
156+
encode session_data
157+
end
158+
159+
def write_session_to_redis(sid, expiry, session_data)
160+
if expiry
161+
redis.setex prefixed(sid), expiry, session_data
162+
else
163+
redis.set prefixed(sid), session_data
164+
end
165+
end
166+
135167
def destroy_session(env, sid, options)
136168
destroy_session_from_sid(sid, (options || {}).to_hash.merge(env: env))
137169
end

0 commit comments

Comments
 (0)