diff options
| author | Lena <lena@omega> | 2026-07-01 00:00:00 +0000 |
|---|---|---|
| committer | Lena <lena@omega> | 2026-07-01 00:00:00 +0000 |
| commit | d2745c0cca84ea006fa44edb4d9bbebffd35d917 (patch) | |
| tree | 6be8757635cf311506936fdba9b2bc5975e4de27 /app/src/main | |
| parent | f3889d4fb5659c94f4ec957219bebada8d1a67fb (diff) | |
| download | rsend-d2745c0cca84ea006fa44edb4d9bbebffd35d917.tar.gz | |
app: kill rsync when a sync is stopped
A stopped worker (schedule replaced, constraints lost) left rsync
running detached until its own network timeout, including a window
where a WiFi-only sync kept pushing on another network. A watchdog
coroutine destroys the process on cancellation, which also closes its
pipes and unblocks the log reader; the worker rethrows cancellation
instead of logging it as a folder error.
Diffstat (limited to 'app/src/main')
| -rw-r--r-- | app/src/main/java/invalid/lena/rsend/RsyncRunner.kt | 20 | ||||
| -rw-r--r-- | app/src/main/java/invalid/lena/rsend/SyncWorker.kt | 3 |
2 files changed, 19 insertions, 4 deletions
diff --git a/app/src/main/java/invalid/lena/rsend/RsyncRunner.kt b/app/src/main/java/invalid/lena/rsend/RsyncRunner.kt index d8f8b93..c37e3ce 100644 --- a/app/src/main/java/invalid/lena/rsend/RsyncRunner.kt +++ b/app/src/main/java/invalid/lena/rsend/RsyncRunner.kt @@ -1,6 +1,9 @@ package invalid.lena.rsend import android.content.Context +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch // RsyncRunner builds and runs the rsync invocation for one folder, streaming // rsync's output into the log. It uses the bundled rsync and rsh binaries and @@ -34,14 +37,23 @@ object RsyncRunner { private fun withSlash(p: String): String = if (p.endsWith("/")) p else "$p/" // runFolder execs rsync for one folder, appending every output line to the - // log, and returns rsync's exit code. - fun runFolder(ctx: Context, remote: Remote, f: Folder, log: SyncLog): Int { + // log, and returns rsync's exit code. Blocks the calling thread (run it on + // an IO dispatcher). If the caller is cancelled (worker stopped, schedule + // replaced, constraints lost) the watchdog kills rsync rather than leave it + // running detached; killing it also closes its pipes, which unblocks the + // log reader below. destroy() on an already-exited process is a no-op. + suspend fun runFolder(ctx: Context, remote: Remote, f: Folder, log: SyncLog): Int = coroutineScope { val cmd = listOf(Native.rsync(ctx).absolutePath) + args(Native.rsh(ctx).absolutePath, remote, f) log.line("rsync ${cmd.drop(1).joinToString(" ")}") val pb = ProcessBuilder(cmd).redirectErrorStream(true) pb.environment().putAll(Keys.env(ctx, remote.port)) val p = pb.start() - p.inputStream.bufferedReader().forEachLine { log.line(it) } - return p.waitFor() + val watchdog = launch { try { awaitCancellation() } finally { p.destroy() } } + try { + p.inputStream.bufferedReader().forEachLine { log.line(it) } + p.waitFor() + } finally { + watchdog.cancel() + } } } diff --git a/app/src/main/java/invalid/lena/rsend/SyncWorker.kt b/app/src/main/java/invalid/lena/rsend/SyncWorker.kt index a70a3e6..1b14673 100644 --- a/app/src/main/java/invalid/lena/rsend/SyncWorker.kt +++ b/app/src/main/java/invalid/lena/rsend/SyncWorker.kt @@ -61,6 +61,9 @@ class SyncWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, 1 } else try { RsyncRunner.runFolder(ctx, cfg.remote, f, log) + } catch (e: CancellationException) { + log.line("cancelled") + throw e } catch (e: Exception) { log.line("error: ${e.message}") 1 |