On Windows, Rails bombs out with a marshal data too short (ArgumentError) message after a non-eventful piece of code. After that, Rails will exit every request with an error until the session data in /tmp gets deleted.
Rails stores its session state using Ruby’s PStore class in ruby/lib/pstore.rb. Because the session data is stored as binary data, Windows needs the IO streams for accessing the session files to be set to binary mode (using the stream.binmode method).
Unfortunately Ruby’s PStore doesn’t use .binmode on the streams it uses, which causes the session files to become corrupted.
To fix this I had to patch ruby/lib/pstore.rb. Matz is after this, so hopefully this will be incorporated in a future release of Ruby.
--- pstore.rb 2004-10-05 11:52:51.965332800 +0200
+++ pstore.rb.unpatched 2004-07-03 04:38:31.000000000 +0200
@@ -99,13 +99,11 @@
content = nil
unless read_only
file = File.open(@filename, File::RDWR | File::CREAT)
- file.binmode
file.flock(File::LOCK_EX)
commit_new(file) if <a href="http://wiki.rubyonrails.org/rails/pages/FileTest" class="existingWikiWord">FileTest</a>.exist?(new_file)
content = file.read()
else
file = File.open(@filename, File::RDONLY)
- file.binmode
file.flock(File::LOCK_SH)
content = (File.read(new_file) rescue file.read())
end
@@ -134,7 +132,6 @@
content = dump(@table)
if !md5 || size != content.size || md5 != Digest::MD5.digest(content)
File.open(tmp_file, "w") {|t|
- t.binmode
t.write(content)
}
File.rename(tmp_file, new_file)
@@ -169,7 +166,6 @@
f.rewind
new_file = @filename + ".new"
File.open(new_file) do |nf|
- nf.binmode
<span class="newWikiWord">FileUtils<a href="http://wiki.rubyonrails.org/rails/pages/FileUtils">?</a></span>.copy_stream(nf, f)
end
File.unlink(new_file)
Relevant lines are those containing a call to File.open.
#
# How to use:
#
# db = PStore.new("/tmp/foo")
# db.transaction do
# p db.roots
# ary = db["root"] = [1,2,3,4]
# ary[0] = [1,1.5]
# end
# db.transaction do
# p db["root"]
# end
require "fileutils"
require "digest/md5"
class PStore
class Error < <span class="newWikiWord">StandardError<a href="http://wiki.rubyonrails.org/rails/pages/StandardError">?</a></span>
end
def initialize(file)
dir = File::dirname(file)
unless File::directory? dir
raise PStore::Error, format("directory %s does not exist", dir)
end
if File::exist? file and not File::readable? file
raise PStore::Error, format("file %s not readable", file)
end
@transaction = false
@filename = file
@abort = false
end
def in_transaction
raise PStore::Error, "not in transaction" unless @transaction
end
def in_transaction_wr()
in_transaction()
raise PStore::Error, "in read-only transaction" if @rdonly
end
private :in_transaction, :in_transaction_wr
def [](name)
in_transaction
@table[name]
end
def fetch(name, default=PStore::Error)
unless @table.key? name
if default==PStore::Error
raise PStore::Error, format("undefined root name `%s'", name)
else
default
end
end
self[name]
end
def []=(name, value)
in_transaction_wr()
@table[name] = value
end
def delete(name)
in_transaction_wr()
@table.delete name
end
def roots
in_transaction
@table.keys
end
def root?(name)
in_transaction
@table.key? name
end
def path
@filename
end
def commit
in_transaction
@abort = false
throw :pstore_abort_transaction
end
def abort
in_transaction
@abort = true
throw :pstore_abort_transaction
end
def transaction(read_only=false)
raise PStore::Error, "nested transaction" if @transaction
begin
@rdonly = read_only
@abort = false
@transaction = true
value = nil
new_file = @filename + ".new"
content = nil
unless read_only
file = File.open(@filename, File::RDWR | File::CREAT)
file.binmode
file.flock(File::LOCK_EX)
commit_new(file) if <a href="http://wiki.rubyonrails.org/rails/pages/FileTest" class="existingWikiWord">FileTest</a>.exist?(new_file)
content = file.read()
else
file = File.open(@filename, File::RDONLY)
file.binmode
file.flock(File::LOCK_SH)
content = (File.read(new_file) rescue file.read())
end
if content != ""
@table = load(content)
if !read_only
size = content.size
md5 = Digest::MD5.digest(content)
end
else
@table = {}
end
content = nil # unreference huge data
begin
catch(:pstore_abort_transaction) do
value = yield(self)
end
rescue Exception
@abort = true
raise
ensure
if !read_only and !@abort
tmp_file = @filename + ".tmp"
content = dump(@table)
if !md5 || size != content.size || md5 != Digest::MD5.digest(content)
File.open(tmp_file, "w") {|t|
t.binmode
t.write(content)
}
File.rename(tmp_file, new_file)
commit_new(file)
end
content = nil # unreference huge data
end
end
ensure
@table = nil
@transaction = false
file.close if file
end
value
end
def dump(table)
Marshal::dump(table)
end
def load(content)
Marshal::load(content)
end
def load_file(file)
Marshal::load(file)
end
private
def commit_new(f)
f.truncate(0)
f.rewind
new_file = @filename + ".new"
File.open(new_file) do |nf|
nf.binmode
<span class="newWikiWord">FileUtils<a href="http://wiki.rubyonrails.org/rails/pages/FileUtils">?</a></span>.copy_stream(nf, f)
end
File.unlink(new_file)
end
end
if __FILE__ == $0
db = PStore.new("/tmp/foo")
db.transaction do
p db.roots
ary = db["root"] = [1,2,3,4]
ary[1] = [1,1.5]
end
1000.times do
db.transaction do
db["root"][0] += 1
p db["root"][0]
end
end
db.transaction(true) do
p db["root"]
end
end
—
I haven’t applied this patch yet, however I did find that I could repeat the results with regularity for my application. I created an authentication module based on the “Ruby on Rails” book and modified it to use SHA256, a salt, and an active/disabled state flag. During testing of the module I noticed that after happily running along logging in and out, deleting users, etc it suddenly failed. The only value that is being stored in the session was the user id (session[:user_id] = user.id). Anytime a user with an id of 21 logged in all attempts to access the application would be meet with a 500 series error. Switching to ActiveRecord sessions appears to have solved my problem for now, haven’t looked into it further.
—
I’m in the same situation as the above anonymous post. My session data is a bit more complex but I’m getting the same results when accessing user id 21. Will try switching to ActiveRecord sessions.
—
Same problem here. Application fails to respond after setting session[:user_id] = user.id when user.id = 21.
—-
I report the same problem.
ynw
—-
2007-07-20 Same problem here. Currently looking into it.
—-