@@ -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