coderrr

November 14, 2008

Making mixico thread safe

Filed under: c, patch, ruby — Tags: , , — coderrr @ 11:57 pm

There is this cool library by _why called Mixico. Its main purpose (I think) is to get around the issue of instance_eval replacing self, which makes it (instance_eval) kinda sucky to use in DSLs. Example:

def x &blk
  SomeProxyClass.new(self).instance_eval(&blk)
end

@i = 5
x do
  p @i # => NOT 5
end

@i won’t be 5 in the above example because @i will be searched for in SomeProxyClass.new, not in the self in which the block was created, due to the fact instance_eval hijacks self. Mixico helps you get around this.

require 'mixico'

def x &blk
  Module.mix_eval(SomeProxyModule, &blk)
end

@i = 5
x do
  p @i # => 5 !!
end

Mixico will make all the methods in the proxy module available to your block, but it won’t hijack self. Pretty sweet deal! Except it’s not thread safe =[.

Why not? Well essentially what it does is call self.extend SomeProxyModule. Now we could do this without mixico and all its magic. But what mixico does allow us to do is unextend the module after the block is finished. This required some C extensions. So the problem is that extending an object or a class is not a thread local operation. It will affect any thread dealing with that object or class. So say you had two mix_eval’s going on in different threads on the same object at the same time… fail.

So at first I was like, yea that’s too bad. But then I thought of a clever way to fix it. Instead of extending self we dup it and then extend the dup. But now we wan’t to run the block in the context of the duped self, so how do we do that? We call instance_eval on the duped self. But wait, doesn’t instance_eval hijack self and that’s bad? Well yes, but this time we’re hijacking self and setting it to the dup’ed self which is almost exactly the same thing.

But not exactly. The dup’ed self will have different instance var and method tables. So you’ll be able to access all the methods and instance vars of the original but if you were to modify them the changes wouldn’t persist outside the block. So how do we fix this? We change the dup’ed self’s iv_tbl and m_tbl pointers to point to the original self’s tables (C hax). This way any modification of ivars or methods will actually be on the original, even though we have a distinct class hierarchy which includes our temporarily mixed in module.

Here’s the thread safe version of mix_eval:

  def safe_mix_eval mod, &blk
    duped_context = blk.context.dup
    # make sure the singleton class is in existence
    class << duped_context; self; end

    duped_context.redirect_tbls(blk.context)

    duped_context.extend mod
    begin
      m = duped_context.is_a?(Module) ? :class_eval : :instance_eval
      duped_context.send(m, &blk)
    ensure
      duped_context.restore_tbls
      (class << duped_context; self; end).disable_mixin mod
    end
  end

This will even handle the special case of adding methods to a class, which needs class_eval instead of instance_eval. What this still doesn’t handle is if you were to try to do something like this:

class X
  Module.safe_mix_eval Module.new do
    class << self
      def x; :x; end
    end
  end
end
X.x # no method error!

This wouldn’t work because the class << self refers to the singleton class of the dup of X not the singelton class of X itself. Therefore we add the singleton method to the wrong class and it doesn’t persist. I think this case is probably rare, but maybe not. I doubt many people use mixico anyway so whatever :P

Benchmarks currently show the thread safe version being about twice as slow as the original.

You can check out the thread safety test case here. Or you can check out the rest of the code and get the gem at github.com/coderrr/mixico-inline.

Props to http://banisterfiend.wordpress.com/ for working through this fix with me.

6 Comments »

  1. very nice write-up! though im pretty sure you can even correct the pathological case (modifying methods on the singleton of the object) by redirecting the tables for the dup’d singleton too; just hope the singleton doesn’t itself have a singleton :) (but even if it does you can repeat the process :)

    Comment by banisterfiend — November 15, 2008 @ 4:21 am

  2. Nice work!

    Quick question — if the block stores the object somewhere and tries to use it later, it’ll have a very nasty surprise, right?

    Comment by Victor Costan — November 17, 2008 @ 12:34 pm

  3. yea, good point! if you store self and use it later… things will get relaly weird!

    Comment by coderrr — November 17, 2008 @ 1:15 pm

  4. [...] solution to the thread-safety issue was discovered by coderrr (http://coderrr.wordpress.com/2008/11/14/making-mixico-thread-safe/). His solution was to duplicate the binding of the block and perform the mix_eval on the [...]

    Pingback by Dupping Our Way Out of Instance Eval? « Like Dream of Banister Fiend — December 18, 2008 @ 3:14 pm

  5. bssydosirndcltpuwell, hi admin adn people nice forum indeed. how’s life? hope it’s introduce branch ;)

    Comment by intiltwernSpecrem — January 3, 2009 @ 5:44 pm

  6. I’m the only one in this world. Can please someone join me in this life? Or maybe death…

    Comment by anateencody — April 23, 2009 @ 9:35 pm


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Customized Silver is the New Black Theme. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 28 other followers

%d bloggers like this: