<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	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/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Stochastic Bytes</title>
	<atom:link href="http://blog.stochasticbytes.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.stochasticbytes.com</link>
	<description>Just another WordPress site</description>
	<lastBuildDate>Tue, 26 Jul 2011 22:06:15 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>Fibering Rail&#8217;s ConnectionPool aka EventMachine+Fibers is still complicated</title>
		<link>http://blog.stochasticbytes.com/2011/07/fibering-rails-connectionpool-aka-eventmachinefibers-is-still-complicated/</link>
		<comments>http://blog.stochasticbytes.com/2011/07/fibering-rails-connectionpool-aka-eventmachinefibers-is-still-complicated/#comments</comments>
		<pubDate>Tue, 26 Jul 2011 21:15:31 +0000</pubDate>
		<dc:creator>Christopher J. Bottaro</dc:creator>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[eventmachine]]></category>
		<category><![CDATA[fibers]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://blog.stochasticbytes.com/?p=230</guid>
		<description><![CDATA[One of the arguments I frequently hear against threads is that the synchronization problems are a pain in the ass to deal with. They are hard to reproduce, hard to debug, blah blah, etc etc. Well it turns out, I am kinda tired of dealing with broken threads in Ruby and also threadsaftey issues in [...]]]></description>
			<content:encoded><![CDATA[<p>One of the arguments I frequently hear against threads is that the synchronization problems are a pain in the ass to deal with.  They are hard to reproduce, hard to debug, blah blah, etc etc.</p>

<p>Well it turns out, I <em>am</em> kinda tired of dealing with <a href="/2011/01/rubys-threaderror-deadlock-recursive-locking-bug/">broken threads in Ruby</a> and also <a href="/2011/01/thread-safety-with-rails-cache-and-memcache-client/">threadsaftey issues in gems</a> which I don&#8217;t have control over.</p>

<p>So I&#8217;ve been playing around with EventMachine+Fibers and came across the <a href="https://github.com/mperham/em_postgresql">em_postgresql</a> gem by <a href="http://twitter.com/#!/mperham">Mike Perham</a> (incidentally, an old coworker of mine).  Sweet!  Mike has done <em>a lot</em> of the initial leg work for making the EM+Fibers paradigm usable in real world Ruby/Rails applications, thanks Mike!</p>

<p>We&#8217;ll be using his <code>em_postgresql</code> gem as a case study to show how some of the threading synchronization complexities are still present when using EventMachine+Fibers.  Let&#8217;s get started.</p>

<p>So Rails uses a database connection pool that assumes you&#8217;re running in a threaded environment.  Part of the <code>em_postgresql</code> gem is to hack <code>ActiveRecord::ConnectionAdapters::ConnectionPool</code> to use fibers instead of threads (to be &#8220;fiber aware&#8221; as is sometimes said).  Well, unfortunately it doesn&#8217;t work, as this code snippet demonstrates:</p>

<pre class="brush: ruby; title: ;">
gem &quot;postgres-pr&quot;
gem &quot;em_postgresql&quot;
require &quot;eventmachine&quot;
require &quot;fiber&quot;
require &quot;active_record&quot;
require &quot;benchmark&quot;

ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.establish_connection :adapter  =&gt; &quot;em_postgresql&quot;,
                                        :port     =&gt; 5432,
                                        :pool     =&gt; 2,
                                        :username =&gt; &quot;cjbottaro&quot;,
                                        :host     =&gt; &quot;localhost&quot;,
                                        :database =&gt; &quot;test&quot;,
                                        :timeout  =&gt; 3

EM.run do
  Fiber.new do
    fibers = []
    time = Benchmark.realtime do
      fibers &lt;&lt; Fiber.new{ ActiveRecord::Base.connection.execute &quot;select pg_sleep(1)&quot; }.tap{ |fiber| fiber.resume }
      fibers &lt;&lt; Fiber.new{ ActiveRecord::Base.connection.execute &quot;select pg_sleep(1)&quot; }.tap{ |fiber| fiber.resume }
      fibers &lt;&lt; Fiber.new{ ActiveRecord::Base.connection.execute &quot;select pg_sleep(1)&quot; }.tap{ |fiber| fiber.resume }
      fibers.each do |fiber|
        while fiber.alive?
          current_fiber = Fiber.current
          EM.next_tick{ current_fiber.resume }
          Fiber.yield
        end
      end
    end
    puts time
    EM.stop
  end.resume
end
</pre>

<p>We specify our our pool size to be 2 and we spawn 3 fibers.  We expect that only two connections to the database are made.  We also expect that the first two SQL statements run in parallel, then the third runs after they are done, giving us a total running time of ~2 seconds.</p>

<p>This is what we get instead:</p>

<pre class="brush: plain; title: ;">
$ ruby em_postgres.rb 
Connecting to localhost:5432
Connecting to localhost:5432
Connecting to localhost:5432
  SQL (0.6ms)   SET client_min_messages TO 'panic'
  SQL (1.0ms)   SET client_min_messages TO 'panic'
  SQL (1.1ms)   SET standard_conforming_strings = on
  SQL (0.4ms)   SET client_min_messages TO 'notice'
  SQL (1.3ms)   SET standard_conforming_strings = on
  SQL (0.8ms)   SET client_min_messages TO 'panic'
  SQL (1.0ms)   SET client_min_messages TO 'notice'
  SQL (1.0ms)   SET standard_conforming_strings = on
  SQL (0.5ms)   SET client_min_messages TO 'notice'
  SQL (1001.1ms)   select pg_sleep(1)
  SQL (1001.0ms)   select pg_sleep(1)
  SQL (1000.7ms)   select pg_sleep(1)
1.0179660320281982
</pre>

<p>Three connections are made and all 3 statements run in parallel giving us a running time of ~1 second.  What went wrong?</p>

<p>First let&#8217;s look at <code>ConnectionPool#checkout</code>.</p>

<pre class="brush: ruby; title: ;">
      def checkout
        # Checkout an available connection
        @connection_mutex.synchronize do
          loop do
            conn = if @checked_out.size &lt; @connections.size
                     checkout_existing_connection
                   elsif @connections.size &lt; @size
                     checkout_new_connection
                   end
            return conn if conn
            # No connections available; wait for one
            if @queue.wait(@timeout)
              next
            else
              # try looting dead threads
              clear_stale_cached_connections!
              if @size == @checked_out.size
                raise ConnectionTimeoutError, &quot;could not obtain a database connection#{&quot; within #{@timeout} seconds&quot; if @timeout}.  The max pool size is currently #{@size}; consider increasing it.&quot;
              end
            end
          end
        end
      end
</pre>

<p><code>em_postgresql</code> hacks <code>@connection_mutex.synchronize</code> to do nothing.  I think the rational is that we don&#8217;t have to worry about synchronization problems (like when using threads) because we won&#8217;t be preempted unless we explicitly say to in the code (by calling <code>Fiber.yield</code>).</p>

<p>The problem is that we are unknowingly calling methods that deep down eventually call <code>Fiber.yield</code>.</p>

<p>Considering our example case, when we call <code>checkout</code>, the condition <code>elsif @connections.size &lt; @size</code> is met and we call <code>checkout_new_connection</code> which is defined as follows:</p>

<pre class="brush: ruby; title: ;">
      def checkout_new_connection
        c = new_connection
        @connections &lt;&lt; c
        checkout_and_verify(c)
      end
</pre>

<p><code>Fiber.yield</code> is called not once by <em>twice</em> in that unsuspecting bit of code.  Remember that the whole point of <code>em_postgresql</code> is to allow network IO to occur in parallel.  Creating a new connection to the database falls under that category, so <code>new_connection</code> is calling <code>Fiber.yield</code>&#8230; and the key thing to notice is that the fiber is yielding <em>before</em> <code>@connections</code> is modified.  Thus our next fiber will call <code>checkout</code> and hit that same condition as before <code>elsif @connections.size &lt; @size</code> and end up in <code>new_connection</code> where the same thing will happen again with the 3rd fiber.  And thus our connection pool&#8217;s purpose is defeated.</p>

<p>The second time <code>Fiber.yield</code> is called in that code is in <code>checkout_and_verify(c)</code> which eventually calls <code>#verify!</code> on the connection.  <code>em_postgresql</code> defines <code>#verify!</code> to issue a <code>SELECT 1</code> statement to the database, which is network IO, thus <code>Fiber.yield</code> is called.  <code>em_postgres</code> caught the issue with this and overwrote <code>checkout_and_verify(c)</code> as follows:</p>

<pre class="brush: ruby; title: ;">
      def checkout_and_verify(c)
        @checked_out &lt;&lt; c
        c.run_callbacks :checkout
        c.verify!
        c
      end
    end
</pre>

<p>It differs from the original in that <code>@checked_out</code> is modified before calling <code>c.verify!</code> (which yields the fiber).  Thus any fibers that enter <code>checkout</code> will see the new size of <code>@checked_out</code> when evaluating the <code>if</code> statements.</p>

<p>This same strategy needs to be applied to <code>checkout_new_connection</code> and <code>@connections</code>.  The problem lies in that <code>@connections</code> is a list of connections and the act of simply creating a connection yields the fiber, so it&#8217;s impossible to modify <code>@connections</code> before yielding the fiber.</p>

<p>I&#8217;ll leave it as an exercise for the reader to come up with a working solution (it&#8217;s not too hard).</p>

<p>In conclusion, even without random preempting, as is the case with cooperative concurrency, it is still somewhat difficult to manage synchronization issues&#8230; at least <em>I</em> think so.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.stochasticbytes.com/2011/07/fibering-rails-connectionpool-aka-eventmachinefibers-is-still-complicated/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Ruby&#8217;s ThreadError: deadlock; recursive locking bug</title>
		<link>http://blog.stochasticbytes.com/2011/01/rubys-threaderror-deadlock-recursive-locking-bug/</link>
		<comments>http://blog.stochasticbytes.com/2011/01/rubys-threaderror-deadlock-recursive-locking-bug/#comments</comments>
		<pubDate>Fri, 14 Jan 2011 05:11:00 +0000</pubDate>
		<dc:creator>Christopher J. Bottaro</dc:creator>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[deadlock]]></category>
		<category><![CDATA[mutex]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[thread]]></category>
		<category><![CDATA[timeout]]></category>

		<guid isPermaLink="false">http://blog.stochasticbytes.com/?p=212</guid>
		<description><![CDATA[Threading in Ruby as been busted for a long time. Back in 1.8.7, I actually got segfaults in threaded code. Thankfully, those went away in 1.9, but now I&#8217;ve come across a new problem which is almost as bad, save for that I can actually do something about it. The problem happens when a thread [...]]]></description>
			<content:encoded><![CDATA[<p>Threading in Ruby as been busted for a long time.  Back in 1.8.7, I actually got segfaults in threaded code.  Thankfully, those went away in 1.9, but now I&#8217;ve come across a new problem which is almost as bad, save for that I can actually do something about it.</p>

<p>The problem happens when a thread locks a Mutex (or Monitor) inside a timeout block and the timeout expires before the thread can unlock it.</p>

<pre class="brush: ruby; title: ;">
require &quot;thread&quot;
require &quot;timeout&quot;

N = 10
M = 10
T = 0.1

lock = Mutex.new
func = Proc.new do
  begin
    Timeout.timeout(T){ lock.synchronize{ sleep } }
  rescue Timeout::Error
    nil
  end
end

threads = N.times.collect{ Thread.new{ M.times{ func.call } } }
threads.each{ |thread| thread.join }

puts &quot;no deadlocks, yay!&quot;
</pre>

<p>The bug doesn&#8217;t happen very frequently; you may even have to run that snippet a few times before you see it happen, but my threaded asynchronous task processing daemon has high enough throughput in our production environment to where we were seeing the problem very frequently.</p>

<p>It wouldn&#8217;t be so bad if the mutex could get out of its bad state; what&#8217;s a failed task here and there out of thousands?  Guess it depends on the tasks&#8230; ;)  Unfortunately, once the mutex is in the bad state, all subsequent tasks run on that thread will fail.</p>

<p>There is hope though!  This disgusting little hack completely alleviates the problem&#8230;</p>

<pre class="brush: ruby; title: ;">
require &quot;thread&quot;
class Mutex
  def lock_with_hack
    lock_without_hack
  rescue ThreadError =&gt; e
    if e.message != &quot;deadlock; recursive locking&quot;
      raise
    else
      unlock
      lock_without_hack
    end
  end
  alias_method :lock_without_hack, :lock
  alias_method :lock, :lock_with_hack
end
</pre>

<p>Now before you go off and include that snippet in your code, be aware that it effectively turns Mutex into Monitor (i.e. allows recursive locking).  In our use case (and I think most), that is fine though.</p>

<p><a href="/wp-content/files/deadlock.rb">Here is a little Ruby script</a> you can run on the command line that demonstrates the bug and also shows how the hack fixes it.  Run like so:</p>

<pre class="brush: plain; title: ;">
ruby deadlock.rb                   # demonstrates bug
ruby deadlock.rb --with-mutex-hack # demonstrates fix
</pre>

<p>I&#8217;ve reported <a href="http://redmine.ruby-lang.org/issues/show/4266">the bug on Ruby&#8217;s issue tracker</a> where it seems to be getting some attention.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.stochasticbytes.com/2011/01/rubys-threaderror-deadlock-recursive-locking-bug/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Thread safety with Rails.cache and memcache-client</title>
		<link>http://blog.stochasticbytes.com/2011/01/thread-safety-with-rails-cache-and-memcache-client/</link>
		<comments>http://blog.stochasticbytes.com/2011/01/thread-safety-with-rails-cache-and-memcache-client/#comments</comments>
		<pubDate>Fri, 07 Jan 2011 19:18:25 +0000</pubDate>
		<dc:creator>Christopher J. Bottaro</dc:creator>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[caching]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[threads]]></category>

		<guid isPermaLink="false">http://blog.stochasticbytes.com/?p=198</guid>
		<description><![CDATA[Either Rails.cache or memcache-client (or the combination of the two) is not thread safe. I was seeing symptoms like the wrong data being fetched for a given key (getting hash when expecting an array and vice versa). What&#8217;s worse is that some data even got written to the wrong keys. At first I was going [...]]]></description>
			<content:encoded><![CDATA[<p>Either <code>Rails.cache</code> or memcache-client (or the combination of the two) is not thread safe.  I was seeing symptoms like the wrong data being fetched for a given key (getting hash when expecting an array and vice versa).  What&#8217;s worse is that some data even got written to the wrong keys.</p>

<p>At first I was going to make a SynchronizedMemCacheStore in the same spirit as SynchronizedMemoryStore in ActiveSupport, but we debated over whether a single lock would become a bottleneck.  Our threaded asynchronous task processor isn&#8217;t running nearly at capacity, but maybe it will someday in the future.</p>

<p>So the fix was to override <code>Rails.cache</code> to use thread local variables.</p>

<pre class="brush: ruby; title: ;">
# inside Rails::Initializer.run in environment.rb
config.after_initialize{ require &quot;threadsafe_rails_cache&quot; }

# threadsafe_rails_cache.rb
module Rails
  def self.cache
    Thread.current[&quot;Rails.cache&quot;] ||= begin
      ActiveSupport::Cache.lookup_store(configuration.cache_store).tap do |cache|
        cache.logger = Rails.logger
      end
    end
  end
end
</pre>

<p>Some ramifications of this&#8230;</p>

<ul>
<li>RAILS_CACHE is still defined internally by Rails.  Obviously you shouldn&#8217;t use it and use <code>Rails.cache</code> instead.  I&#8217;m not sure if Rails uses RAILS_CACHE internally though.</li>
<li>You&#8217;re going to have a lot more connections open to Memcached.</li>
</ul>

<p>Here&#8217;s a nifty graph to show that the fix worked.  Red indicates failed tasks, and green indicates successes.  The fix was deployed around 12:00.</p>

<p><img src="/wp-content/uploads/2011/01/throughput.png" alt="Task Throughput" /></p>

<p>Versions</p>

<ul>
<li>Ruby 1.9.2</li>
<li>Rails 2.3.10</li>
<li>memcache-client 1.8.5</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://blog.stochasticbytes.com/2011/01/thread-safety-with-rails-cache-and-memcache-client/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>AFP Linux server</title>
		<link>http://blog.stochasticbytes.com/2010/12/afp-linux-server/</link>
		<comments>http://blog.stochasticbytes.com/2010/12/afp-linux-server/#comments</comments>
		<pubDate>Sat, 18 Dec 2010 02:14:48 +0000</pubDate>
		<dc:creator>Christopher J. Bottaro</dc:creator>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[AFP]]></category>
		<category><![CDATA[Avahi]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[Netatalk]]></category>
		<category><![CDATA[OS X]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://blog.stochasticbytes.com/?p=150</guid>
		<description><![CDATA[This is a guide on how to mimic an OS X file server using Linux and the open source implementation of AFP, Netatalk. These instructions are for Ubuntu Server 10.04, but should translate pretty easily to other distros. I have a Linux NAS, but all my other computers are Macs. NFS is simple, but buggy. [...]]]></description>
			<content:encoded><![CDATA[<p>This is a guide on how to mimic an OS X file server using Linux and the open source implementation of AFP, Netatalk.  These instructions are for Ubuntu Server 10.04, but should translate pretty easily to other distros.</p>

<p>I have a Linux NAS, but all my other computers are Macs.  NFS is simple, but buggy.  There are all sorts of problems if you disconnect from the network without unmounting first.  Also, I ran into issues where Finder would just lockup upon opening an NFS mounted directory.</p>

<p>Time to make the switch to AFP.</p>

<h4>Installing netatalk</h4>

<p>OS X doesn&#8217;t like sending clear text passwords, so you have to install libgcrypt-dev so netatalk builds the uams_dhx2.so module.</p>

<pre class="brush: plain; title: ;">
sudo aptitude install build-essential libdb-dev libgcrypt-dev
wget http://prdownloads.sourceforge.net/netatalk/netatalk-2.1.4.tar.bz2?download -O netatalk-2.1.4.tar.bz2
tar xjf netatalk-2.1.4.tar.bz2
cd netatalk-2.1.4
./configure --prefix=/usr/local/netatalk-2.1.4
make &amp;&amp; sudo make install
</pre>

<p>Because we installed by source, we gotta do some symlinking and creating of config files, etc.  First off, I like to make a link in <code>/usr/local</code>.</p>

<pre class="brush: plain; title: ;">
sudo ln -s /usr/local/netatalk-2.1.4 /usr/local/netatalk
</pre>

<p>Now we&#8217;re going to link the config stuff to <code>/etc</code>.</p>

<pre class="brush: plain; title: ;">
sudo ln -s /usr/local/netatalk/etc/netatalk /etc/netatalk
</pre>

<p>Now install <a href="/wp-content/files/netatalk">this</a> initd script as <code>/etc/init.d/netatalk</code> and edit <code>SBIN_PATH</code> and <code>BIN_PATH</code> according to where you installed netatalk.</p>

<pre class="brush: plain; title: ;">
BIN_PATH=/usr/local/netatalk/bin
SBIN_PATH=/usr/local/netatalk/sbin
</pre>

<h4>Configuring netatalk</h4>

<p>Pretty easy.  You just gotta configure two things: afpd and what volumes you want to share.</p>

<p>My <code>/etc/netatalk/afpd.conf</code> is just one line.</p>

<pre class="brush: plain; title: ;">
- -tcp -noddp -uamlist uams_dhx2.so,uams_guest.so -nosavepassword
</pre>

<p>It says &#8220;my default server uses tcp, not ddp and allows users to authentica via dhx2 or as a guest without saving their password.&#8221;  At least, that&#8217;s what I think it says.  The <code>afpd.conf</code> file is well document and it also has a man page.</p>

<p>My <code>/etc/netatalk/AppleVolumes.default</code> is similarly simple.</p>

<pre class="brush: plain; title: ;">
:DEFAULT: options:upriv,usedots
/mnt/quantum &quot;Quantum&quot; allowed_hosts:192.168.0.0/16
</pre>

<p>The <code>:DEFAULT:</code> section applies to all volumes.  <code>upriv</code> means use Unix privileges and <code>usedots</code> means hide files that start with a dot. I&#8217;m only exporting one volume named Quantum and the arguments are pretty self explanatory.  Again, the <code>AppleVolumes.default</code> is well documented and there a man page for it as well.</p>

<p>Now you can start it up.</p>

<pre class="brush: plain; title: ;">
sudo /etc/init.d/netatalk start
</pre>

<p>And connect to it via Finder -> Go -> Connect to Server&#8230;</p>

<p>For the &#8220;Server Address:&#8221; I fill out <code>afp://hostname/Quantum</code> where &#8220;hostname&#8221; is my Linux machine&#8217;s hostname and &#8220;Quantum&#8221; is the name of my volume.</p>

<p>The authentication credentials are your Linux username and password.  Your Linux user will need permissions on the exported volume (obviously).</p>

<h4>Troubleshooting</h4>

<p>Can&#8217;t connect?  See what&#8217;s going on by changing your <code>afpd.conf</code> to be something like.</p>

<pre class="brush: plain; title: ;">
- -tcp -noddp -uamlist uams_dhx2.so,uams_guest.so -nosavepassword -setuplog &quot;default log_debug /tmp/afpd.log&quot;
</pre>

<p>Now you can <code>tail -f /tmp/afpd.log</code> to see what&#8217;s going on when you try to connect.  Note, there are other log levels than &#8220;log_debug&#8221; if you need more info.  See the man page.</p>

<h4>Configuring Avahi</h4>

<p>So by now you&#8217;ve noticed that your Linux AFP server doesn&#8217;t show up in the Finder&#8217;s sidebar like other real Macs do.  Not a problem, we can fix that by installing and configuring Avahi to advertise it.</p>

<pre class="brush: plain; title: ;">
sudo aptitude install avahi-daemon libnss-mdns
</pre>

<p>Now edit <code>/etc/nsswitch.conf</code> and make sure the &#8220;hosts&#8221; line has &#8220;mdns&#8221; in it.</p>

<pre class="brush: plain; title: ;">
hosts:          files mdns4_minimal [NOTFOUND=return] dns mdns4 mdns
</pre>

<p>Now create a new file <code>/etc/avahi/services/afpd.service</code> and fill it out.</p>

<pre class="brush: xml; title: ;">
&lt;?xml version=&quot;1.0&quot; standalone='no'?&gt;&lt;!--*-nxml-*--&gt;
&lt;!DOCTYPE service-group SYSTEM &quot;avahi-service.dtd&quot;&gt;
&lt;service-group&gt;
  &lt;name replace-wildcards=&quot;yes&quot;&gt;%h&lt;/name&gt;
  &lt;service&gt;
    &lt;type&gt;_afpovertcp._tcp&lt;/type&gt;
    &lt;port&gt;548&lt;/port&gt;
  &lt;/service&gt;
  &lt;service&gt;
    &lt;type&gt;_device-info._tcp&lt;/type&gt;
    &lt;port&gt;0&lt;/port&gt;
    &lt;txt-record&gt;model=MacPro&lt;/txt-record&gt;
  &lt;/service&gt;
&lt;/service-group&gt;
</pre>

<p>You can change &#8220;MacPro&#8221; to one of PowerBook, PowerMac, Macmini, iMac, MacBook, MacBookPro, MacBookAir, Xserve, AppleTV1,1, AirPort in order to change the icon that shows up in Finder.</p>

<p>Now restart Avahi and your Linux AFP server should show up in the Finder&#8217;s sidebar!</p>

<pre class="brush: plain; title: ;">
sudo /etc/init.d/avahi-daemon restart
</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.stochasticbytes.com/2010/12/afp-linux-server/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Monit tips and caveats</title>
		<link>http://blog.stochasticbytes.com/2010/11/monit-tips-and-caveats/</link>
		<comments>http://blog.stochasticbytes.com/2010/11/monit-tips-and-caveats/#comments</comments>
		<pubDate>Thu, 18 Nov 2010 04:30:50 +0000</pubDate>
		<dc:creator>Christopher J. Bottaro</dc:creator>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[monit]]></category>
		<category><![CDATA[ulimit]]></category>

		<guid isPermaLink="false">http://blog.stochasticbytes.com/?p=133</guid>
		<description><![CDATA[We switched back to Monit after experimenting some with God (oh, what a nightmare). This post is just meant to be a collection of tips for working with Monit. I&#8217;ll update it as I discover/remember more. 1. Monit does not honor limits.conf We have a Solr process with 3 cores. The number of open file [...]]]></description>
			<content:encoded><![CDATA[<p>We switched back to Monit after experimenting some with God (oh, what a nightmare).  This post is just meant to be a collection of tips for working with Monit.  I&#8217;ll update it as I discover/remember more.</p>

<h4>1. Monit does not honor limits.conf</h4>

<p>We have a Solr process with 3 cores.  The number of open file descriptors can get pretty big between optimizes, exceeding the default limit of 1024 in Ubuntu.  No problem, I&#8217;ll just edit <code>/etc/security/limits.conf</code> and reboot!  No dice.  Looking at <code>/proc/&lt;solr_pid&gt;/limits</code> shows that the changes didn&#8217;t take.</p>

<p>At least the solution is easy.  Change the Monit start command to execute a bash script instead of starting the Solr process directly.  In the bash script, just call <code>ulimit -n 2048</code> before starting the Solr process.</p>

<h4>2. Monit kills your environment</h4>

<p>This is actually well known and documented.  Again the solution is to have Monit call a bash script and just set the environment there before starting your process.</p>

<p>But if it&#8217;s just one or two environments variable you need to set, you can easily do it in the Monit start command using <code>/usr/bin/env</code> .</p>

<pre class="brush: plain; title: ;">
check process some_process
  start = &quot;/usr/bin/env RAILS_ENV=production DEBUG=false /path/to/some_executable --arg1 foo -arg2 bar
  ...
</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.stochasticbytes.com/2010/11/monit-tips-and-caveats/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>WordPress 3.0 with Nginx</title>
		<link>http://blog.stochasticbytes.com/2010/11/wordpress-3-0-with-nginx/</link>
		<comments>http://blog.stochasticbytes.com/2010/11/wordpress-3-0-with-nginx/#comments</comments>
		<pubDate>Thu, 04 Nov 2010 07:21:45 +0000</pubDate>
		<dc:creator>Christopher J. Bottaro</dc:creator>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Nginx]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Wordpress]]></category>

		<guid isPermaLink="false">http://www.stochasticbytes.com/?p=111</guid>
		<description><![CDATA[There is no mod_php for Nginx, so what to do? Setup Nginx to proxy requests to PHP-FPM (an alternative FastCGI implementation)! This post is going to pretty curt&#8230; basically it&#8217;s a post just for me incase my server goes down and I have to redo all this stuff (pfft, backup). PHP prior to 5.3.3 doesn&#8217;t [...]]]></description>
			<content:encoded><![CDATA[<p>There is no mod_php for Nginx, so what to do?  Setup Nginx to proxy requests to PHP-FPM (an alternative FastCGI implementation)!  This post is going to pretty curt&#8230; basically it&#8217;s a post just for me incase my server goes down and I have to redo all this stuff (pfft, backup).</p>

<p>PHP prior to 5.3.3 doesn&#8217;t include PHP-FPM.  You have to download the PHP sources, unpack them, then inject the PHP-FPM sources into them via Subversion.</p>

<pre class="brush: plain; title: ;">
cd $PHP_SRC
svn co http://svn.php.net/repository/php/php-src/trunk/sapi/fpm sapi/fpm
./buildconf --force
./configure --enable-fpm --with-mysql --with-zlib --with-curl --with-openssl --prefix=/usr/local/php-5.3.2 --with-libevent-dir=/usr/local/libevent-1.4.14b-stable/ --with-gd
make &amp;&amp; sudo make install
</pre>

<p>Take a look at the configure line.  We can see that we need the dev packages for MySQL, zlib, curl, openssl, and gd.  libevent was installed by source.</p>

<p>Now the php-fpm binary should be in <code>/usr/local/php-5.3.2/sbin</code>.  It needs two configuration files: <a href="/wp-content/files/php-fpm.conf">php-fpm.conf</a> and <a href="/wp-content/files/php.ini">php.ini</a>.  You can put them anywhere, but I chose to put them in <code>/etc/php</code>.</p>

<p>Ok, we&#8217;re ready to start PHP-FPM&#8230;</p>

<pre class="brush: plain; title: ;">
/usr/local/php-5.3.2/sbin/php-fpm -c /etc/php/php.ini -y /etc/php/php-fpm.conf
</pre>

<p>Assuming you&#8217;ve built and installed Nginx, just add a server like this to your configuration file:</p>

<pre class="brush: plain; title: ;">
server {
  listen              80;
  server_name         www.stochasticbytes.com;
  root                /home/webapps/blog.stochasticbytes.com;
  location / {
    index index.php;
    
    # if file exists return it right away
     if (-f $request_filename) {
       break;
     }
    
     # otherwise rewrite
     if (!-e $request_filename) {
       rewrite ^(.+)$ /index.php$1 last;
       break;
     }
  }
  
  # This matches urls like:
  # /index.php/categories/blog
  location ~ ^/index\.php {
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_param SCRIPT_FILENAME
                  $document_root$fastcgi_script_name;
    fastcgi_param PATH_INFO
                  $fastcgi_script_name;
    include fastcgi_params;
  }
  
  # this matches urls like:
  # /wp-content/plugins/blah/bleck.php
  location ~ \.php$ {
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_param SCRIPT_FILENAME
                  $document_root$fastcgi_script_name;
    fastcgi_param PATH_INFO
                  $fastcgi_script_name;
    include fastcgi_params;
  }
}
</pre>

<p>Start Nginx and you&#8217;re off to races&#8230; assuming you have a WordPress installation at that root directive.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.stochasticbytes.com/2010/11/wordpress-3-0-with-nginx/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Installing Ubuntu 10.04 Server on a USB Stick</title>
		<link>http://blog.stochasticbytes.com/2010/05/installing-ubuntu-10-04-server-on-a-usb-stick/</link>
		<comments>http://blog.stochasticbytes.com/2010/05/installing-ubuntu-10-04-server-on-a-usb-stick/#comments</comments>
		<pubDate>Wed, 12 May 2010 15:01:00 +0000</pubDate>
		<dc:creator>Christopher J. Bottaro</dc:creator>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://localhost/?p=18</guid>
		<description><![CDATA[I used my MacBook to install Ubuntu 10.04 Server (henceforth just Ubuntu) to a USB stick, then I used it to boot my Dell Inspiron 6000D. Here&#8217;s how it went. Logical Sector Size of USB Stick Some USB sticks can’t be used for booting (or are difficult to). Gnu’s Parted doesn’t like devices with logical [...]]]></description>
			<content:encoded><![CDATA[<p>I used my MacBook to install Ubuntu 10.04 Server (henceforth just Ubuntu) to a USB stick, then I used it to boot my Dell Inspiron 6000D.  Here&#8217;s how it went.</p>

<h4>Logical Sector Size of USB Stick</h4>

<p>Some USB sticks can’t be used for booting (or are difficult to).  Gnu’s Parted doesn’t like devices with logical sector sizes of anything but 512.  Also, partman (Ubuntu installer’s disk partitioner) will completely hang during installation while scanning for devices if you have a USB storage device plugged in with logical sector size != 512.</p>

<p>UPDATE:  My stick has 512 logical sector size, but still hung because it came with a FAT32 partition.  Removing the partition fixed the problem.</p>

<p>So I bought a <a href="http://www.newegg.com/Product/Product.aspx?Item=N82E16820134708&amp;cm_re=datatraveler_101-_-20-134-708-_-Product">4 GB Kingston DataTraveler 101</a>.  It has a logical sector size of 512 and will show up as a normal hard drive during the Ubuntu installation process.</p>

<h4>Getting Started</h4>

<p>I like to <a href="&quot;http://releases.ubuntu.com/10.04/ubuntu-10.04-server-i386.iso.torrent&quot;">torrent</a> the Ubuntu installation CD.  Bittorrent is pretty damn fast.</p>

<p>Boot your MacBook using the Ubuntu installation CD (hold down ‘c’ while it is booting).  Choose “Install Ubuntu” and follow the instructions like normal.</p>

<h4>Partitioning</h4>

<p>When you get to the disk partitioning part, make sure you partition your USB stick and NOT your MacBook’s hard drive.  My USB stick was <code>/dev/sdc</code> and my MacBook’s hard drive was <code>/dev/sda</code>.</p>

<p>I kept it simple and made a single root partition with an ext4 filesystem.  Making a swap partition is optional; the idea being that if you omit it, you won’t put as much wear on your USB stick.  Discussion on running Linux without swap space can be found <a href="http://kerneltrap.org/node/3202">here</a>.</p>

<p>Some people think that using a journaling filesystem will prematurely wear a USB stick, but <a href="http://robert.penz.name/137/no-swap-partition-journaling-filesystem-on-a-ssd/">this guy doesn’t think so</a>.</p>

<h4>Installing Grub</h4>

<p>Towards the end of the installation, it will ask you where to install Grub.  Again, be sure to specify your USB stick device and NOT your MacBook’s hard drive.  I specified /dev/sdc.  Notice that’s the entire device (not just a partition), thus installing Grub on the MBR.</p>

<h4>All Done?</h4>

<p>Eventually, the installation process will ask you to eject the CD and reboot.  So we’re done, right?  Oh no no no, it’s just starting to get interesting&#8230; =/</p>

<p>Hit ctrl-alt-f2 and drop into a shell&#8230;</p>

<h4>Fixing Grub</h4>

<p><strong>UPDATE</strong>:  My Dell booted fine without following this step.</p>

<p>When the Ubuntu installer ran, the USB stick partitions get assigned some device ids like <code>hd(1,1)</code>.  When Grub installed, those are the device ids it wrote to <code>/boot/grub/grub.cfg</code>.  When you boot from the USB stick, it will get assigned ids like <code>hd(0,1)</code> and thus Grub will fail because it can’t find the devices specified in <code>/boot/grub/grub.cfg</code>.  I’m not quite sure if that’s exactly what happens, but it’s something like that.</p>

<p>Mount your USB stick partitions and edit <code>/boot/grub/grub.cfg</code>.  Replace all instances of <code>hd(1</code> (or whatever number your installation happened to use) with <code>hd(0</code>.</p>

<p>You’re not supposed to edit grub.cfg because it’s the output of a bunch of template files and bash scripts and thus will be overwritten each time you install a new kernel or do anything that calls grub-install (or grub-setup or update-grub or whatever), but honestly I don’t know how to edit the templates properly, so I just have to remember to edit grub.cfg each time it gets overwritten.</p>

<p>Inspiration for this step came from <a href="http://www.ubuntugeek.com/a-much-easier-way-to-install-ubuntu-on-a-usb-device-stick-or-hd.html">here</a>.</p>

<h4>Using tmpfs</h4>

<p>Flash memory has a limited number of writes so it’s a good idea to try to minimize them.  One way to do that is use tmpfs for directories that are frequently written to.</p>

<p>Append the following to your <code>/etc/fstab</code> file:</p>

<pre class="brush: plain; title: ;">
tmpfs /tmp       tmpfs defaults,size=64M 0 0
tmpfs /var/crash tmpfs defaults,size=64M 0 0
tmpfs /var/lock  tmpfs defaults,size=64M 0 0
tmpfs /var/log   tmpfs defaults,size=64M 0 0
tmpfs /var/run   tmpfs defaults,size=64M 0 0
tmpfs /var/tmp   tmpfs defaults,size=64M 0 0
</pre>

<p>One nifty thing about tmpfs is that it won’t actually use memory unless you put files on it, so it’s ok to specify the size option to be something much larger than what you’ll actually use at that mount point.</p>

<h4>Fixing Apt/Aptitude</h4>

<p>Most programs that write to <code>/var/log</code> are fairly robust:  they will create files/directories if they don’t exist (perfect since we’re using tmpfs).  Apt is not.  It will crash if the directory <code>/var/log/apt</code> doesn’t exist.</p>

<p>I was lazy and just added</p>

<pre class="brush: plain; title: ;">
mkdir /var/log/apt
</pre>

<p>to <code>/etc/rc.local</code> instead of going through the rigamarole of using <code>update-rc.d</code>.</p>

<h4>Done! &#8230; ?</h4>

<p>Alright, we can finally finish the Ubuntu installation process, eject the CD, pull out the USB stick and boot another computer with it (my Dell Inspiron).  So let’s try that&#8230;</p>

<h4>Where’s my internet?</h4>

<p>For fuck sake.  Internet doesn’t work.  As far as I can tell, this is what happened&#8230;</p>

<p>When we installed Ubuntu on the USB stick, it thought it was installing on my MacBook so it detected all of it’s hardware and wrote network configurations for eth0 where ever the hell it writes those things.</p>

<p>Now after I boot my Dell with the USB stick, I run <code>dmesg | grep eth</code> and see messages like&#8230;</p>

<pre class="brush: plain; title: ;">
udev: renamed network interface eth0 to eth1
udev: renamed network interface eth0 to eth2
</pre>

<p>Now I don’t really know what this means, but I suppose it has something to do with the hardware that Linux detects at boot conflicts with the network configuration written at installation.</p>

<p>At least the fix is easy, just edit <code>/etc/network/interfaces</code> and append the lines:</p>

<pre class="brush: plain; title: ;">
auto eth1
iface eth1 inet dhcp
</pre>

<p>And reboot.</p>

<p>That fixes my ethernet.  I’m not sure if subbing <code>eth2</code> will enable my wireless because I’m not really interested in using wireless.</p>

<h4>Done</h4>

<p>Ok, I think that’s it.  I’ll update this post if I run into more issues.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.stochasticbytes.com/2010/05/installing-ubuntu-10-04-server-on-a-usb-stick/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Time, DateTime and to_yaml</title>
		<link>http://blog.stochasticbytes.com/2009/04/time-datetime-and-to_yaml/</link>
		<comments>http://blog.stochasticbytes.com/2009/04/time-datetime-and-to_yaml/#comments</comments>
		<pubDate>Thu, 23 Apr 2009 03:12:00 +0000</pubDate>
		<dc:creator>Christopher J. Bottaro</dc:creator>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://localhost/?p=17</guid>
		<description><![CDATA[This is interesting. If you convert an instance of DateTime to YAML and reload it using YAML.load, it comes back as an instance of Time, minus the fractional seconds. require 'rubygems' require 'activesupport' dt = DateTime.now puts dt.to_f # =&#62; 1240455468.91318 t = YAML.load(dt.to_yaml) puts t.class.name # =&#62; Time puts t.to_f # =&#62; 1240455468.0 Also, [...]]]></description>
			<content:encoded><![CDATA[<p>This is interesting.  If you convert an instance of DateTime to YAML and reload it using <code>YAML.load</code>, it comes back as an instance of Time, minus the fractional seconds.</p>

<pre class="brush: ruby; title: ;">
require 'rubygems'
require 'activesupport'
dt = DateTime.now
puts dt.to_f
# =&gt; 1240455468.91318
t = YAML.load(dt.to_yaml)
puts t.class.name
# =&gt; Time
puts t.to_f
# =&gt; 1240455468.0
</pre>

<p>Also, if you convert a DateTime to UTC then to a Time, it drops the fractional seconds.  Converting a DateTime directly to a Time does NOT drop the fractional seconds, nor does converting a Time to UTC.  Weird.</p>

<pre class="brush: ruby; title: ;">
require 'rubygems'
require 'activesupport'
dt = DateTime.now
puts dt.utc.to_f
# =&gt; 1240456717.12572
puts dt.to_time.to_f
# =&gt; 1240456717.12572
puts dt.utc.to_time.to_f
# =&gt; 1240455680.0
puts dt.to_time.utc.to_f
# =&gt; 1240456717.12572
</pre>

<p>As you can see, I&#8217;m using ActiveSupport.  I don&#8217;t really know how much (or if any) that is affecting this weird behavior.</p>

<p>Heh, I don&#8217;t really know if &#8220;fractional seconds&#8221; is proper terminology either.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.stochasticbytes.com/2009/04/time-datetime-and-to_yaml/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>eager loading + limit + order in named scope = ActiveRecord 2.1.0 bug</title>
		<link>http://blog.stochasticbytes.com/2008/07/eager-loading-limit-order-in-named-scope-activerecord-2-1-0-bug/</link>
		<comments>http://blog.stochasticbytes.com/2008/07/eager-loading-limit-order-in-named-scope-activerecord-2-1-0-bug/#comments</comments>
		<pubDate>Sun, 20 Jul 2008 23:23:00 +0000</pubDate>
		<dc:creator>Christopher J. Bottaro</dc:creator>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://localhost/?p=14</guid>
		<description><![CDATA[I found this bug while writing a (now defunct) Rails plugin that uses named scopes. It&#8217;s caused by an oversight in construct_finder_sql_for_association_limiting and manifests when the following three conditions are met: there is a has_many association being eager loaded the query is being limited there is order specified by a named scope I fixed the [...]]]></description>
			<content:encoded><![CDATA[<p>I found this bug while writing a (now defunct) Rails plugin that uses named scopes.  It&#8217;s caused by an oversight in <code>construct_finder_sql_for_association_limiting</code> and manifests when the following three conditions are met:</p>

<ol>
<li>there is a has_many association being eager loaded</li>
<li>the query is being limited</li>
<li>there is order specified by a named scope</li>
</ol>

<p>I fixed the bug by making modifications to <code>construct_finder_sql_for_association_limiting</code>.  You can apply the fix by including the following code into your Rails project:</p>

<pre class="brush: ruby; title: ;">
module ActiveRecord
  module Associations
    module ClassMethods

      def construct_finder_sql_for_association_limiting(options, join_dependency)
        scope       = scope(:find) || {}
        order       = [options[:order], scope[:order]].compact.join(', ')

        # Only join tables referenced in order or conditions since this is particularly slow on the pre-query.
        tables_from_conditions = conditions_tables(options)
        tables_from_order      = order_tables(options)
        all_tables             = tables_from_conditions + tables_from_order
        distinct_join_associations = all_tables.uniq.map{|table|
          join_dependency.joins_for_table_name(table)
        }.flatten.compact.uniq

        is_distinct = !options[:joins].blank? || include_eager_conditions?(options, tables_from_conditions) || include_eager_order?(options, tables_from_order)
        sql = &quot;SELECT &quot;
        if is_distinct
          sql &lt;&lt; connection.distinct(&quot;#{connection.quote_table_name table_name}.#{primary_key}&quot;, order)
        else
          sql &lt;&lt; primary_key
        end
        sql &lt;&lt; &quot; FROM #{connection.quote_table_name table_name} &quot;

        if is_distinct
          sql &lt;&lt; distinct_join_associations.collect(&amp;:association_join).join
          add_joins!(sql, options, scope)
        end

        add_conditions!(sql, options[:conditions], scope)
        add_group!(sql, options[:group], scope)

        if order and is_distinct
          connection.add_order_by_for_association_limiting!(sql, :order =&gt; order)
        else
          add_order!(sql, options[:order], scope)
        end

        add_limit!(sql, options, scope)

        return sanitize_sql(sql)
      end
      
    end
  end
end
</pre>

<p>If you are curious about what exactly was changed, see <a href="http://pastie.org/237580">this diff pastie</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.stochasticbytes.com/2008/07/eager-loading-limit-order-in-named-scope-activerecord-2-1-0-bug/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>callback chains and metaclasses</title>
		<link>http://blog.stochasticbytes.com/2008/07/callback-chains-and-metaclasses/</link>
		<comments>http://blog.stochasticbytes.com/2008/07/callback-chains-and-metaclasses/#comments</comments>
		<pubDate>Thu, 17 Jul 2008 17:45:00 +0000</pubDate>
		<dc:creator>Christopher J. Bottaro</dc:creator>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://localhost/?p=13</guid>
		<description><![CDATA[For whatever reason (don&#8217;t ask) our Rails app needed to be able to define callbacks on objects. As it stands now, you can only define callbacks on classes. Well, metaclasses are classes that pertain to a single specific object, so I figured I could add the callbacks there and everything will work. Please excuse my [...]]]></description>
			<content:encoded><![CDATA[<p>For whatever reason (don&#8217;t ask) our Rails app needed to be able to define callbacks on objects.  As it stands now, you can only define callbacks on classes.  Well, metaclasses are classes that pertain to a single specific object, so I figured I could add the callbacks there and everything will work.</p>

<p>Please excuse my <em>extremely</em> contrived example.</p>

<pre class="brush: ruby; title: ;">
p = Post.find(...)
if p.contains_porn?
  class &lt;&lt; p
    before_save :filter_content
  end
end
p.save # should trigger filter_content
</pre>

<p>Well it doesn&#8217;t work.</p>

<p>After digging through the ActiveSupport source code, I saw that it only looks for callback chains in the object&#8217;s class, not metaclass.  That&#8217;s not too hard to fix&#8230;</p>

<pre class="brush: ruby; title: ;">
module ActiveSupport
  module Callbacks
    def run_callbacks(kind, options = {}, &amp;block)
      callback_chain_method = &quot;#{kind}_callback_chain&quot;
      # Meta class inherits Class so we don't have to merge it in 1.9
      if RUBY_VERSION &gt;= '1.9'
        metaclass.send(callback_chain_method).run(self, options, &amp;block)
      else
        callbacks = self.class.send(callback_chain_method) | metaclass.send(callback_chain_method)
        callbacks.run(self, options, &amp;block)
      end
    end
  end
end
</pre>

<p>Credit to Josh Peek for the Ruby 1.9 fix.</p>

<p>Ok, so that got it working&#8230; with <strong>one major caveat:  you cannot serialize objects that use callbacks</strong>.  Ruby cannot serialize objects that have metaclasses and the <code>run_callbacks</code> method creates a metaclass whether you use it or not.</p>

<p>That&#8217;s fine with me, I don&#8217;t really like serializing objects anyways, but apparently it&#8217;s a major problem for other people&#8230; see <a href="http://github.com/rails/rails/commit/e0846c8417093853f4f7f62732983e990c28d669">here</a> and <a href="http://rails.lighthouseapp.com/projects/8994/tickets/575-callbacks-don-t-work-from-extended-modules">here</a>.</p>

<p>Josh Peek suggested a solution where we store the callback chains on the objects themselves, so object specific callbacks would be done like this&#8230;</p>

<pre class="brush: ruby; title: ;">
p = Post.find(...)
p.before_save :filter_content if p.contains_porn?
p.save # should trigger filter_content
</pre>

<p>That seems like a very simple, clean and rational solution.  I&#8217;ll look into coding it up later.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.stochasticbytes.com/2008/07/callback-chains-and-metaclasses/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

