the all-thing | 2010-07-29 19:44:42 -0400 ========================================== Ruby, Ncurses and blocked threads --------------------------------- Date: August 6, 2009 6:40pm Author: William Morgan Labels: ruby, ncurses, sup URL: http://all-thing.net/ruby-ncurses-and-thread-blocking.txt If you're writing a multithreaded Ruby program that uses ncurses, you might be curious why program stops running when you call @Ncurses.getch@. Sup [1] has been plagued by this issue since 2005. Thankfully, I think I finally understand it. The problem is that there is a bug in the Ruby ncurses library such that using blocking input will block *all* Ruby threads when it waits for user input, instead of just the calling thread. So @Ncurses.getch@ will cause everything to grind to a halt. This is probably due to the library not releasing the GVL when blocking on stdin. This bug is present in the latest rubygems version of curses, 0.9.1. It has been fixed in the latest libncurses-ruby Debian packages (1.1-3). To see if you have a buggy, blocking version of the ruby ncurses library, run this program: require 'rubygems' require 'ncurses' require 'thread' Ncurses.initscr Ncurses.noecho Ncurses.cbreak Ncurses.curs_set 0 Thread.new do sleep 0.1 Ncurses.stdscr.mvaddstr 0, 0, "library is GOOD." end begin Ncurses.stdscr.mvaddstr 0, 0, "library is BAD." Ncurses.getch ensure Ncurses.curs_set 1 Ncurses.endwin puts "bye" end (I purposely require @rubygems@ in there to load the rubygems ncurses library if it's present; you can drop this if you don't use rubygems.) There are two workarounds to this problem. First, you can simply tell ncurses to use nonblocking input: Ncurses.nodelay Ncurses.stdscr, true But if you're writing a multithreaded app, you probably aren't interested in nonblocking input, unless you want a nasty polling loop. The better choice is to add a call to @IO.select@ before @getch@, which will block the calling thread until there's an actual keypress, and then allow @getch@ to pick it up: if IO.select [$stdin], nil, nil, 1 Ncurses.getch end @IO.select@ requires a delay, so you'll have to handle the periodic nils that generates. But the background threads should no longer block. There is one further complication, which is that you won't be able to receive the pseudo-keypresses Ncurses emits when the terminal size changes, since they don't show up on @$stdin@ and thus the @select@ won't pass. The solution is to install your own signal handler: trap("WINCH") { ... handle sigwinch ... } You will still see the resize events coming from @getch@, but only once the user presses a key. You can drop them at this point. That should be enough to make any multithreaded Ruby ncurses app able function. Of course, once everyone's using a fixed version fo the ncurses libraries, you can do away with the @select@ and set @nodelay@ to false. (One last hint for the future: I've found it necessary to set it to false before every call to @getch@; otherwise a ctrl-c will magically change it back to nonblocking mode. Not sure why.) [1] http://sup.rubyforge.org Replies -------- Felipe Contreras, on November 2, 2009 5:43pm: ["| \n", "| Shouldn't we be pushing for the ncurses gem to be updated? Anyway, I tried\n", "| ncurses-1.2.4 and indeed your test now works correctly, but I still feel sup\n", "| very slow, specially when I launch it and it's loading the threads.\n", "| \n", "| \n", "| \n"] Ollivier Robert, on November 20, 2009 2:04pm: ["| \n", "| Is support for things like ff-ncurses planned? Either the ncurses gem has\n", "| been removed (moved to gemcutter?) or there is a problem on my side but I\n", "| can't seem to find it (as a gem).\n", "| \n"] William Morgan, on December 7, 2009 1:21pm: [" | \n", " | What is ff-ncurses?\n", " | \n", " | \n", " | Works for me (tm).\n", " | \n"] This delicious text version served up by Whisper .