coderrr

December 2, 2008

HTMLEntities race condition

Filed under: bug, patch, ruby — Tags: , , — coderrr @ 8:37 pm

Ever ran into this error?

/usr/lib/ruby/gems/1.8/gems/htmlentities-4.0.0/lib/htmlentities.rb:110:in `map': uninitialized constant HTMLEntities::MAPPINGS (NameError)

or this?

/usr/lib/ruby/gems/1.8/gems/htmlentities-4.0.0/lib/htmlentities.rb:141:in `reverse_map': undefined method `invert' for nil:NilClass (NoMethodError)

Well you might have if you’re using the HTMLEntities gem in threaded ruby code. And if you are but haven’t gotten one of those errors, they might just be waiting for the stars to align correctly before they manifest themselves.

The errors are due to a race condition in the code:

  def map
    @map ||= (require "htmlentities/#{@flavor}"; HTMLEntities::MAPPINGS[@flavor])
  end

The issue arises in the following scenario:
Thread1 calls require and begins processing the required file, thread2 calls require which returns immediately because ruby has already marked the file as required even though it hasn’t finished loading. So then when thread2 tries to access HTMLEntities::MAPPINGS it is either undefined or empty because thread1 hasn’t actually finished evaling the required file yet.

Quick monkeypatch to fix the issue:

require 'rubygems'
require 'htmlentities'
class HTMLEntities
  alias_method :unsafe_map, :map
  @@map_guard = Mutex.new
  def map
    @@map_guard.synchronize { unsafe_map }
  end
end

You could also require all the htmlentities/* files at the beginning of your application but monkeypatching is so much more fun :)

4 Comments »

  1. As the author of HTMLEntities, it’s always nice to see people using my code, even if it is as a bug on a blog! I haven’t run into that problem – maybe I’m just lucky – but it looks like a genuine bug to me. I’ll roll your fix into the next release.

    Comment by Paul Battley — December 2, 2008 @ 10:14 pm

  2. thanks man! :)

    Comment by coderrr — December 3, 2008 @ 4:04 am

  3. require is generally not thread safe. Any code that uses it in a threaded environment must make sure it is synchronized. May be one can just monkey patch the require method and synchronize it for every call

    Comment by oldmoe — December 3, 2008 @ 1:03 pm

  4. @oldmoe: yea i’d been thinking about that, of course if you just synchronized every require call with the same mutex you’d deadlock on nested requires

    … but if you kept a hash of mutexes keyed by expanded file path which you used to synchronize, that might “just work”

    not sure if there are any reasons not to do this…

    Comment by coderrr — December 4, 2008 @ 6:16 pm


RSS feed for comments on this post. TrackBack URI

Leave a comment

Blog at WordPress.com.