<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
		>
<channel>
	<title>Comments on: Race conditions in Rails sessions and how to fix them</title>
	<atom:link href="http://www.paulbutcher.com/2007/05/race-conditions-in-rails-sessions-and-how-to-fix-them/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.paulbutcher.com/2007/05/race-conditions-in-rails-sessions-and-how-to-fix-them/</link>
	<description></description>
	<lastBuildDate>Fri, 03 Feb 2012 16:38:44 +0000</lastBuildDate>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.1.2</generator>
	<item>
		<title>By: paul</title>
		<link>http://www.paulbutcher.com/2007/05/race-conditions-in-rails-sessions-and-how-to-fix-them/comment-page-1/#comment-31473</link>
		<dc:creator>paul</dc:creator>
		<pubDate>Thu, 12 Jan 2012 18:37:50 +0000</pubDate>
		<guid isPermaLink="false">http://www.texperts.com/2007/05/01/race-conditions-in-rails-sessions-and-how-to-fix-them/#comment-31473</guid>
		<description>I&#039;m afraid I can&#039;t be much help, Ben - I&#039;m not aware of this issue being resolved in Rails, but I&#039;ve not been seriously involved in Rails development for a couple of years now, so my knowledge is not as up to date as it might be. I don&#039;t know if there&#039;s a more up to date ticket.</description>
		<content:encoded><![CDATA[<p>I&#8217;m afraid I can&#8217;t be much help, Ben &#8211; I&#8217;m not aware of this issue being resolved in Rails, but I&#8217;ve not been seriously involved in Rails development for a couple of years now, so my knowledge is not as up to date as it might be. I don&#8217;t know if there&#8217;s a more up to date ticket.</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Ben</title>
		<link>http://www.paulbutcher.com/2007/05/race-conditions-in-rails-sessions-and-how-to-fix-them/comment-page-1/#comment-31415</link>
		<dc:creator>Ben</dc:creator>
		<pubDate>Thu, 12 Jan 2012 03:09:09 +0000</pubDate>
		<guid isPermaLink="false">http://www.texperts.com/2007/05/01/race-conditions-in-rails-sessions-and-how-to-fix-them/#comment-31415</guid>
		<description>I know this is an old post, but hoping for some updates as it&#039;s still referenced around the web:
1. Has this issue been resolved in any way in rails core? (I believe it hasn&#039;t)
2. If not, is there a current ticket number available?</description>
		<content:encoded><![CDATA[<p>I know this is an old post, but hoping for some updates as it&#8217;s still referenced around the web:<br />
1. Has this issue been resolved in any way in rails core? (I believe it hasn&#8217;t)<br />
2. If not, is there a current ticket number available?</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Nodeta &#187; Blog Archive &#187; Avoiding Rails session race conditions - now with PostgreSQL</title>
		<link>http://www.paulbutcher.com/2007/05/race-conditions-in-rails-sessions-and-how-to-fix-them/comment-page-1/#comment-17</link>
		<dc:creator>Nodeta &#187; Blog Archive &#187; Avoiding Rails session race conditions - now with PostgreSQL</dc:creator>
		<pubDate>Fri, 27 Mar 2009 19:04:41 +0000</pubDate>
		<guid isPermaLink="false">http://www.texperts.com/2007/05/01/race-conditions-in-rails-sessions-and-how-to-fix-them/#comment-17</guid>
		<description>[...] story short: Ruby on Rails session handling does have some concurrency problems. Especially with AJAX requests this is a very potential [...]</description>
		<content:encoded><![CDATA[<p>[...] story short: Ruby on Rails session handling does have some concurrency problems. Especially with AJAX requests this is a very potential [...]</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Frederick Cheung</title>
		<link>http://www.paulbutcher.com/2007/05/race-conditions-in-rails-sessions-and-how-to-fix-them/comment-page-1/#comment-21</link>
		<dc:creator>Frederick Cheung</dc:creator>
		<pubDate>Wed, 25 Mar 2009 09:53:30 +0000</pubDate>
		<guid isPermaLink="false">http://www.texperts.com/2007/05/01/race-conditions-in-rails-sessions-and-how-to-fix-them/#comment-21</guid>
		<description>The code for this is also on github ( github.com/fcheung ), I&#039;d encourage you to fork the plugin and post your changes there rather than putting them in a blog comment where they probably won&#039;t get much visability.</description>
		<content:encoded><![CDATA[<p>The code for this is also on github ( github.com/fcheung ), I&#8217;d encourage you to fork the plugin and post your changes there rather than putting them in a blog comment where they probably won&#8217;t get much visability.</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: SACK</title>
		<link>http://www.paulbutcher.com/2007/05/race-conditions-in-rails-sessions-and-how-to-fix-them/comment-page-1/#comment-20</link>
		<dc:creator>SACK</dc:creator>
		<pubDate>Wed, 25 Mar 2009 09:39:34 +0000</pubDate>
		<guid isPermaLink="false">http://www.texperts.com/2007/05/01/race-conditions-in-rails-sessions-and-how-to-fix-them/#comment-20</guid>
		<description>The above code snippet solving the problem when the session is saved multiple times during a single request is wrong. So here&#039;s the correct and tested version of it. See comments in code for details.

&lt;code&gt;
require &#039;active_record&#039;
require &#039;cgi&#039;
require &#039;cgi/session&#039;
require &#039;base64&#039;
require &#039;pp&#039;
# +SmartSessionStore+ is a session store that strives to correctly handle session storage in the face of multiple
# concurrent actions accessing the session. It is derived from Stephen Kaes&#039; +SqlSessionStore+, a stripped down,
# optimized for speed version of class +ActiveRecordStore+.

class SmartSessionStore

  # The class to be used for creating, retrieving and updating sessions.
  # Defaults to SmartSessionStore::Session, which is derived from +ActiveRecord::Base+.
  #
  # In order to achieve acceptable performance you should implement
  # your own session class, similar to the one provided for Myqsl.
  #
  # Only functions +find_session+, +create_session+,
  # +update_session+ and +destroy+ are required. See file +mysql_session.rb+.

  cattr_accessor :session_class
  @@session_class = SqlSession

  # Create a new SmartSessionStore instance.
  #
  # +session+ is the session for which this instance is being created.
  #
  # +option+ is currently ignored as no options are recognized.

  def initialize(session, option=nil)
    if @session = @@session_class.find_session(session.session_id)
      # Is this really necessary? Because CGI:Session will call restore method anyway.
      self.data = unmarshalize(@session.data)
    else
      @session = @@session_class.create_session(session.session_id, marshalize({}))
      self.data = {}
    end
  end

  # Update the database and disassociate the session object
  def close
    if @session
      save_session
      @session = nil
    end
  end

  # Delete the current session, disassociate and destroy session object
  def delete
    if @session
      @session.destroy
      @session = nil
    end
  end

  # Restore session data from the session object.
  # The data hash returned by this restore method is stored in the CGI::Session and is used
  # by the application when session properties are set and get within
  # an ActionController::Base instance whenever the brackets operator is used.
  # e.g. session[:username] = &quot;Peter&quot;.
  # This means that this session store must not create a new Hash
  # except here, because otherwise the CGI:Session and this session store
  # use different objects.
  def restore
    if @session
      self.data = unmarshalize(@session.data)
    end
  end

  # Save session data in the session object
  def update
    if @session
      save_session
    end
  end

  private

  def data= data
    @data = data
    if @session &amp;&amp; @session.data
      @original_marshalized_data = @session.data
    else
      @original_marshalized_data = marshalize( {})
    end
    @data
  end

  def unmarshalize(data)
    Marshal.load(Base64.decode64(data))
  end

  def marshalize(data)
    Base64.encode64(Marshal.dump(data))
  end

  # Bugfix by Flexoptix was added in this method:
  # If save is called more than once in a single request (it is already called automatically
  # at the end of request processing by the rails framework and it can be called manually through
  # &quot;session.update()&quot;, the session may not be stored into the database, because smart_session thinks
  # nothing has changed.
  def save_session
    # fo_logger = Log4r::Logger.new(self.class.name.to_s)
    # fo_logger.level = Log4r::DEBUG
    # fo_logger.add(&#039;fo_logger_output&#039;)

    if @original_marshalized_data
      # Bugfix by Flexoptix BEGIN
      # we have to get the information every time not only if @original_data is empty.
      # this is because @original_marshalized_data is update at the end of this method.
      # So if save_session is called more than once than this is necessary.
      # ORIGINAL @original_data &#124;&#124;= unmarshalize @original_marshalized_data
      @original_data = unmarshalize @original_marshalized_data
      # Bugfix by Flexoptix END
    else
      @original_data = {}
    end
    @data &#124;&#124;= {}

    # fo_logger.info(&quot;#{ENV[&#039;SERVER_IDENTIFICATION&#039;]} - session date before merge: #{@data.object_id} #{@data.to_a().join(&#039;, &#039;)}&quot;)
    # fo_logger.info(&quot;#{ENV[&#039;SERVER_IDENTIFICATION&#039;]} - comparing with original: #{@original_data.object_id} #{@original_data.to_a().join(&#039;, &#039;)}&quot;)

    # find out which keys have been deleted
    deleted_keys = @original_data.keys - @data.keys
    changed_keys = []

    # find out which keys have been change
    @data.each {&#124;k,v&#124; changed_keys &lt;&lt; k if Marshal.dump( @original_data[k]) != Marshal.dump( v)}

    if changed_keys.empty? &amp;&amp; deleted_keys.empty?
      # fo_logger.info(&quot;#{ENV[&#039;SERVER_IDENTIFICATION&#039;]} - no session update&quot;)
      return
    end

    SqlSession.transaction do
      fresh_session = @@session_class.find_session(@session.session_id, true)
      # Important for testing:
      # This code block is only execute when the session record in database has changed meanwhile
      # by another concurrent request. So the manual save problem (which has been fixed)
      # only popped up when the there were more than one instance of mongrel.
      if fresh_session &amp;&amp; fresh_session.data != @original_marshalized_data &amp;&amp; fresh_data = unmarshalize(fresh_session.data)
        # fo_logger.info(&quot;#{ENV[&#039;SERVER_IDENTIFICATION&#039;]} - current data: #{fresh_data.to_a().join(&#039;, &#039;)}&quot;)
        deleted_keys.each {&#124;k&#124; fresh_data.delete k}
        changed_keys.each {&#124;k&#124; fresh_data[k] = @data[k]}

        # Bugfix by Flexoptix BEGIN
        # ORIGINAL @data = fresh_data
        # we must use the existing hash which was returned to the CGI::Session when
        # the restore method was called.
        # If we just would use the = operator the @data field
        # would point to a new Hash in memory and CGI::Session would not
        # know anything about it.
        @data.clear()
        @data.merge!(fresh_data)
        # Bugfix by Flexoptix END

        @session = fresh_session
      end
      # Bugfix by Flexoptix BEGIN
      # ORIGINAL: @session.update_session(@original_marshalized_data)
      @original_marshalized_data = marshalize(@data)
      @session.update_session(marshalize(@data)) # @original_marshalized_data
      # Bugfix by Flexoptix END

      # fo_logger.info(&quot;#{ENV[&#039;SERVER_IDENTIFICATION&#039;]} - session data after  merge: #{@data.object_id} #{@data.to_a().join(&#039;, &#039;)}&quot;)
    end

  end
end

__END__

# This software is released under the MIT license
# Copyright (c) 2007 Frederick Cheung
# Copyright (c) 2005,2006 Stefan Kaes

# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# &quot;Software&quot;), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:

# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

&lt;/code&gt;</description>
		<content:encoded><![CDATA[<p>The above code snippet solving the problem when the session is saved multiple times during a single request is wrong. So here&#8217;s the correct and tested version of it. See comments in code for details.</p>
<p><code><br />
require 'active_record'<br />
require 'cgi'<br />
require 'cgi/session'<br />
require 'base64'<br />
require 'pp'<br />
# +SmartSessionStore+ is a session store that strives to correctly handle session storage in the face of multiple<br />
# concurrent actions accessing the session. It is derived from Stephen Kaes' +SqlSessionStore+, a stripped down,<br />
# optimized for speed version of class +ActiveRecordStore+.</p>
<p>class SmartSessionStore</p>
<p>  # The class to be used for creating, retrieving and updating sessions.<br />
  # Defaults to SmartSessionStore::Session, which is derived from +ActiveRecord::Base+.<br />
  #<br />
  # In order to achieve acceptable performance you should implement<br />
  # your own session class, similar to the one provided for Myqsl.<br />
  #<br />
  # Only functions +find_session+, +create_session+,<br />
  # +update_session+ and +destroy+ are required. See file +mysql_session.rb+.</p>
<p>  cattr_accessor :session_class<br />
  @@session_class = SqlSession</p>
<p>  # Create a new SmartSessionStore instance.<br />
  #<br />
  # +session+ is the session for which this instance is being created.<br />
  #<br />
  # +option+ is currently ignored as no options are recognized.</p>
<p>  def initialize(session, option=nil)<br />
    if @session = @@session_class.find_session(session.session_id)<br />
      # Is this really necessary? Because CGI:Session will call restore method anyway.<br />
      self.data = unmarshalize(@session.data)<br />
    else<br />
      @session = @@session_class.create_session(session.session_id, marshalize({}))<br />
      self.data = {}<br />
    end<br />
  end</p>
<p>  # Update the database and disassociate the session object<br />
  def close<br />
    if @session<br />
      save_session<br />
      @session = nil<br />
    end<br />
  end</p>
<p>  # Delete the current session, disassociate and destroy session object<br />
  def delete<br />
    if @session<br />
      @session.destroy<br />
      @session = nil<br />
    end<br />
  end</p>
<p>  # Restore session data from the session object.<br />
  # The data hash returned by this restore method is stored in the CGI::Session and is used<br />
  # by the application when session properties are set and get within<br />
  # an ActionController::Base instance whenever the brackets operator is used.<br />
  # e.g. session[:username] = "Peter".<br />
  # This means that this session store must not create a new Hash<br />
  # except here, because otherwise the CGI:Session and this session store<br />
  # use different objects.<br />
  def restore<br />
    if @session<br />
      self.data = unmarshalize(@session.data)<br />
    end<br />
  end</p>
<p>  # Save session data in the session object<br />
  def update<br />
    if @session<br />
      save_session<br />
    end<br />
  end</p>
<p>  private</p>
<p>  def data= data<br />
    @data = data<br />
    if @session &amp;&amp; @session.data<br />
      @original_marshalized_data = @session.data<br />
    else<br />
      @original_marshalized_data = marshalize( {})<br />
    end<br />
    @data<br />
  end</p>
<p>  def unmarshalize(data)<br />
    Marshal.load(Base64.decode64(data))<br />
  end</p>
<p>  def marshalize(data)<br />
    Base64.encode64(Marshal.dump(data))<br />
  end</p>
<p>  # Bugfix by Flexoptix was added in this method:<br />
  # If save is called more than once in a single request (it is already called automatically<br />
  # at the end of request processing by the rails framework and it can be called manually through<br />
  # "session.update()", the session may not be stored into the database, because smart_session thinks<br />
  # nothing has changed.<br />
  def save_session<br />
    # fo_logger = Log4r::Logger.new(self.class.name.to_s)<br />
    # fo_logger.level = Log4r::DEBUG<br />
    # fo_logger.add('fo_logger_output')</p>
<p>    if @original_marshalized_data<br />
      # Bugfix by Flexoptix BEGIN<br />
      # we have to get the information every time not only if @original_data is empty.<br />
      # this is because @original_marshalized_data is update at the end of this method.<br />
      # So if save_session is called more than once than this is necessary.<br />
      # ORIGINAL @original_data ||= unmarshalize @original_marshalized_data<br />
      @original_data = unmarshalize @original_marshalized_data<br />
      # Bugfix by Flexoptix END<br />
    else<br />
      @original_data = {}<br />
    end<br />
    @data ||= {}</p>
<p>    # fo_logger.info("#{ENV['SERVER_IDENTIFICATION']} - session date before merge: #{@data.object_id} #{@data.to_a().join(', ')}")<br />
    # fo_logger.info("#{ENV['SERVER_IDENTIFICATION']} - comparing with original: #{@original_data.object_id} #{@original_data.to_a().join(', ')}")</p>
<p>    # find out which keys have been deleted<br />
    deleted_keys = @original_data.keys - @data.keys<br />
    changed_keys = []</p>
<p>    # find out which keys have been change<br />
    @data.each {|k,v| changed_keys &lt;&lt; k if Marshal.dump( @original_data[k]) != Marshal.dump( v)}</p>
<p>    if changed_keys.empty? &amp;&amp; deleted_keys.empty?<br />
      # fo_logger.info("#{ENV['SERVER_IDENTIFICATION']} - no session update")<br />
      return<br />
    end</p>
<p>    SqlSession.transaction do<br />
      fresh_session = @@session_class.find_session(@session.session_id, true)<br />
      # Important for testing:<br />
      # This code block is only execute when the session record in database has changed meanwhile<br />
      # by another concurrent request. So the manual save problem (which has been fixed)<br />
      # only popped up when the there were more than one instance of mongrel.<br />
      if fresh_session &amp;&amp; fresh_session.data != @original_marshalized_data &amp;&amp; fresh_data = unmarshalize(fresh_session.data)<br />
        # fo_logger.info("#{ENV['SERVER_IDENTIFICATION']} - current data: #{fresh_data.to_a().join(', ')}")<br />
        deleted_keys.each {|k| fresh_data.delete k}<br />
        changed_keys.each {|k| fresh_data[k] = @data[k]}</p>
<p>        # Bugfix by Flexoptix BEGIN<br />
        # ORIGINAL @data = fresh_data<br />
        # we must use the existing hash which was returned to the CGI::Session when<br />
        # the restore method was called.<br />
        # If we just would use the = operator the @data field<br />
        # would point to a new Hash in memory and CGI::Session would not<br />
        # know anything about it.<br />
        @data.clear()<br />
        @data.merge!(fresh_data)<br />
        # Bugfix by Flexoptix END</p>
<p>        @session = fresh_session<br />
      end<br />
      # Bugfix by Flexoptix BEGIN<br />
      # ORIGINAL: @session.update_session(@original_marshalized_data)<br />
      @original_marshalized_data = marshalize(@data)<br />
      @session.update_session(marshalize(@data)) # @original_marshalized_data<br />
      # Bugfix by Flexoptix END</p>
<p>      # fo_logger.info("#{ENV['SERVER_IDENTIFICATION']} - session data after  merge: #{@data.object_id} #{@data.to_a().join(', ')}")<br />
    end</p>
<p>  end<br />
end</p>
<p>__END__</p>
<p># This software is released under the MIT license<br />
# Copyright (c) 2007 Frederick Cheung<br />
# Copyright (c) 2005,2006 Stefan Kaes</p>
<p># Permission is hereby granted, free of charge, to any person obtaining<br />
# a copy of this software and associated documentation files (the<br />
# "Software"), to deal in the Software without restriction, including<br />
# without limitation the rights to use, copy, modify, merge, publish,<br />
# distribute, sublicense, and/or sell copies of the Software, and to<br />
# permit persons to whom the Software is furnished to do so, subject to<br />
# the following conditions:</p>
<p># The above copyright notice and this permission notice shall be<br />
# included in all copies or substantial portions of the Software.</p>
<p># THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,<br />
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF<br />
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND<br />
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE<br />
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION<br />
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION<br />
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>
<p></code></p>
]]></content:encoded>
	</item>
	<item>
		<title>By: SACK</title>
		<link>http://www.paulbutcher.com/2007/05/race-conditions-in-rails-sessions-and-how-to-fix-them/comment-page-1/#comment-19</link>
		<dc:creator>SACK</dc:creator>
		<pubDate>Fri, 13 Mar 2009 14:59:49 +0000</pubDate>
		<guid isPermaLink="false">http://www.texperts.com/2007/05/01/race-conditions-in-rails-sessions-and-how-to-fix-them/#comment-19</guid>
		<description>Thanks for the short review. The use case is indeed a bit weird. The actual use case in out application is as follows:
- We have an ajax style application. Depending on the user&#039;s actions there may be lots of concurrent requests.
- Nonetheless there are some services which must be called synchronous. This of course must be ensured by the frontend in javascript. But for security reasons we also ensure this with special before and after filters on the server.
The before filter sets a special flag in the session indicating that the service is currently running. The after filter removes this flag from the session.
If the second service is called it first checks whether the first service is still running. If so we throw an exception.

Maybe there are better techniques to implement such a lock.</description>
		<content:encoded><![CDATA[<p>Thanks for the short review. The use case is indeed a bit weird. The actual use case in out application is as follows:<br />
- We have an ajax style application. Depending on the user&#8217;s actions there may be lots of concurrent requests.<br />
- Nonetheless there are some services which must be called synchronous. This of course must be ensured by the frontend in javascript. But for security reasons we also ensure this with special before and after filters on the server.<br />
The before filter sets a special flag in the session indicating that the service is currently running. The after filter removes this flag from the session.<br />
If the second service is called it first checks whether the first service is still running. If so we throw an exception.</p>
<p>Maybe there are better techniques to implement such a lock.</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Frederick Cheung</title>
		<link>http://www.paulbutcher.com/2007/05/race-conditions-in-rails-sessions-and-how-to-fix-them/comment-page-1/#comment-18</link>
		<dc:creator>Frederick Cheung</dc:creator>
		<pubDate>Fri, 13 Mar 2009 14:26:12 +0000</pubDate>
		<guid isPermaLink="false">http://www.texperts.com/2007/05/01/race-conditions-in-rails-sessions-and-how-to-fix-them/#comment-18</guid>
		<description>Looks sane to me (although I personally don&#039;t think of this as a valid use case)</description>
		<content:encoded><![CDATA[<p>Looks sane to me (although I personally don&#8217;t think of this as a valid use case)</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: SACK</title>
		<link>http://www.paulbutcher.com/2007/05/race-conditions-in-rails-sessions-and-how-to-fix-them/comment-page-1/#comment-16</link>
		<dc:creator>SACK</dc:creator>
		<pubDate>Fri, 13 Mar 2009 14:21:42 +0000</pubDate>
		<guid isPermaLink="false">http://www.texperts.com/2007/05/01/race-conditions-in-rails-sessions-and-how-to-fix-them/#comment-16</guid>
		<description>Concerning the change request made by Patrick DiLeonard:

We ran into the same problem. Our application needs to update the session several times during a single request by calling

session.update(). Unfortunatelly - as Frederick Cheung already wrote - this case isn&#039;t considered by the SmartSessionStore.

Thus it may happen that not all updates made to the session are stored to the database.

An example:
- We make a request and the session is acquired from database
- Say there&#039;s the key &quot;Name&quot; in the session data with the value &quot;Max Mustermann&quot;
- Then we change the name to &quot;Lisa Lustig&quot; and call session.update()
- smartSessionStore realizes that the value has changed and thus does an update to the database
- Then our code does funky stuff and realizes that &quot;Lisa Lustig&quot; is not valid and thus reverts the name back to &quot;Max

Mustermann&quot;. Again we call session.update().
- SmartSessionStore compares the initial value of key &quot;name&quot; (which was &quot;Max Mustermann&quot;) with the new value (which is also

&quot;Max Mustermann&quot;). Because the 2 values are equal SmartSessionStore thinks thtat there were no changes and doesn&#039;t perform an

update to the database. Although the actual value stored in the datatabse is &quot;Lisa Lustig&quot;.


To fox this problem we implemented some changes in class smart_session_store.rb in method save_session:

&lt;code&gt;
def save_session
    if @original_marshalized_data
      @original_data &#124;&#124;= unmarshalize @original_marshalized_data
    else
      @original_data = {}
    end
    @data &#124;&#124;= {}

    deleted_keys = @original_data.keys - @data.keys
    changed_keys = []
    @data.each {&#124;k,v&#124; changed_keys &lt;&lt; k if Marshal.dump( @original_data[k]) != Marshal.dump( v)}

    return if changed_keys.empty? &amp;&amp; deleted_keys.empty?

    SqlSession.transaction do
      fresh_session = @@session_class.find_session(@session.session_id, true)
      if fresh_session &amp;&amp; fresh_session.data != @original_marshalized_data &amp;&amp; fresh_data = unmarshalize(fresh_session.data)
        deleted_keys.each {&#124;k&#124; fresh_data.delete k}
        changed_keys.each {&#124;k&#124; fresh_data[k] = @data[k]}
        @data = fresh_data
        @session = fresh_session
      end
      # Bugfix BEGIN
      @original_marshalized_data = marshalize(@data) # NEW LINE
      @session.update_session(@original_marshalized_data) # ORIGINAL: @session.update_session(marshalize(@data))
      # Bugfix END
    end
  end
&lt;/code&gt;

The lines between &quot;# Bugfix BEGIN&quot; and &quot;# Bugfix END&quot; are the ones we changed. We simply update the instance variable

@original_marshalized_data, so that the next time you call session.update() in the same request the method is capable of

evaluation which keys have changed and which not and perform an update if necessary.

I tested the code a bit and it seemed to work properly. I would appreciate if anyone who is more experienced with the

SmartSessionStore can review this code a bit.

Thankx.</description>
		<content:encoded><![CDATA[<p>Concerning the change request made by Patrick DiLeonard:</p>
<p>We ran into the same problem. Our application needs to update the session several times during a single request by calling</p>
<p>session.update(). Unfortunatelly &#8211; as Frederick Cheung already wrote &#8211; this case isn&#8217;t considered by the SmartSessionStore.</p>
<p>Thus it may happen that not all updates made to the session are stored to the database.</p>
<p>An example:<br />
- We make a request and the session is acquired from database<br />
- Say there&#8217;s the key &#8220;Name&#8221; in the session data with the value &#8220;Max Mustermann&#8221;<br />
- Then we change the name to &#8220;Lisa Lustig&#8221; and call session.update()<br />
- smartSessionStore realizes that the value has changed and thus does an update to the database<br />
- Then our code does funky stuff and realizes that &#8220;Lisa Lustig&#8221; is not valid and thus reverts the name back to &#8220;Max</p>
<p>Mustermann&#8221;. Again we call session.update().<br />
- SmartSessionStore compares the initial value of key &#8220;name&#8221; (which was &#8220;Max Mustermann&#8221;) with the new value (which is also</p>
<p>&#8220;Max Mustermann&#8221;). Because the 2 values are equal SmartSessionStore thinks thtat there were no changes and doesn&#8217;t perform an</p>
<p>update to the database. Although the actual value stored in the datatabse is &#8220;Lisa Lustig&#8221;.</p>
<p>To fox this problem we implemented some changes in class smart_session_store.rb in method save_session:</p>
<p><code><br />
def save_session<br />
    if @original_marshalized_data<br />
      @original_data ||= unmarshalize @original_marshalized_data<br />
    else<br />
      @original_data = {}<br />
    end<br />
    @data ||= {}</p>
<p>    deleted_keys = @original_data.keys - @data.keys<br />
    changed_keys = []<br />
    @data.each {|k,v| changed_keys &lt;&lt; k if Marshal.dump( @original_data[k]) != Marshal.dump( v)}</p>
<p>    return if changed_keys.empty? &amp;&amp; deleted_keys.empty?</p>
<p>    SqlSession.transaction do<br />
      fresh_session = @@session_class.find_session(@session.session_id, true)<br />
      if fresh_session &amp;&amp; fresh_session.data != @original_marshalized_data &amp;&amp; fresh_data = unmarshalize(fresh_session.data)<br />
        deleted_keys.each {|k| fresh_data.delete k}<br />
        changed_keys.each {|k| fresh_data[k] = @data[k]}<br />
        @data = fresh_data<br />
        @session = fresh_session<br />
      end<br />
      # Bugfix BEGIN<br />
      @original_marshalized_data = marshalize(@data) # NEW LINE<br />
      @session.update_session(@original_marshalized_data) # ORIGINAL: @session.update_session(marshalize(@data))<br />
      # Bugfix END<br />
    end<br />
  end<br />
</code></p>
<p>The lines between &#8220;# Bugfix BEGIN&#8221; and &#8220;# Bugfix END&#8221; are the ones we changed. We simply update the instance variable</p>
<p>@original_marshalized_data, so that the next time you call session.update() in the same request the method is capable of</p>
<p>evaluation which keys have changed and which not and perform an update if necessary.</p>
<p>I tested the code a bit and it seemed to work properly. I would appreciate if anyone who is more experienced with the</p>
<p>SmartSessionStore can review this code a bit.</p>
<p>Thankx.</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Paul Butcher</title>
		<link>http://www.paulbutcher.com/2007/05/race-conditions-in-rails-sessions-and-how-to-fix-them/comment-page-1/#comment-15</link>
		<dc:creator>Paul Butcher</dc:creator>
		<pubDate>Wed, 26 Mar 2008 13:06:15 +0000</pubDate>
		<guid isPermaLink="false">http://www.texperts.com/2007/05/01/race-conditions-in-rails-sessions-and-how-to-fix-them/#comment-15</guid>
		<description>We submitted this to the Rails Trac site when we originally wrote the article (over a year ago). Here&#039;s the ticket:

http://dev.rubyonrails.org/ticket/8256

And also posted it to the Rails core mailing list:

http://www.ruby-forum.com/topic/106919

As you know, what makes it into the core and what doesn&#039;t largely depends upon what does and what does not receive support from the community. As you can see, we didn&#039;t get a great deal of response.

I agree with you that it would be better if Rails core included this fix, but the trick is gaining the attention of other Rails developers.

Can I suggest that if you feel strongly about this, it might be worth raising it on the Rails mailing list? If someone other than the original authors raises it, it will demonstrate wider support and other people may add their voice?

PS - one additional point. The default session store in the current version of Rails is the cookie store, in which our fix is completely impossible :-(</description>
		<content:encoded><![CDATA[<p>We submitted this to the Rails Trac site when we originally wrote the article (over a year ago). Here&#8217;s the ticket:</p>
<p><a href="http://dev.rubyonrails.org/ticket/8256" rel="nofollow">http://dev.rubyonrails.org/ticket/8256</a></p>
<p>And also posted it to the Rails core mailing list:</p>
<p><a href="http://www.ruby-forum.com/topic/106919" rel="nofollow">http://www.ruby-forum.com/topic/106919</a></p>
<p>As you know, what makes it into the core and what doesn&#8217;t largely depends upon what does and what does not receive support from the community. As you can see, we didn&#8217;t get a great deal of response.</p>
<p>I agree with you that it would be better if Rails core included this fix, but the trick is gaining the attention of other Rails developers.</p>
<p>Can I suggest that if you feel strongly about this, it might be worth raising it on the Rails mailing list? If someone other than the original authors raises it, it will demonstrate wider support and other people may add their voice?</p>
<p>PS &#8211; one additional point. The default session store in the current version of Rails is the cookie store, in which our fix is completely impossible <img src='http://www.paulbutcher.com/wp-includes/images/smilies/icon_sad.gif' alt=':-(' class='wp-smiley' /> </p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Ralph</title>
		<link>http://www.paulbutcher.com/2007/05/race-conditions-in-rails-sessions-and-how-to-fix-them/comment-page-1/#comment-14</link>
		<dc:creator>Ralph</dc:creator>
		<pubDate>Wed, 26 Mar 2008 12:49:33 +0000</pubDate>
		<guid isPermaLink="false">http://www.texperts.com/2007/05/01/race-conditions-in-rails-sessions-and-how-to-fix-them/#comment-14</guid>
		<description>This should be pushed in rails code as the default implementation, or at least default behaviour. Here is why:
- session store should just work, it is the very basement of stateful web
- race conditions are often awfully difficult to spot
- a lot of web developers that are not engineer could just not cope with such problems

Please push your work, or at least your ideas, into rails.</description>
		<content:encoded><![CDATA[<p>This should be pushed in rails code as the default implementation, or at least default behaviour. Here is why:<br />
- session store should just work, it is the very basement of stateful web<br />
- race conditions are often awfully difficult to spot<br />
- a lot of web developers that are not engineer could just not cope with such problems</p>
<p>Please push your work, or at least your ideas, into rails.</p>
]]></content:encoded>
	</item>
</channel>
</rss>

