Skip to content

Commit

Permalink
Kill spawned NPM and NODE processes
Browse files Browse the repository at this point in the history
  • Loading branch information
DGolubets committed Feb 2, 2018
1 parent d823f37 commit a6e9edf
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 16 deletions.
5 changes: 5 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ licenses += ("MIT", url("https://opensource.org/licenses/MIT"))
publishMavenStyle := false
publishArtifact in Test := false

libraryDependencies ++= Seq(
"net.java.dev.jna" % "jna" % "4.5.1",
"net.java.dev.jna" % "jna-platform" % "4.5.1"
)

import sbtrelease.ReleasePlugin.autoImport.ReleaseTransformations._

releaseProcess := Seq[ReleaseStep](
Expand Down
22 changes: 6 additions & 16 deletions src/main/scala/ru/dgolubets/sbt/npmassets/Npm.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package ru.dgolubets.sbt.npmassets

import java.io.File
import scala.collection.JavaConverters._

import ru.dgolubets.sbt.npmassets.util.ProcessUtil

/**
* NPM process manager
Expand All @@ -11,20 +12,7 @@ class Npm() {
private var process: Option[Process] = None

def start(scriptName: String, cwd: File, envVars: Map[String, String]): Unit = {
val os = sys.props("os.name").toLowerCase
var command = "npm run" :: scriptName :: Nil
command = os match {
case x if x contains "windows" => List("cmd", "/C") ++ command
case _ => command
}

val processBuilder = new ProcessBuilder(command.asJava)
processBuilder.directory(cwd)
processBuilder.environment().putAll(envVars.asJava)
processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT)
processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT)
val p = processBuilder.start()

val p = ProcessUtil.exec("npm", "run" :: scriptName :: Nil, envVars, Some(cwd))
process = Some(p)
}

Expand All @@ -35,7 +23,9 @@ class Npm() {
def isRunning: Boolean = process.exists(_.isAlive())

def stop(): Unit = {
process.foreach(_.destroyForcibly())
process.foreach { p =>
ProcessUtil.kill(p)
}
process = None
}
}
16 changes: 16 additions & 0 deletions src/main/scala/ru/dgolubets/sbt/npmassets/util/OsInfo.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ru.dgolubets.sbt.npmassets.util

import ru.dgolubets.sbt.npmassets.util.OsKind.OsKind

case class OsInfo(kind: OsKind)

object OsInfo {
def current: OsInfo = {
val os = sys.props("os.name").toLowerCase
if(os.contains("windows")){
OsInfo(OsKind.Windows)
} else {
OsInfo(OsKind.Linux)
}
}
}
6 changes: 6 additions & 0 deletions src/main/scala/ru/dgolubets/sbt/npmassets/util/OsKind.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package ru.dgolubets.sbt.npmassets.util

object OsKind extends Enumeration {
type OsKind = Value
val Linux, Windows = Value
}
70 changes: 70 additions & 0 deletions src/main/scala/ru/dgolubets/sbt/npmassets/util/ProcessUtil.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package ru.dgolubets.sbt.npmassets.util

import java.io.File

import com.sun.jna.Pointer
import com.sun.jna.platform.win32.{Kernel32, WinNT}

import scala.collection.JavaConverters._

object ProcessUtil {

private def getProcessId(p: Process): Int = {
p.getClass.getName match {
case "java.lang.UNIXProcess" =>
val f = p.getClass.getDeclaredField("pid")
try {
f.setAccessible(true)
f.getInt(p)
} finally {
f.setAccessible(false)
}
case "java.lang.Win32Process" | "java.lang.ProcessImpl" =>
val f = p.getClass.getDeclaredField("handle")
try {
f.setAccessible(true)
val handle = f.getLong(p)
val kernel = Kernel32.INSTANCE
val hand = new WinNT.HANDLE()
hand.setPointer(Pointer.createConstant(handle))
kernel.GetProcessId(hand)
} finally {
f.setAccessible(false)
}
case _ =>
-1
}
}

def exec(command: String,
args: Seq[String] = Seq.empty,
envVars: Map[String, String] = Map.empty,
cwd: Option[File] = None): Process = {
val commands = OsInfo.current.kind match {
case OsKind.Linux =>
// start process with new session id to kill it all later
"setsid" :: command :: args.toList
case OsKind.Windows =>
"cmd" :: "/C" :: command :: args.toList
}

val processBuilder = new ProcessBuilder(commands.asJava)
for (dir <- cwd) {
processBuilder.directory(dir)
}
processBuilder.environment().putAll(envVars.asJava)
processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT)
processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT)
processBuilder.start()
}

def kill(p: Process): Unit = {
val pid = getProcessId(p)
OsInfo.current.kind match {
case OsKind.Linux =>
Runtime.getRuntime.exec(s"pkill -s $pid")
case OsKind.Windows =>
Runtime.getRuntime.exec(s"taskkill /pid $pid /t /f")
}
}
}

0 comments on commit a6e9edf

Please sign in to comment.