mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 11:18:24 +00:00
fix(desktop): read now-playing command output while the command runs
runCommand waited for the process to exit before reading its stdout. On a busy Mac `ps -A -o comm=` prints ~100 KB, past the pipe buffer, so ps blocked on the write, waitFor hit the 5 s timeout and the macOS reader never saw Spotify or Music (and every poll stalled 5 s). Read stdout to EOF while a watchdog thread kills a command that outlives the timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
bc1f970dd9
commit
5b7cffc72d
+17
-7
@@ -25,6 +25,7 @@ import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
/** Reads what the OS says another app is playing right now. */
|
||||
interface OsNowPlayingReader {
|
||||
@@ -63,13 +64,22 @@ internal suspend fun runCommand(
|
||||
.start()
|
||||
process = started
|
||||
started.outputStream.close()
|
||||
// Wait before reading so a hung command cannot block the read forever. The outputs
|
||||
// here are a few KB, well under the pipe buffer, so the command never blocks on it.
|
||||
if (!started.waitFor(timeoutSeconds, TimeUnit.SECONDS) || started.exitValue() != 0) {
|
||||
null
|
||||
} else {
|
||||
started.inputStream.bufferedReader(Charsets.UTF_8).use { it.readText() }
|
||||
}
|
||||
// Read while the command runs: `ps -A -o comm=` prints ~100 KB on a busy Mac, past the
|
||||
// pipe buffer, so waiting for the exit before reading deadlocked until the timeout and
|
||||
// the reader never saw a player. The watchdog still kills a command that hangs, which
|
||||
// also ends the read.
|
||||
val watchdog =
|
||||
thread(isDaemon = true, name = "now-playing-command-timeout") {
|
||||
try {
|
||||
if (!started.waitFor(timeoutSeconds, TimeUnit.SECONDS)) started.destroyForcibly()
|
||||
} catch (_: InterruptedException) {
|
||||
// The command finished first.
|
||||
}
|
||||
}
|
||||
val output = started.inputStream.bufferedReader(Charsets.UTF_8).use { it.readText() }
|
||||
val exited = started.waitFor(timeoutSeconds, TimeUnit.SECONDS)
|
||||
watchdog.interrupt()
|
||||
if (!exited || started.exitValue() != 0) null else output
|
||||
} catch (e: InterruptedException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
|
||||
+22
@@ -22,9 +22,11 @@ package com.vitorpamplona.amethyst.desktop.nowPlaying
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.nip38UserStatuses.nowPlaying.NowPlayingSource
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import java.io.File
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class OsNowPlayingReaderTest {
|
||||
private val listNames =
|
||||
@@ -215,4 +217,24 @@ class OsNowPlayingReaderTest {
|
||||
assertEquals("ZuneMusic", WindowsNowPlayingReader.appLabel("Microsoft.ZuneMusic_8wekyb3d8bbwe!Microsoft.ZuneMusic"))
|
||||
assertEquals("308046B0AF4A39CB", WindowsNowPlayingReader.appLabel("308046B0AF4A39CB"))
|
||||
}
|
||||
|
||||
private val hasShell = File("/bin/sh").canExecute()
|
||||
|
||||
/** `ps -A -o comm=` prints ~100 KB on a busy Mac; reading only after the exit deadlocked on the pipe. */
|
||||
@Test
|
||||
fun runCommandReturnsOutputLargerThanThePipeBuffer() =
|
||||
runTest {
|
||||
if (!hasShell) return@runTest
|
||||
val output = runCommand(listOf("/bin/sh", "-c", "i=0; while [ \$i -lt 4000 ]; do echo 0123456789012345678901234567890123456789012345678; i=\$((i+1)); done"))
|
||||
assertEquals(4000 * 50, output?.length)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun runCommandGivesUpOnAHungCommand() =
|
||||
runTest {
|
||||
if (!hasShell) return@runTest
|
||||
val started = System.currentTimeMillis()
|
||||
assertNull(runCommand(listOf("/bin/sh", "-c", "sleep 30"), timeoutSeconds = 1))
|
||||
assertTrue(System.currentTimeMillis() - started < 10_000, "the timeout must end the call")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user