tag:www.rhnh.net,2008:/locking Locking - Xavier Shay's Blog 2009-08-04T17:34:14Z Enki Xavier Shay notreal@rhnh.net tag:www.rhnh.net,2008:Post/799 2009-08-04T17:40:14Z 2009-08-04T17:34:14Z Acts_as_state_machine locking <p>consider the following!</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt>7<tt> </tt>8<tt> </tt>9<tt> </tt><strong>10</strong><tt> </tt>11<tt> </tt>12<tt> </tt>13<tt> </tt>14<tt> </tt>15<tt> </tt>16<tt> </tt>17<tt> </tt>18<tt> </tt>19<tt> </tt><strong>20</strong><tt> </tt>21<tt> </tt>22<tt> </tt>23<tt> </tt>24<tt> </tt>25<tt> </tt>26<tt> </tt>27<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="r">class</span> <span class="cl">Door</span> &lt; <span class="co">ActiveRecord</span>::<span class="co">Base</span><tt> </tt> acts_as_state_machine <span class="sy">:initial</span> =&gt; <span class="sy">:closed</span><tt> </tt><tt> </tt> state <span class="sy">:closed</span><tt> </tt> state <span class="sy">:open</span>, <span class="sy">:enter</span> =&gt; <span class="sy">:say_hello</span><tt> </tt><tt> </tt> event <span class="sy">:open</span> <span class="r">do</span><tt> </tt> transitions <span class="sy">:from</span> =&gt; <span class="sy">:closed</span>, <span class="sy">:to</span> =&gt; <span class="sy">:open</span><tt> </tt> <span class="r">end</span><tt> </tt><tt> </tt> <span class="r">def</span> <span class="fu">say_hello</span><tt> </tt> puts <span class="s"><span class="dl">&quot;</span><span class="k">hello</span><span class="dl">&quot;</span></span><tt> </tt> <span class="r">end</span><tt> </tt><span class="r">end</span><tt> </tt><tt> </tt>door = <span class="co">Door</span>.create!<tt> </tt><tt> </tt>fork <span class="r">do</span><tt> </tt> transaction <span class="r">do</span><tt> </tt> door.open!<tt> </tt> <span class="r">end</span><tt> </tt><span class="r">end</span><tt> </tt><tt> </tt>door.open!<tt> </tt><tt> </tt><span class="c"># &gt;&gt; hello</span><tt> </tt><span class="c"># &gt;&gt; hello</span><tt> </tt></pre></td> </tr></table> <p>It&#8217;s broken, you can only open a door once. This is a classic double-update problem. One way to solve is with pessimistic locking. I made some codes that automatically lock any object when you call an event on it.</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt>7<tt> </tt>8<tt> </tt>9<tt> </tt><strong>10</strong><tt> </tt>11<tt> </tt>12<tt> </tt>13<tt> </tt>14<tt> </tt>15<tt> </tt>16<tt> </tt>17<tt> </tt>18<tt> </tt>19<tt> </tt><strong>20</strong><tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="r">class</span> <span class="cl">ActiveRecord::Base</span><tt> </tt> <span class="c"># Forces all state transition events to obtain a DB lock</span><tt> </tt> <span class="r">def</span> <span class="pc">self</span>.obtain_lock_before_all_state_transitions<tt> </tt> event_table.keys.each <span class="r">do</span> |transition|<tt> </tt> define_method(<span class="s"><span class="dl">&quot;</span><span class="il"><span class="idl">#{</span>transition<span class="idl">}</span></span><span class="k">_with_lock!</span><span class="dl">&quot;</span></span>) <span class="r">do</span><tt> </tt> <span class="pc">self</span>.class.transaction <span class="r">do</span><tt> </tt> lock!<tt> </tt> send(<span class="s"><span class="dl">&quot;</span><span class="il"><span class="idl">#{</span>transition<span class="idl">}</span></span><span class="k">_without_lock!</span><span class="dl">&quot;</span></span>)<tt> </tt> <span class="r">end</span><tt> </tt> <span class="r">end</span><tt> </tt> alias_method_chain <span class="s"><span class="dl">&quot;</span><span class="il"><span class="idl">#{</span>transition<span class="idl">}</span></span><span class="k">!</span><span class="dl">&quot;</span></span>, <span class="sy">:lock</span><tt> </tt> <span class="r">end</span><tt> </tt> <span class="r">end</span><tt> </tt><span class="r">end</span><tt> </tt><tt> </tt><span class="r">class</span> <span class="cl">Door</span> &lt; <span class="co">ActiveRecord</span>::<span class="co">Base</span><tt> </tt> <span class="c"># ... as before</span><tt> </tt><tt> </tt> obtain_lock_before_all_state_transitions<tt> </tt><span class="r">end</span><tt> </tt></pre></td> </tr></table> <p>beware! Your state transitions can now throw <code>ActiveRecord::RecordNotFound</code> errors (from <code>lock!</code>), since the object may have been deleted before you got a chance to play with it.</p> <p>If you&#8217;re not using any locking in your web app, you&#8217;re probably doing it wrong. Just sayin&#8217;.</p>