aboutsummaryrefslogtreecommitdiff
path: root/app/src/main
diff options
context:
space:
mode:
authorLena <lena@omega>2026-07-01 00:00:00 +0000
committerLena <lena@omega>2026-07-01 00:00:00 +0000
commitd2745c0cca84ea006fa44edb4d9bbebffd35d917 (patch)
tree6be8757635cf311506936fdba9b2bc5975e4de27 /app/src/main
parentf3889d4fb5659c94f4ec957219bebada8d1a67fb (diff)
downloadrsend-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.kt20
-rw-r--r--app/src/main/java/invalid/lena/rsend/SyncWorker.kt3
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