how to cancel ConsoleReader.readLine()
Asked Answered
S

3

10

first of all, i'm learning scala and new to the java world. I want to create a console and run this console as a service that you could start and stop. I was able to run a ConsoleReader into an Actor but i don't know how to stop properly the ConsoleReader. Here is the code :

import eu.badmood.util.trace
import scala.actors.Actor._

import tools.jline.console.ConsoleReader

object Main {

  def main(args:Array[String]){
    //start the console
    Console.start(message => {
      //handle console inputs
      message match {
        case "exit" => Console.stop()
        case _ => trace(message)
      }
    })

    //try to stop the console after a time delay
    Thread.sleep(2000)
    Console.stop()

  }

}

object Console {

  private val consoleReader = new ConsoleReader()

  private var running = false

  def start(handler:(String)=>Unit){
    running = true
    actor{
      while (running){
        handler(consoleReader.readLine("\33[32m> \33[0m"))
      }
    }
  }

  def stop(){
    //how to cancel an active call to ConsoleReader.readLine ?
    running = false
  }

}

I'm also looking for any advice concerning this code !

Standee answered 17/7, 2011 at 9:47 Comment(2)
I'm not sure what closing the console means. Why do you need to "close" it ?Cathicathie
if i call the stop method directly (not after handling a console input), the ConsoleReader will still waiting for an input. I want to know if it's possible to cancel a readLine callStandee
M
3

The underlying call to read a characters from the input is blocking. On non-Windows platform, it will use System.in.read() and on Windows it will use org.fusesource.jansi.internal.WindowsSupport.readByte.

So your challenge is to cause that blocking call to return when you want to stop your console service. See http://www.javaspecialists.eu/archive/Issue153.html and Is it possible to read from a InputStream with a timeout? for some ideas... Once you figure that out, have read return -1 when your console service stops, so that ConsoleReader thinks it's done. You'll need ConsoleReader to use your version of that call:

  • If you are on Windows, you'll probably need to override tools.jline.AnsiWindowsTerminal and use the ConsoleReader constructor that takes a Terminal (otherwise AnsiWindowsTerminal will just use WindowsSupport.readByte` directly)
  • On unix, there is one ConsoleReader constructor that takes an InputStream, you could provide your own wrapper around System.in

A few more thoughts:

  • There is a scala.Console object already, so for less confusion name yours differently.
  • System.in is a unique resource, so you probably need to ensure that only one caller uses Console.readLine at a time. Right now start will directly call readLine and multiple callers can call start. Probably the console service can readLine and maintain a list of handlers.
Mundt answered 17/7, 2011 at 15:49 Comment(3)
ouch ! i thought that it will be less difficult ! i still have a lot to learn : ) thank you for your answer !Standee
@OXMO456, you can do simpler if you don't care for the console prompt remaining until the program terminates: just start ConsoleReader in it's own thread and use thread.setDaemon(true) so that it doesn't prevent the program from terminating.Mundt
thank you ! I'll leave that aside until a better understanding of the basics !Standee
S
1

Assuming that ConsoleReader.readLine responds to thread interruption, you could rewrite Console to use a Thread which you could then interrupt to stop it.

object Console {

  private val consoleReader = new ConsoleReader()
  private var thread : Thread = _

  def start(handler:(String)=>Unit) : Thread = {
    thread = new Thread(new Runnable {
      override def run() {
        try {
          while (true) {
            handler(consoleReader.readLine("\33[32m> \33[0m"))
          }
        } catch {
          case ie: InterruptedException =>
        }
      }
    })
    thread.start()
    thread
  }

  def stop() {
    thread.interrupt()
  }

}
Salvatore answered 18/7, 2011 at 18:37 Comment(4)
Hi, thank you for your reply. I just tried your code, but the Thread doesn't seems interrupted. Just get a warning during execution : Failed to query stty columnsjava.lang.InterruptedException at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:485) ...Standee
It's possible that consoleReader.readLine does not respond to interrupts. Most of the Java std library's blocking methods do, however, especially with regards to IO. I just checked the javadocs, actually, and it looks like it doesn't throw an InterruptedExceptionSalvatore
Typically when an IO method blocks but is not interruptible you can get around this by, instead of calling thread.interrupt() to stop it, you can instead just close the inputstream or what not that it's using, which usually will throw an IOException that you can catch.Salvatore
i tried to close the input stream : consoleReader.getInput().close() but the prompt is still here : ( and after pressing return i get an error... i'll try again tomorrowStandee
L
0

You may overwrite your ConsoleReader InputStream. IMHO this is reasonable well because of STDIN is a "slow" stream. Please improve example for your needs. This is only sketch, but it works:

def createReader() =
terminal.synchronized {
  val reader = new ConsoleReader
  terminal.enableEcho()
  reader.setBellEnabled(false)
  reader.setInput(new InputStreamWrapper(reader.getInput())) // turn on InterruptedException for InputStream.read
  reader
}

with InputStream wrapper:

class InputStreamWrapper(is: InputStream, val timeout: Long = 50) extends FilterInputStream(is) {
@tailrec
final override def read(): Int = {
  if (is.available() != 0)
    is.read()
  else {
    Thread.sleep(timeout)
    read()
  }
}

}

P.S. I tried to use NIO - a lot of troubles with System.in (especially crossplatform). I returned to this variant. CPU load is near 0%. This is suitable for such interactive application.

Lunt answered 23/11, 2012 at 13:15 Comment(1)
Yup. API was changed... since 2012Lunt

© 2022 - 2024 — McMap. All rights reserved.