diff --git a/library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/JsProcess.kt b/library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/JsProcess.kt index 0190baa..706f4cc 100644 --- a/library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/JsProcess.kt +++ b/library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/JsProcess.kt @@ -21,3 +21,7 @@ package io.matthewnelson.kmp.process.internal /** [docs](https://nodejs.org/api/process.html#processpid) */ @JsName("pid") internal external val process_pid: Int + +/** [docs](https://nodejs.org/api/process.html#processversions) */ +@JsName("versions") +internal external val process_versions: dynamic diff --git a/library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/JsStream.kt b/library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/JsStream.kt index b1081c8..89c3e04 100644 --- a/library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/JsStream.kt +++ b/library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/JsStream.kt @@ -29,6 +29,8 @@ internal open external class stream_Readable { eventName: String, listener: Function, ): stream_Readable + + internal fun destroy() } /** [docs](https://nodejs.org/api/stream.html#class-streamwritable) */ diff --git a/library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/NodeJsProcess.kt b/library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/NodeJsProcess.kt index 89d7634..bdbcf57 100644 --- a/library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/NodeJsProcess.kt +++ b/library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/NodeJsProcess.kt @@ -18,6 +18,7 @@ package io.matthewnelson.kmp.process.internal import io.matthewnelson.kmp.file.File +import io.matthewnelson.kmp.file.errorCodeOrNull import io.matthewnelson.kmp.process.* internal class NodeJsProcess internal constructor( @@ -39,33 +40,90 @@ internal class NodeJsProcess internal constructor( INIT, ) { + private var _exitCode: Int? = null + override fun destroy(): Process { val wasDestroyed = !isDestroyed isDestroyed = true - if (!jsProcess.killed && isAlive) { - // TODO: check result. check error - jsProcess.kill(destroySignal.name) + @Suppress("UNUSED_VARIABLE") + val error: Throwable? = if (!jsProcess.killed && isAlive) { + try { + jsProcess.kill(destroySignal.name) + null + } catch (t: Throwable) { + val code = t.errorCodeOrNull + var destroySelfIfAlive = false + + // https://github.com/05nelsonm/kmp-process/issues/108 + run { + if (!IsWindows) return@run + if (code != "EPERM") return@run + + val (major, minor, patch) = try { + val split = (process_versions["uv"] as String).split('.') + Triple(split[0].toInt(), split[1].toInt(), split[2].toInt()) + } catch (_: Throwable) { + return@run + } + + // libuv 1.48.0 bad windows implementation + if (major == 1 && minor == 48 && patch == 0) { + destroySelfIfAlive = true + } + } + + if (code == "ESRCH") { + destroySelfIfAlive = true + } + + var error: Throwable? = t + + if (destroySelfIfAlive && isAlive) { + // Still registering as "alive" w/o exit code, but no process + // found with our PID. Unable to kill b/c no process, it's just + // gone... Self-assign exit code and move on. + _exitCode = destroySignal.code + jsProcess.stdin?.end() + jsProcess.stdout?.destroy() + jsProcess.stderr?.destroy() + error = null + } + + error + } + } else { + null } if (wasDestroyed) jsProcess.unref() + // TODO: Handle errors Issue #109 + return this } // @Throws(IllegalStateException::class) override fun exitCode(): Int { - jsProcess.exitCode?.toInt()?.let { return it } + _exitCode?.let { return it } + + jsProcess.exitCode?.toInt()?.let { + _exitCode = it + return it + } jsProcess.signalCode?.let { signal -> - return try { + val code = try { Signal.valueOf(signal) } catch (_: IllegalArgumentException) { destroySignal }.code + + _exitCode = code + return code } - throw IllegalStateException("Process hasn't exited") + return _exitCode ?: throw IllegalStateException("Process hasn't exited") } override fun pid(): Int { diff --git a/library/process/src/nativeMain/kotlin/io/matthewnelson/kmp/process/internal/NativeProcess.kt b/library/process/src/nativeMain/kotlin/io/matthewnelson/kmp/process/internal/NativeProcess.kt index d7bd7d1..5ff5fb5 100644 --- a/library/process/src/nativeMain/kotlin/io/matthewnelson/kmp/process/internal/NativeProcess.kt +++ b/library/process/src/nativeMain/kotlin/io/matthewnelson/kmp/process/internal/NativeProcess.kt @@ -80,21 +80,36 @@ internal constructor( val hasBeenDestroyed = isDestroyed isDestroyed = true - if (isAlive) { - val s = when (destroySignal) { + @Suppress("UNUSED_VARIABLE") + val killErrno = if (isAlive) { + val sig = when (destroySignal) { Signal.SIGTERM -> SIGTERM Signal.SIGKILL -> SIGKILL } - kill(pid, s) + var errno = if (kill(pid, sig) == -1) { + errno + } else { + null + } + + // Always call isAlive whether there was an error + // or not to ensure _exitCode is set. + if (!isAlive) { + errno = null + } - isAlive + errno + } else { + null } - try { + @Suppress("UNUSED_VARIABLE") + val closeError = try { handle.close() - } catch (_: IOException) { - // TODO: exception handler + null + } catch (t: IOException) { + t } if (!hasBeenDestroyed) { @@ -109,6 +124,8 @@ internal constructor( ?.result } + // TODO: Handle errors Issue #109 + this }