[MacRuby-devel] Concurrency bug in ControlTower

Joshua Ballanco jballanc at gmail.com
Mon Jan 24 09:13:45 PST 2011


Hey Charles,

Sure, this is as good a place as any to report issues on ControlTower.
There's also a component for ControlTower on the MacRuby trac site, but I
guess we don't really call that out on the web site.

Regarding the potential bug...well...where to begin? So, yes, MacRuby blocks
have the same semantics as regular Ruby blocks, *except* when they are
dispatched through GCD. In that case, ivars are shared, but local variable
get copied. I'm sure Laurent can explain why better than I, but it has to do
with the semantics of libdispatch and the uncertainty inherent in when a
dispatched block or function will execute (i.e. if local variables were not
copied during dispatch, they might go out of scope and be collected before
GCD ever gets around to running the code).

To illustrate how this impacts async semantics wrt ruby, here's a sample
done with MacRuby/GCD and canonical Ruby:

Earth: ~/Desktop > cat macfruit.rb
fruit_sellers = Dispatch::Group.new
offerings = %w|apples oranges bananas pinaples coconuts|
idx = 0
while(idx < 5)
  fruit = offerings[idx]
  Dispatch::Queue.concurrent.async(fruit_sellers) do
    sleep 5-idx
    puts "I sell #{fruit}, fresh as can be. Come and get some!"
  end
  idx += 1
end
fruit_sellers.wait
puts "What would you like?"

Earth: ~/Desktop > macruby macfruit.rb
I sell coconuts, fresh as can be. Come and get some!
I sell pinaples, fresh as can be. Come and get some!
I sell bananas, fresh as can be. Come and get some!
I sell oranges, fresh as can be. Come and get some!
I sell apples, fresh as can be. Come and get some!
What would you like?

Earth: ~/Desktop > macruby macfruit.rb
I sell coconuts, fresh as can be. Come and get some!
I sell pinaples, fresh as can be. Come and get some!
I sell bananas, fresh as can be. Come and get some!
I sell oranges, fresh as can be. Come and get some!
I sell apples, fresh as can be. Come and get some!
What would you like?

Earth: ~/Desktop > cat mrifruit.rb fruit_sellers = []
offerings = %w|apples oranges bananas pinaples coconuts|
idx = 0
while(idx < 5)
  fruit = offerings[idx]
  fruit_sellers << Thread.new do
    sleep 5-idx
    puts "I sell #{fruit}, fresh as can be. Come and get some!"
  end
  idx += 1
end
fruit_sellers.each(&:join)
puts "What would you like?"

Earth: ~/Desktop > ruby mrifruit.rb
I sell coconuts, fresh as can be. Come and get some!
I sell coconuts, fresh as can be. Come and get some!
I sell coconuts, fresh as can be. Come and get some!
I sell coconuts, fresh as can be. Come and get some!
I sell coconuts, fresh as can be. Come and get some!
What would you like?

In other words, not a bug in MacRuby...also, this code is scheduled for a
rewrite that will turn it into a bit more of a continuation-passing style of
handling the connections. I don't think tap is really the solution we are
looking for here, since after dispatching, the main #accept loop doesn't
really care about that connection (other than that, at some point, it is
responsibly closed).

Cheers,

Josh

On Sat, Jan 22, 2011 at 2:14 AM, Charles Oliver Nutter
<headius at headius.com>wrote:

> Apologies reporting this here; I'm not sure where I should report bugs
> in ControlTower.
>
> I believe there's a bug in ControlTower around line 34:
>
>    def open
>      @status = :open
>      while (@status == :open)
>        connection = @socket.accept # << here
>
>        @request_queue.async(@request_group) do
>
> The "connection" local variable is used in the async block below to
> parse the request and send the response. Unless MacRuby's blocks
> behave differently than regular Ruby, this variable is shared across
> all activations of that block. As a result, it's possible that two
> concurrent requests will end up using each others' connection, and
> usually just blowing up as a result.
>
> The fix I've come up with is to wrap the @request_queue.async call in
> a tap call:
>
>    def open
>      @status = :open
>      while (@status == :open)
>        conn = @socket.accept
>
>        conn.tap do |connection|
>          @request_queue.async(@request_group) do
>
> Since async can't accept any explicitly-passed state, this seems like
> the safest way to ensure the connection reference is not shared by
> separate invocations. I'm not sure if it's possible in GCD, but having
> async take an optional argument with explicitly-passed state might
> also be a good way to fix this.
>
> - Charlie
> _______________________________________________
> MacRuby-devel mailing list
> MacRuby-devel at lists.macosforge.org
> http://lists.macosforge.org/mailman/listinfo.cgi/macruby-devel
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/macruby-devel/attachments/20110124/8886544d/attachment.html>


More information about the MacRuby-devel mailing list