From 8dd1b344eb7453022519e38666d13f9841b739e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20S=C5=82omkowski?= Date: Wed, 7 May 2025 00:08:49 +0200 Subject: [PATCH 1/2] Add tryWait() to Child object --- kommand-core/kommand_core.h | 2 ++ kommand-core/src/bin/leaks_test.rs | 15 ++++++-- kommand-core/src/process/child.rs | 6 ++++ kommand-core/src/result.rs | 31 +++++++++++++---- .../kotlin/com/kgit2/kommand/process/Child.kt | 3 ++ .../kotlin/com/kgit2/kommand/ChildTest.kt | 34 +++++++++++++++++-- .../com/kgit2/kommand/process/Child.jvm.kt | 9 +++++ .../kotlin/com/kgit2/kommand/Extension.kt | 16 +++++---- .../com/kgit2/kommand/process/Child.native.kt | 21 +++++++----- 9 files changed, 110 insertions(+), 27 deletions(-) diff --git a/kommand-core/kommand_core.h b/kommand-core/kommand_core.h index 125d1bf..4041a0c 100644 --- a/kommand-core/kommand_core.h +++ b/kommand-core/kommand_core.h @@ -103,6 +103,8 @@ unsigned int id_child(const void *child); struct IntResult wait_child(const void *child); +struct IntResult try_wait_child(const void *child); + /** * The returned [Output] will empty * if convert the [Child.stdout] and [Child.stderr] to buffered diff --git a/kommand-core/src/bin/leaks_test.rs b/kommand-core/src/bin/leaks_test.rs index 4e7f630..5b2f0b7 100644 --- a/kommand-core/src/bin/leaks_test.rs +++ b/kommand-core/src/bin/leaks_test.rs @@ -2,14 +2,14 @@ use std::ffi::{c_char, c_ulonglong}; use kommand_core::ffi_util::{as_cstring, into_string}; use kommand_core::io::{drop_stdout, read_line_stdout}; -use kommand_core::process::{buffered_stdout_child, Stdio}; +use kommand_core::process::{buffered_stdout_child, try_wait_child, Stdio}; use kommand_core::process::{drop_child, wait_child}; use kommand_core::process::{drop_command, new_command, spawn_command, stdout_command}; use kommand_core::result::ErrorType; fn main() { unsafe { - (0..1).for_each(|i| { + (0..100).for_each(|i| { let command = new_command(as_cstring("target/debug/kommand-echo").as_ptr()); // arg_command(command, as_cstring("echo").as_ptr()); stdout_command(command, Stdio::Pipe); @@ -21,6 +21,12 @@ fn main() { panic!("{}", into_string(result.err)) }; + if try_wait_child(child).ok == -2 { + println!("Child is not finished"); + } else { + println!("Child is finished"); + } + let stdout = buffered_stdout_child(child); if stdout.is_null() { panic!("stdout is null"); @@ -44,6 +50,11 @@ fn main() { } wait_child(child); + let status = try_wait_child(child); + if status.ok < 0 { + drop_command(command); + panic!("No proper status code returned: {}", status.ok); + } drop_stdout(stdout); drop_child(child); drop_command(command); diff --git a/kommand-core/src/process/child.rs b/kommand-core/src/process/child.rs index a7939f3..4d59a9c 100644 --- a/kommand-core/src/process/child.rs +++ b/kommand-core/src/process/child.rs @@ -68,6 +68,12 @@ pub extern "C" fn wait_child(mut child: *const c_void) -> IntResult { child.wait().into() } +#[no_mangle] +pub extern "C" fn try_wait_child(mut child: *const c_void) -> IntResult { + let child = as_child_mut(&mut child); + child.try_wait().into() +} + /// The returned [Output] will empty /// if convert the [Child.stdout] and [Child.stderr] to buffered /// with [buffered_stdout_child] and [buffered_stderr_child] diff --git a/kommand-core/src/result.rs b/kommand-core/src/result.rs index e2dc65e..314f6bd 100644 --- a/kommand-core/src/result.rs +++ b/kommand-core/src/result.rs @@ -117,16 +117,33 @@ impl From, E>> for VoidResult { impl From> for IntResult { fn from(value: io::Result) -> Self { match value { - Ok(status) => match status.code() { + Ok(status) => Ok(Some(status)), + Err(e) => Err(e), + } + .into() + } +} + +impl From>> for IntResult { + fn from(value: io::Result>) -> Self { + match value { + Ok(status) => match status { None => IntResult { - ok: -1, - err: into_cstring("No exit code"), + ok: -2, + err: into_cstring("Application still running"), error_type: ErrorType::None, }, - Some(code) => IntResult { - ok: code, - err: std::ptr::null_mut() as *mut c_char, - error_type: ErrorType::None, + Some(status) => match status.code() { + None => IntResult { + ok: -1, + err: into_cstring("No exit code"), + error_type: ErrorType::None, + }, + Some(code) => IntResult { + ok: code, + err: std::ptr::null_mut() as *mut c_char, + error_type: ErrorType::None, + }, }, }, Err(e) => IntResult { diff --git a/src/commonMain/kotlin/com/kgit2/kommand/process/Child.kt b/src/commonMain/kotlin/com/kgit2/kommand/process/Child.kt index 2496bba..1d10a66 100644 --- a/src/commonMain/kotlin/com/kgit2/kommand/process/Child.kt +++ b/src/commonMain/kotlin/com/kgit2/kommand/process/Child.kt @@ -20,6 +20,9 @@ expect class Child { @Throws(KommandException::class) fun wait(): Int + @Throws(KommandException::class) + fun tryWait(): Int? + @Throws(KommandException::class) fun waitWithOutput(): Output } diff --git a/src/commonTest/kotlin/com/kgit2/kommand/ChildTest.kt b/src/commonTest/kotlin/com/kgit2/kommand/ChildTest.kt index d6c03a0..3fd6529 100644 --- a/src/commonTest/kotlin/com/kgit2/kommand/ChildTest.kt +++ b/src/commonTest/kotlin/com/kgit2/kommand/ChildTest.kt @@ -4,6 +4,7 @@ import com.kgit2.kommand.process.Command import com.kgit2.kommand.process.Stdio import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertNull class ChildTest { @Test @@ -12,6 +13,7 @@ class ChildTest { .arg("interval") .stdout(Stdio.Pipe) val child = command.spawn() + assertNull(child.tryWait()) val output = child.waitWithOutput() val expect = listOf("0", "1", "2", "3", "4").joinToString("\n") + "\n" assertEquals(expect, output.stdout) @@ -29,6 +31,7 @@ class ChildTest { .arg("interval") .stdout(Stdio.Pipe) val child = command.spawn() + assertNull(child.tryWait()) // var line = child.bufferedStdout()?.readLine() // var expect = 0 // while (!line.isNullOrEmpty()) { @@ -39,7 +42,25 @@ class ChildTest { child.bufferedStdout()?.lines()?.withIndex()?.forEach { assertEquals(it.index.toString(), it.value) } - child.wait() + val returnCode = child.wait() + assertEquals(returnCode, child.tryWait()) + assertEquals(returnCode, child.wait()) + } + + @Test + fun spawnTryWait() { + val command = Command(platformEchoPath()) + .args("interval", "5") // waits 500 ms + .stdout(Stdio.Pipe) + val child = command.spawn() + + repeat(3) { + assertNull(child.tryWait()) + println(child.bufferedStdout()?.readLine()) + } + + val returnCode = child.wait() + assertEquals(0, returnCode) } @Test @@ -49,12 +70,15 @@ class ChildTest { .stdin(Stdio.Pipe) .stdout(Stdio.Pipe) val child = command.spawn() + assertNull(child.tryWait()) val expect = "Hello World!" child.bufferedStdin()?.writeLine(expect) child.bufferedStdin()?.flush() val line = child.bufferedStdout()?.readLine() assertEquals(expect, line) - child.wait() + val returnCode = child.wait() + assertEquals(returnCode, child.tryWait()) + assertEquals(returnCode, child.wait()) } @Test @@ -68,9 +92,13 @@ class ChildTest { for (j in 0..10000) { child.bufferedStdin()?.writeLine("[$j]$expect") child.bufferedStdin()?.flush() + assertNull(child.tryWait()) val line = child.bufferedStdout()?.readLine() assertEquals("[$j]$expect", line) } - child.wait() + val returnCode = child.wait() + assertEquals(returnCode, child.tryWait()) + assertEquals(returnCode, child.wait()) + assertEquals(returnCode, child.tryWait()) } } diff --git a/src/jvmMain/kotlin/com/kgit2/kommand/process/Child.jvm.kt b/src/jvmMain/kotlin/com/kgit2/kommand/process/Child.jvm.kt index 22c23cf..ddaf132 100644 --- a/src/jvmMain/kotlin/com/kgit2/kommand/process/Child.jvm.kt +++ b/src/jvmMain/kotlin/com/kgit2/kommand/process/Child.jvm.kt @@ -5,6 +5,7 @@ import com.kgit2.kommand.exception.KommandException import com.kgit2.kommand.io.BufferedReader import com.kgit2.kommand.io.BufferedWriter import com.kgit2.kommand.io.Output +import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicReference actual class Child( @@ -45,6 +46,14 @@ actual class Child( return process.waitFor() } + @Throws(KommandException::class) + actual fun tryWait(): Int? { + return when (process.waitFor(0, TimeUnit.MICROSECONDS)) { + true -> wait() + false -> return null + } + } + @Throws(KommandException::class) actual fun waitWithOutput(): Output { stdin.get()?.close() diff --git a/src/nativeMain/kotlin/com/kgit2/kommand/Extension.kt b/src/nativeMain/kotlin/com/kgit2/kommand/Extension.kt index dae3fb1..b02b46d 100644 --- a/src/nativeMain/kotlin/com/kgit2/kommand/Extension.kt +++ b/src/nativeMain/kotlin/com/kgit2/kommand/Extension.kt @@ -9,12 +9,7 @@ import kommand_core.drop_output import kommand_core.drop_string import kommand_core.into_output import kommand_core.void_to_string -import kotlinx.cinterop.ByteVar -import kotlinx.cinterop.CPointer -import kotlinx.cinterop.CValue -import kotlinx.cinterop.memScoped -import kotlinx.cinterop.pointed -import kotlinx.cinterop.toKString +import kotlinx.cinterop.* fun CPointer.asString(): String { val result = this.toKString() @@ -53,6 +48,15 @@ fun Output.Companion.from(result: CValue): Output = mem } } +fun Int.Companion.fromOptional(result: CValue): Int? = memScoped { + return@memScoped if (result.ptr.pointed.ok == -2) { + // Application still running + null + } else { + Int.Companion.from(result) + } +} + @Throws(KommandException::class) fun Int.Companion.from(result: CValue): Int = memScoped { if (result.ptr.pointed.err != null) { diff --git a/src/nativeMain/kotlin/com/kgit2/kommand/process/Child.native.kt b/src/nativeMain/kotlin/com/kgit2/kommand/process/Child.native.kt index 1ea8f59..0a38faa 100644 --- a/src/nativeMain/kotlin/com/kgit2/kommand/process/Child.native.kt +++ b/src/nativeMain/kotlin/com/kgit2/kommand/process/Child.native.kt @@ -2,19 +2,14 @@ package com.kgit2.kommand.process import com.kgit2.kommand.exception.KommandException import com.kgit2.kommand.from +import com.kgit2.kommand.fromOptional import com.kgit2.kommand.io.BufferedReader import com.kgit2.kommand.io.BufferedWriter import com.kgit2.kommand.io.Output import com.kgit2.kommand.io.ReaderType import com.kgit2.kommand.unwrap -import kommand_core.buffered_stderr_child -import kommand_core.buffered_stdin_child -import kommand_core.buffered_stdout_child -import kommand_core.drop_child -import kommand_core.id_child -import kommand_core.kill_child -import kommand_core.wait_child -import kommand_core.wait_with_output_child +import kommand_core.* +import kommand_core.try_wait_child import kotlinx.atomicfu.atomic import kotlinx.atomicfu.locks.SynchronizedObject import kotlinx.cinterop.COpaquePointer @@ -23,7 +18,7 @@ import kotlin.native.ref.createCleaner actual class Child( private var inner: COpaquePointer? -): SynchronizedObject() { +) : SynchronizedObject() { private var stdin: AtomicReference = AtomicReference(null) private var stdout: AtomicReference = AtomicReference(null) private var stderr: AtomicReference = AtomicReference(null) @@ -68,6 +63,14 @@ actual class Child( Int.from(wait_child(inner)) } + @Throws(KommandException::class) + actual fun tryWait(): Int? { + return when (Int.fromOptional(try_wait_child(inner))) { + null -> null + else -> wait() + } + } + @Throws(KommandException::class) actual fun waitWithOutput(): Output = run { stdin.getAndSet(null)?.close() From a43619cba4d58846b7005c6d5a821bf3e1cec6cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20S=C5=82omkowski?= Date: Fri, 17 Apr 2026 00:47:09 +0200 Subject: [PATCH 2/2] Apply nitpicks from Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/jvmMain/kotlin/com/kgit2/kommand/process/Child.jvm.kt | 2 +- .../kotlin/com/kgit2/kommand/process/Child.native.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/jvmMain/kotlin/com/kgit2/kommand/process/Child.jvm.kt b/src/jvmMain/kotlin/com/kgit2/kommand/process/Child.jvm.kt index ddaf132..be586bc 100644 --- a/src/jvmMain/kotlin/com/kgit2/kommand/process/Child.jvm.kt +++ b/src/jvmMain/kotlin/com/kgit2/kommand/process/Child.jvm.kt @@ -50,7 +50,7 @@ actual class Child( actual fun tryWait(): Int? { return when (process.waitFor(0, TimeUnit.MICROSECONDS)) { true -> wait() - false -> return null + false -> null } } diff --git a/src/nativeMain/kotlin/com/kgit2/kommand/process/Child.native.kt b/src/nativeMain/kotlin/com/kgit2/kommand/process/Child.native.kt index 0a38faa..0e81381 100644 --- a/src/nativeMain/kotlin/com/kgit2/kommand/process/Child.native.kt +++ b/src/nativeMain/kotlin/com/kgit2/kommand/process/Child.native.kt @@ -65,7 +65,8 @@ actual class Child( @Throws(KommandException::class) actual fun tryWait(): Int? { - return when (Int.fromOptional(try_wait_child(inner))) { + val result = Int.fromOptional(try_wait_child(inner)) + return when (result) { null -> null else -> wait() }