ProfSteve / lanterna

Automatically exported from code.google.com/p/lanterna
GNU Lesser General Public License v3.0
0 stars 0 forks source link

Blocking readInput #44

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
I noticed the readInput function is non-blocking. So, in a loop the CPU usage 
reaches 50% while the program waits for user presses a key.
Does a way exist to wait for a press key action by a sort of blocking readInput 
function?

Original issue reported on code.google.com by christia...@gmail.com on 5 Oct 2012 at 2:44

GoogleCodeExporter commented 9 years ago
The way the input system is built, it's not very easy to add a 'truly' blocking 
method that is using the underlying input stream for blocking I/O. I'd rather 
not add a method that simulates blocking input, since it's quite easy to do 
this yourself:

Key key = terminal.readInput();
while(key == null) {
    Thread.sleep(1);
    key = terminal.readInput();
}

This should do the trick and shouldn't consume any CPU. If sleeping for 1 ms is 
too much, you can probably try Thread.yield() instead.

Original comment by mab...@gmail.com on 7 Oct 2012 at 3:19

GoogleCodeExporter commented 9 years ago
I tough use the sleep method to solve this issue. I was afraid a lack of 
responsiveness with this solution. I'll try the yield method. Is this solution 
you've chosen in the GUI layer?

Thanks for your answer and help. 

Chris

Le 7 oct. 2012 à 05:19, lanterna@googlecode.com a écrit :

Original comment by christia...@gmail.com on 7 Oct 2012 at 1:07

GoogleCodeExporter commented 9 years ago
No, I'm doing sleep(1):

if(!repainted) {
    try {
        Thread.sleep(1);
    }
    catch(InterruptedException e) {}
}

I did a quick test and it seems both Thread.yield() and Thread.sleep(0) will 
cause one CPU core to max load. Thread.sleep(0, 1) (and of course 
Thread.sleep(1) ) seemed to work though.

public class YieldTest {
    public static void main(String[] args) throws InterruptedException {
        while(true) {
            Thread.sleep(0, 1);
        }
    }
}

Original comment by mab...@gmail.com on 8 Oct 2012 at 1:32

GoogleCodeExporter commented 9 years ago
Fine, thanks you :)

Christian

Le 8 oct. 2012 à 03:32, lanterna@googlecode.com a écrit :

Original comment by christia...@gmail.com on 8 Oct 2012 at 6:37

GoogleCodeExporter commented 9 years ago
Do I understand correctly?  It is your intention never to add a blocking 
version of readInput?

This seems like a fundamental design flaw to me, since every individual 
application will now need to pick a sleep value, and make a decision to trade 
off responsiveness for CPU usage.  Particularly in virtualized environments, 
where machine resources are over-committed, and one machine can be running 
dozens of OSes, each with dozens of idle applications, it is not appropriate to 
have every application on the system wake up 1000 times per second just to poll 
for input and discover that it has nothing to do.

One easy possibility would be to add a blocking readInput call that is 
initially implemented using the sleep technique you descrive.  That way, 
Lanterna at least appears to offer a blocking call, and you leave the door open 
in the future if you change your mind and want to offer a better implementation.

Original comment by p.r.do...@gmail.com on 19 Jun 2013 at 11:55

GoogleCodeExporter commented 9 years ago
True, but if you use blocking I/O then the screen won't be able to 
redraw/refresh the screen until there is input available. I chose this polling 
approach mostly because I didn't want to multi-thread it (and also, for my 
purposes of writing this library, sleep(1) wasn't a problem).

However, with the next major revision of Lanterna, I'm trying to make the whole 
stack thread-safe and could probably put both input and output on separate 
threads; truly enabling blocking input. But then again, there would be another 
thread doing polling of the input queue to find out if there's anything new...

Original comment by mab...@gmail.com on 24 Jun 2013 at 11:45

GoogleCodeExporter commented 9 years ago
I believe that this is a very, very, very important feature for any server 
application using lanterna. I am evaluating using it myself (kudos btw, nice 
lib so far!) and the only two stumbling blocks so far have been the privateness 
of the util functions to en/disable local echo, linemode etc. in TelnetTerminal 
-- and the lack of a blocking readInput.

I'd suggest using the current read(buf,off,len) implementation of the 
InputStream derived guy in TelnetTerminal, adding a nonblocking parameter to 
it, and change the fillBuffer() stuff from:

if(inputStream.available() > 0) {
 fillBuffer();
}
if(bytesInBuffer == 0) {
 return -1;
}

to:

if (inputStream.availabe() > 0 || !nonblocking) {
  fillBuffer();
}
while (bytesInBuffer == 0) {
  if (nonblocking)
    return -1;
  else
    fillBuffer();
}

with appropriate 

then, implement read(buf, off, len) by either read(buf, off, len, true) or
read(buf, off, len, false), and make the four-argument version public. Also,
implement read() by read(_, _, _, false).

Rationale: Yes, this blocks the thread. But usually there's two sides to a 
socket connection you are interested in. There's the reader side and the writer 
side. There's potentially four kinds of interactions that I see (use-case 
wise): server-push: write, client-push: read, client-request-reply: 
read-and-then-write, server-client-reply: write-and-then-read.

In server-push, obviously, I know that I want to push.
In client-push, I'd like to be notified, and then react.
In client-request-reply, I'd like to be notified, and then react (and reply).
In server-client-reply, I'd like to write, and then be notified of an answer.

This also goes along with an architecture that gives each connection one thread 
to play on. The Terminal (and Screen) instances there just live on that one 
thread. My computational loop(s) live on (an)other thread(s). Input from the 
threads are transported to the other threads in a thread-safe manner already 
anyways. I'd like to be able to just sleep on the client, waiting on input. If 
the computational loop decides it wants to push something, it would just 
interrupt the thread (the read from a socket's input stream should throw an 
InterruptedIOException as suggested by the (closed) bugreport at 
http://bugs.java.com/view_bug.do?bug_id=4905626 ), I'd handle the 
InterruptedIOException in my client, knowing that I'd be pushing something 
next; push; and return to my read-block-sleep.

With this approach, don't you think this is suitable for implementation? (I'll 
be trying, but thought I'd ask if you, being familiar with the code obviously, 
see a glaring problem with it). It should be giving both blocking and 
non-blocking I/O depending on your needs and design (1:1 thread:telnetTerminal 
or 1:n thread:telnetTerminal).

Original comment by ben...@multimud.net on 2 Sep 2014 at 8:12

GoogleCodeExporter commented 9 years ago
Sounds reasonable. Given that you are mentioning TelnetTerminal, are you using 
Lanterna 3 by any chance?

Original comment by mab...@gmail.com on 21 Sep 2014 at 2:14