Search code examples
rubymultithreadingstdingetch

Thread ignoring first input when using getch


require 'rubygems'
require 'mechanize'
require 'io/console'

flag = 0
t2 =Thread.new do
    puts flag
    loop do
        temp = STDIN.getch
        if temp=="\n"
            flag = (flag+1)%2
            puts flag
        end
    end
end

# => Some foreground code

t2.join

When i run the code i get the value of flag printed 0 as it should. But the thread does not change the value of flag on the first Enter I hit. Hitting Enter the second time changes flag to 1 although. The thread works normally toggling the value of flag on further Enter hits. Why is this happening? What have I done wrong?

Problem seems to be only with getch as when I use gets in place of getch the problem disappears. But I cant use gets because I want the user to hit a single key without needing to press Enter after the key to give input. For example flag should not change when the user inputs a instead of Enter and so I have used getch to make sure the input is given after a single keyboard hit.

A similar problem was described here but it isn't a duplicate.

Edit 1: The problem seems to be with getch and not the check what do ever.

flag = 0

t2 =Thread.new do
  puts flag

  loop do
    temp = STDIN.getch
    flag = (flag+1)%2
    puts flag
  end

end
t2.join

Even after removing the if statement, the first Enter is ignored no matter what but other characters seem to respond to the first time. The problem is coming only when I hit Enter. It doesn't count the first Enter I hit.

ruby 2.3.3p222 (2016-11-21 revision 56859) [x64-mingw32]

Solution

  • I tried your code on a Windows machine and was able to re-create the problem. As you correctly guessed it has nothing to do with threading and everything with how getch works (on Windows). If you add a p temp.inspect to your loop you will see that it is not so much that the first '\n' is swallowed, rather it is that it is somehow "held back". The best way to see this if you press Enter and another key alternatively. You will see that the inspect is "off-by-one".

    A good explanation about the problem is discussed here: https://www.rubytapas.com/2016/12/14/ruby-code-on-windows/

    With that information, a simple solution (that has the added benefit to run also on Linux, not sure about Mac) is:

    require 'rubygems'
    require 'mechanize'
    require 'io/console'
    
    STDIN.binmode     #this line added to prevent line-end translation
    
    flag = 0
    t2 =Thread.new do
        puts flag
        loop do
            temp = STDIN.getch
            if temp=="\r"  # note: changed from LF to CR
                flag = (flag+1)%2
                puts flag
            end
        end
    end
    
    # => Some foreground code
    
    t2.join
    

    Notes: Admittedly I don't fully get the way it works. I was expecting that Enter would cause a "\r\n" sequence in binmode, but I only see "\r". Not sure what happens to the "\n", but it seems to work reliably this way. Also note that in the current version the program can not be terminated with Ctrl+C. You'll have to add a check for that.