mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +00:00
perf(agent): run diagnostic cleanup off startup thread (#2147)
* perf(agent): run stage cleanup off startup thread * fix(agent): preserve active diagnostic stages * test(agent): verify active stage locks cross-process
This commit is contained in:
1 parent
6a681bcbc2
commit
c8a76e7cce
4 files changed
+61
-7
No files matched your search
@@ -17,7 +17,7 @@ class App : Application() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
runCatching { EnhancedSessionLogShare.cleanupAbandonedStages(this) }
|
||||
Thread({ runCatching { EnhancedSessionLogShare.cleanupAbandonedStages(this) } }, "diagnostic-stage-cleanup").start()
|
||||
com.droiddeck.launcher.ui.Themes.loadGradient(this)
|
||||
com.droiddeck.launcher.ui.BuildWatermark.install(this)
|
||||
HomeApp.trackRecents(this)
|
||||
|
||||
@@ -51,17 +51,21 @@ internal object EnhancedSessionLogShare {
|
||||
|
||||
internal class DiagnosticStageLease internal constructor(
|
||||
val directory: File,
|
||||
private val canonicalPath: String,
|
||||
private val channel: FileChannel,
|
||||
private val lock: FileLock,
|
||||
) : Closeable {
|
||||
override fun close() {
|
||||
try {
|
||||
lock.release()
|
||||
} finally {
|
||||
synchronized(stageLifecycleMonitor) {
|
||||
activeStagePaths.remove(canonicalPath)
|
||||
try {
|
||||
channel.close()
|
||||
lock.release()
|
||||
} finally {
|
||||
directory.deleteRecursively()
|
||||
try {
|
||||
channel.close()
|
||||
} finally {
|
||||
directory.deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,6 +121,7 @@ internal object EnhancedSessionLogShare {
|
||||
override fun initialValue(): SecureSourceOpener? = null
|
||||
}
|
||||
private val stageLifecycleMonitor = Any()
|
||||
private val activeStagePaths = mutableSetOf<String>()
|
||||
|
||||
internal fun acquireDiagnosticStage(context: Context, outputName: String): DiagnosticStageLease {
|
||||
val cache = context.cacheDir
|
||||
@@ -133,7 +138,9 @@ internal object EnhancedSessionLogShare {
|
||||
try {
|
||||
val lock = channel.tryLock()
|
||||
?: throw IOException("Could not lock the diagnostic staging directory")
|
||||
DiagnosticStageLease(stage, channel, lock)
|
||||
val canonicalPath = stage.canonicalPath
|
||||
activeStagePaths += canonicalPath
|
||||
DiagnosticStageLease(stage, canonicalPath, channel, lock)
|
||||
} catch (error: Exception) {
|
||||
channel.close()
|
||||
stage.deleteRecursively()
|
||||
@@ -149,6 +156,7 @@ internal object EnhancedSessionLogShare {
|
||||
cache.listFiles()?.forEach { stage ->
|
||||
if (!stage.isDirectory || Files.isSymbolicLink(stage.toPath()) ||
|
||||
stage.canonicalFile.parentFile != canonicalCache || !isDiagnosticStage(stage.name)) return@forEach
|
||||
if (stage.canonicalPath in activeStagePaths) return@forEach
|
||||
val active = File(stage, ".active")
|
||||
if (Files.isSymbolicLink(active.toPath())) return@forEach
|
||||
val channel = runCatching {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.droiddeck.launcher.session;
|
||||
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
public final class DiagnosticStageLockProbe {
|
||||
public static void main(String[] args) throws Exception {
|
||||
try (FileChannel channel = FileChannel.open(Path.of(args[0]), StandardOpenOption.WRITE)) {
|
||||
FileLock lock = channel.tryLock();
|
||||
if (lock == null) {
|
||||
System.exit(0);
|
||||
}
|
||||
lock.release();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import java.text.SimpleDateFormat
|
||||
import java.util.UUID
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.zip.ZipFile
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
@@ -34,6 +35,32 @@ class SessionLogShareZipTest {
|
||||
EnhancedSessionLogShare.cleanupAbandonedStages(app)
|
||||
assertFalse(abandoned.exists())
|
||||
assertTrue(active.directory.isDirectory)
|
||||
EnhancedSessionLogShare.cleanupAbandonedStages(app)
|
||||
assertTrue(active.directory.isDirectory)
|
||||
val probeClassResource = checkNotNull(
|
||||
DiagnosticStageLockProbe::class.java.getResource("DiagnosticStageLockProbe.class"),
|
||||
)
|
||||
val probeClass = File(probeClassResource.toURI())
|
||||
var classPathRoot = checkNotNull(probeClass.parentFile)
|
||||
repeat(4) { classPathRoot = checkNotNull(classPathRoot.parentFile) }
|
||||
val classPath = classPathRoot.absolutePath
|
||||
val javaName = if (checkNotNull(System.getProperty("os.name")).startsWith("Windows")) {
|
||||
"java.exe"
|
||||
} else {
|
||||
"java"
|
||||
}
|
||||
val java = File(File(System.getProperty("java.home"), "bin"), javaName)
|
||||
val probe = ProcessBuilder(
|
||||
java.absolutePath,
|
||||
"-cp",
|
||||
classPath,
|
||||
DiagnosticStageLockProbe::class.java.name,
|
||||
File(active.directory, ".active").absolutePath,
|
||||
).redirectErrorStream(true).start()
|
||||
val completed = probe.waitFor(5, TimeUnit.SECONDS)
|
||||
if (!completed) probe.destroyForcibly()
|
||||
assertTrue("lock probe timed out", completed)
|
||||
assertEquals("cleanup released the active stage lock", 0, probe.exitValue())
|
||||
} finally {
|
||||
active.close()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user