crystal-lang / crystal

The Crystal Programming Language
https://crystal-lang.org
Apache License 2.0
19.42k stars 1.62k forks source link

Select with empty else block hangs all fibers, and compiler does not give error #12154

Open DougEverly opened 2 years ago

DougEverly commented 2 years ago

The empty else block will hang all fibers and will not give a compile error.

    select
    when x = chan.receive
      puts x
    else
    end

An empty when block works as expected.

Example:

% crystal --version
Crystal 1.4.1 (2022-04-22)

LLVM: 13.0.1
Default target: aarch64-apple-darwin21.5.0

Works as expected:

chan = Channel(Bool).new
chan1 = Channel(Bool).new

# extra fiber to demonstrate hang across fibers
spawn do
  loop do
    puts "hey"
    sleep 1
  end
end

spawn do
  loop do
    select
    when x = chan1.receive
      # no code here, but ok
    when x = chan.receive
      puts x
    end
  end
end

loop do
  chan.send(true)
  sleep 1
end

Unexpectedly hangs all fibers, and without compiler error:

chan = Channel(Bool).new
chan1 = Channel(Bool).new

# extra fiber to demonstrate hang across fibers
spawn do
  loop do
    puts "hey"
    sleep 1
  end
end

spawn do
  loop do
    select
    when x = chan1.receive
      # no code here, but ok
    when x = chan.receive
      puts x
    else
      # no code here, but hangs
    end
  end
end

loop do
  chan.send(true)
  sleep 1
end
Blacksmoke16 commented 2 years ago

I think what's going on is that it's not the fact that it's empty causing it to hang, but more so there is no blocking operation that allows it to switch to another fiber. For example, in your working flow:

Once you introduce the else block, the second iteration won't block execution, thus never allowing the main fiber to run which essentially traps the program in the infinite loop of the second spawned fiber.

In short, I'm fairly certain this is working by design.

EDIT: You can prove this by adding sleep 0 causes it to work, but something like puts "else" does not, but does make it more clear what's going on.

straight-shoota commented 2 years ago

In short, I'm fairly certain this is working by design.

Yes, I agree. Your else branch short circuits the fiber in a tight loop. There is no escape 🧟

We're missing documentation on select (https://github.com/crystal-lang/crystal-book/issues/208). There this could be mentioned as a possible trap.