-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Get process PID, use it to send SIGTERM
#120
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package scala.sys.process | ||
|
||
import java.lang.{Process => JProcess} | ||
import sbt.{Level, Logger} | ||
import scala.util.control.NonFatal | ||
|
||
case class ProcessWithPid(process: Process, pid: Option[Long]) | ||
|
||
object ProcessWithPid { | ||
private def reflectJProcess(p: Process.SimpleProcess): JProcess = { | ||
val field = p.getClass.getDeclaredField("p") | ||
field.setAccessible(true) | ||
field.get(p).asInstanceOf[JProcess] | ||
} | ||
|
||
// Java 9+ has a `Process#pid()` method, but Java 8 and below have a private `Process#pid` field | ||
// We first try to reflect on the method and then fall back to reflecting on the field | ||
private def reflectJProcessPid(p: JProcess): Long = | ||
try { | ||
val method = classOf[JProcess].getMethod("pid") | ||
method.invoke(p) match { | ||
case pid: java.lang.Long => pid | ||
case pid => throw new RuntimeException(s"Expected process PID ($pid) to be a Long, but it was a ${pid.getClass.getName}") | ||
} | ||
Comment on lines
+20
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you're content with requiring everyone who develops Otherwise, compiling this on Java 8 fails with an error
|
||
} catch { | ||
case e: NoSuchMethodException => | ||
val field = p.getClass.getDeclaredField("pid") | ||
field.setAccessible(true) | ||
field.getLong(p) | ||
} | ||
|
||
def apply(process: Process, log: Logger): ProcessWithPid = | ||
try { | ||
process match { | ||
case p: Process.SimpleProcess => | ||
val jp = reflectJProcess(p) | ||
val pid = reflectJProcessPid(jp) | ||
ProcessWithPid(process, Some(pid)) | ||
|
||
case p => | ||
throw new RuntimeException(s"Expected app process to be a Process.SimpleProcess but it was a ${p.getClass.getName}") | ||
} | ||
} catch { | ||
case NonFatal(e) => | ||
log.log(Level.Warn, s"Failed to determine process PID: $e") | ||
ProcessWithPid(process, None) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,22 +17,38 @@ | |
package spray.revolver | ||
|
||
import java.lang.{Runtime => JRuntime} | ||
import java.util.concurrent.TimeUnit | ||
import sbt.{Logger, ProjectRef} | ||
|
||
import scala.sys.process.Process | ||
import scala.sys.process.ProcessWithPid | ||
|
||
/** | ||
* A token which we put into the SBT state to hold the Process of an application running in the background. | ||
*/ | ||
case class AppProcess(projectRef: ProjectRef, consoleColor: String, log: Logger)(process: Process) { | ||
case class AppProcess(projectRef: ProjectRef, consoleColor: String, log: Logger)(process: ProcessWithPid) { | ||
val shutdownHook = createShutdownHook("... killing ...") | ||
|
||
private def destroyProcess(): Unit = process.process.destroy() | ||
|
||
private def killProcess(pid: Long): Unit = { | ||
val exited = try { | ||
JRuntime.getRuntime.exec(s"kill -15 $pid").waitFor(10, TimeUnit.SECONDS) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Open to suggestions on how long this waits |
||
} catch { case e: InterruptedException => true } | ||
|
||
if (!exited) destroyProcess() | ||
} | ||
|
||
private def stopProcess(): Int = { | ||
process.pid.fold(destroyProcess())(killProcess) | ||
process.process.exitValue() | ||
} | ||
|
||
def createShutdownHook(msg: => String) = | ||
new Thread(new Runnable { | ||
def run() { | ||
if (isRunning) { | ||
log.info(msg) | ||
process.destroy() | ||
stopProcess() | ||
} | ||
} | ||
}) | ||
|
@@ -42,7 +58,7 @@ case class AppProcess(projectRef: ProjectRef, consoleColor: String, log: Logger) | |
val watchThread = { | ||
val thread = new Thread(new Runnable { | ||
def run() { | ||
val code = process.exitValue() | ||
val code = process.process.exitValue() | ||
finishState = Some(code) | ||
log.info("... finished with exit code %d" format code) | ||
unregisterShutdownHook() | ||
|
@@ -58,8 +74,7 @@ case class AppProcess(projectRef: ProjectRef, consoleColor: String, log: Logger) | |
|
||
def stop() { | ||
unregisterShutdownHook() | ||
process.destroy() | ||
process.exitValue() | ||
stopProcess() | ||
} | ||
|
||
def registerShutdownHook() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has to be in the
scala.sys.process
package so it has access toProcess.SimpleProcess
, which is package private