Created
December 18, 2013 20:38
-
-
Save huntc/8029479 to your computer and use it in GitHub Desktop.
The Ringo actor for https://github.com/typesafehub/js-engine#javascript-engine The following project shows an application of the js-engine: https://github.com/typesafehub/sbt-jshint-plugin#sbt-jshint-plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.typesafe.jse | |
import akka.actor._ | |
import scala.collection.mutable.ListBuffer | |
import scala.concurrent.blocking | |
import java.io._ | |
import akka.contrib.process.StreamEvents.Ack | |
import akka.contrib.process.{Sink, Source} | |
import scala.collection.immutable | |
import com.typesafe.jse.Engine.ExecuteJs | |
import org.ringojs.tools.RingoRunner | |
/** | |
* Declares an in-JVM Ringo based JavaScript engine. The actor is expected to be | |
* associated with a blocking dispatcher as its use of Jdk streams are blocking. | |
*/ | |
class Ringo(ioDispatcherId: String) extends Engine { | |
// The main objective of this actor implementation is to establish actors for both the execution of | |
// Ringo code (Ringo's execution is blocking), and actors for the source of stdio (which is also blocking). | |
// This actor is then a conduit of the IO as a result of execution. | |
val stdoutOs = new PipedOutputStream() | |
val stderrOs = new PipedOutputStream() | |
val stdoutIs = new PipedInputStream(stdoutOs) | |
val stderrIs = new PipedInputStream(stderrOs) | |
def receive = { | |
case ExecuteJs(source, args, timeout, timeoutExitValue, modulePaths) => | |
val requester = sender | |
// Create an input stream and close it immediately as it isn't going to be used. | |
val stdinOs = new PipedOutputStream() | |
val stdinIs = new PipedInputStream(stdinOs) | |
try { | |
val stdinSink = context.actorOf(Sink.props(stdinOs, ioDispatcherId = ioDispatcherId), "stdin") | |
val stdoutSource = context.actorOf(Source.props(stdoutIs, self, ioDispatcherId = ioDispatcherId), "stdout") | |
val stderrSource = context.actorOf(Source.props(stderrIs, self, ioDispatcherId = ioDispatcherId), "stderr") | |
context.become(engineIOHandler( | |
stdinSink, stdoutSource, stderrSource, | |
requester, | |
Ack, | |
timeout, timeoutExitValue | |
)) | |
context.actorOf(RingoShell.props( | |
source.getParentFile.getCanonicalFile, | |
immutable.Seq(source.getCanonicalPath) ++ args, | |
modulePaths, | |
stdinIs, new PrintStream(stdoutOs), new PrintStream(stderrOs) | |
), "ringo-shell") ! RingoShell.Execute | |
// We don't need an input stream so close it out straight away. | |
stdinSink ! PoisonPill | |
} finally { | |
blocking { | |
closeSafely(stdinIs) | |
closeSafely(stdinOs) | |
} | |
} | |
} | |
def closeSafely(closable: Closeable): Unit = { | |
try { | |
closable.close() | |
} catch { | |
case _: Exception => | |
} | |
} | |
override def postStop() = { | |
// Be paranoid and ensure that all resources are cleared up. | |
blocking { | |
closeSafely(stderrIs) | |
closeSafely(stdoutIs) | |
closeSafely(stderrOs) | |
closeSafely(stdoutOs) | |
} | |
} | |
} | |
object Ringo { | |
/** | |
* Give me a Ringo props. | |
*/ | |
def props(ioDispatcherId: String = "blocking-process-io-dispatcher"): Props = { | |
Props(classOf[Ringo], ioDispatcherId) | |
.withDispatcher(ioDispatcherId) | |
} | |
} | |
/** | |
* Manage the execution of the Ringo shell setting up its environment, running the main entry point | |
* and sending its parent the exit code when we're done. | |
*/ | |
private[jse] class RingoShell( | |
moduleBase: File, | |
args: immutable.Seq[String], | |
modulePaths: immutable.Seq[String], | |
stdinIs: InputStream, | |
stdoutOs: PrintStream, | |
stderrOs: PrintStream | |
) extends Actor with ActorLogging { | |
import RingoShell._ | |
// Formulate arguments to the Ringo shell. | |
val lb = ListBuffer[String]() | |
lb ++= Seq( | |
"--optlevel=-1", | |
s"--modules=${moduleBase.getCanonicalPath}" | |
) | |
lb ++= modulePaths.flatMap(Seq("-modules", _)) | |
lb ++= args | |
val shellArgs = lb.toArray | |
def receive = { | |
case Execute => | |
blocking { | |
try { | |
if (log.isDebugEnabled) { | |
log.debug("Invoking Ringo with {}", shellArgs) | |
} | |
new RingoRunner(stdinIs, stdoutOs, stderrOs).run(shellArgs) | |
} finally { | |
stdoutOs.close() | |
stderrOs.close() | |
} | |
} | |
sender ! 0 | |
} | |
} | |
private[jse] object RingoShell { | |
def props( | |
moduleBase: File, | |
args: immutable.Seq[String], | |
modulePaths: immutable.Seq[String], | |
stdinIs: InputStream, | |
stdoutOs: PrintStream, | |
stderrOs: PrintStream | |
): Props = { | |
Props(classOf[RingoShell], moduleBase, args, modulePaths, stdinIs, stdoutOs, stderrOs) | |
} | |
case object Execute | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment