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> < <span class="co">ActiveRecord</span>::<span class="co">Base</span><tt>
</tt> acts_as_state_machine <span class="sy">:initial</span> => <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> => <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> => <span class="sy">:closed</span>, <span class="sy">:to</span> => <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">"</span><span class="k">hello</span><span class="dl">"</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"># >> hello</span><tt>
</tt><span class="c"># >> hello</span><tt>
</tt></pre></td>
</tr></table>
<p>It’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">"</span><span class="il"><span class="idl">#{</span>transition<span class="idl">}</span></span><span class="k">_with_lock!</span><span class="dl">"</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">"</span><span class="il"><span class="idl">#{</span>transition<span class="idl">}</span></span><span class="k">_without_lock!</span><span class="dl">"</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">"</span><span class="il"><span class="idl">#{</span>transition<span class="idl">}</span></span><span class="k">!</span><span class="dl">"</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> < <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’re not using any locking in your web app, you’re probably doing it wrong. Just sayin’.</p>