Porting a really Ruby IO.select use case into Crystal

i am current porting a ruby gem like Foreman but things are designed to run in both foreground/background into Crystal, I am quite ensure it is doable, although there are many challenges.

Following is one about IO.select, i am not a expect of it, in fact, manipulate it directly rarely, I need help.

The original ruby version code is here, following is the copy.

def listen
  loop do
    # IO.select IOs, timeout 30 seconds
    io = IO.select([@sp_reader] + @listeners.keys, nil, nil, 30)

    if io && io.first
      io.first.each do |io|
        if io == @sp_reader
          io.read_nonblock(999)
          next
        end

        Thread.new(io) do |io|
          handle_client(client: io.accept, server: io)
        end
      end
    end

    @stopped_processes.reject do |process|
      if io = @listeners.key(process)
        Procodile.log nil, "proxy", "Stopped proxy listener for #{process.name}"
        io.close
        @listeners.delete(io)
      end

      true
    end
  end
rescue e
  Procodile.log nil, "proxy", "Exception: #{e.class}: #{e.message}"
  Procodile.log nil, "proxy", e.backtrace[0, 5].join("\n")
end

Now, i want to port to Crystal, because the IO.select was removed, as describe by this,

But, i don’t sure how to handle this, following is a attempt.

def listen
  sleep_chan = Channel(Nil).new
  sp_reader_chan = Channel(Nil).new
  listener_chan = Channel(Nil).new

  spawn do
    loop do
      sleep 30
      sleep_chan.send nil
    end
  end

  spawn do
    loop do
      @sp_reader.read(Bytes.new(999))
      sp_reader_chan.send nil
    end
  end

  @listeners.keys.each do |io|
    spawn do
      loop do
        handle_client(client: io.accept, server: io)
        listener_chan.send nil
      end
    end
  end

  loop do
    select
    when sp_reader_chan.receive
    when listener_chan.receive
    when sleep_chan.receive
    end

    @stopped_processes.reject do |process|
      if io = @listeners.key(process)
        Procodile.log nil, "proxy", "Stopped proxy listener for #{process.name}"
        io.close
        @listeners.delete(io)
      end

      true
    end
  end
end

Please help point out the issue, incorrect usage.

Hopefully more and more Ruby code can be rewritten in Crystal.

On Github repo of your cited gem, it shows that there is a Crystal implementation in place. Have you looked into that? GitHub - arktisklada/crank: Foreman port to crystal.

Cool, thanks.

i don’t know this shard before, i will refer to it when i have issue, i test crank shards just now, it not work anymore because crystal version is too old, i have to fix it before run it.

I personal use procodile on my projects, so, i want to rewrite it, The main purpose is to improve my Crystal skills more quickly.

1 Like

@naqvis , thanks very much for help, crank starts work now.

Procodile is far feature rich than crank, but, i will refer to its implementation.

1 Like

Have you heard of Nox before (used by Lucky)? GitHub - matthewmcgarvey/nox: Procfile-based process manager written in Crystal

Oops, i knew it, but i forget it before you mention it, thanks