mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-04 12:07:09 +00:00
fix(sidecar): cap stability poll independently of settle budget
The previous shape halved durationMillis between waitForAppToSettle and the structural poll, then hammered hierarchy() at 80ms intervals - on Maestro this stacked enough RPCs that hierarchy fetches began timing out under load and the run stalled. Pass the full budget to waitForAppToSettle and cap the follow-up structural poll at 600ms with a 120ms interval, so the device sees at most a handful of extra hierarchy reads per step.
This commit is contained in:
1 parent
d0cf5a98c7
commit
a10791e00f
1 file changed
+17
-17
@@ -16,7 +16,8 @@ interface DriverBackend {
|
|||||||
fun metrics(bundleId: String): MetricsSample
|
fun metrics(bundleId: String): MetricsSample
|
||||||
}
|
}
|
||||||
|
|
||||||
internal const val STABILITY_POLL_INTERVAL_MILLIS = 80L
|
internal const val STABILITY_POLL_INTERVAL_MILLIS = 120L
|
||||||
|
internal const val STABILITY_POLL_CAP_MILLIS = 600L
|
||||||
|
|
||||||
// pollUntilStable returns as soon as two consecutive snapshot() results are
|
// pollUntilStable returns as soon as two consecutive snapshot() results are
|
||||||
// structurally identical, capped at timeoutMillis. snapshot must omit
|
// structurally identical, capped at timeoutMillis. snapshot must omit
|
||||||
@@ -424,17 +425,17 @@ class StubDriverBackend(private val platform: String) : DriverBackend {
|
|||||||
|
|
||||||
override fun waitForIdle(durationMillis: Long) {
|
override fun waitForIdle(durationMillis: Long) {
|
||||||
// Two-stage settle: an mAnimating poll catches View-system animations,
|
// Two-stage settle: an mAnimating poll catches View-system animations,
|
||||||
// then a structural-hash poll catches Compose cross-fades where two
|
// then a short structural-hash poll catches Compose cross-fades where
|
||||||
// composables are simultaneously alive but mAnimating is already false.
|
// two composables are simultaneously alive but mAnimating is already
|
||||||
|
// false. The structural poll is hard-capped so we don't hammer the
|
||||||
|
// device with repeat hierarchy fetches when the UI never stabilizes.
|
||||||
if (durationMillis <= 0) return
|
if (durationMillis <= 0) return
|
||||||
val animationBudget = durationMillis / 2
|
val animationDeadline = System.currentTimeMillis() + durationMillis
|
||||||
val animationDeadline = System.currentTimeMillis() + animationBudget
|
|
||||||
while (System.currentTimeMillis() < animationDeadline) {
|
while (System.currentTimeMillis() < animationDeadline) {
|
||||||
if (isDeviceIdle()) break
|
if (isDeviceIdle()) break
|
||||||
Thread.sleep(IDLE_POLL_INTERVAL_MILLIS)
|
Thread.sleep(IDLE_POLL_INTERVAL_MILLIS)
|
||||||
}
|
}
|
||||||
val structuralBudget = durationMillis - animationBudget
|
pollUntilStable(STABILITY_POLL_CAP_MILLIS) { structuralHash(hierarchy()) }
|
||||||
pollUntilStable(structuralBudget) { structuralHash(hierarchy()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isDeviceIdle(): Boolean {
|
private fun isDeviceIdle(): Boolean {
|
||||||
@@ -504,12 +505,13 @@ class MaestroDriverBackend(private val serial: String?) : DriverBackend {
|
|||||||
override fun waitForIdle(durationMillis: Long) {
|
override fun waitForIdle(durationMillis: Long) {
|
||||||
// waitForAppToSettle returns early on Compose cross-fade transitions
|
// waitForAppToSettle returns early on Compose cross-fade transitions
|
||||||
// where both source and destination composables are semantically
|
// where both source and destination composables are semantically
|
||||||
// alive; a follow-up structural-hash poll lands on a single stable
|
// alive; a follow-up short structural-hash poll lands on a single
|
||||||
// frame before the runner reads hierarchy + screenshot concurrently.
|
// stable frame before the runner reads hierarchy + screenshot
|
||||||
val maestroBudget = durationMillis / 2
|
// concurrently. The structural poll is hard-capped independently of
|
||||||
val stabilityBudget = durationMillis - maestroBudget
|
// durationMillis so we don't pile on hierarchy fetches when settle
|
||||||
driver.waitForAppToSettle(null, null, maestroBudget.toInt())
|
// never converges.
|
||||||
pollUntilStable(stabilityBudget) {
|
driver.waitForAppToSettle(null, null, durationMillis.toInt())
|
||||||
|
pollUntilStable(STABILITY_POLL_CAP_MILLIS) {
|
||||||
structuralHash(
|
structuralHash(
|
||||||
com.fasterxml.jackson.module.kotlin.jacksonObjectMapper()
|
com.fasterxml.jackson.module.kotlin.jacksonObjectMapper()
|
||||||
.writeValueAsString(driver.contentDescriptor(false)),
|
.writeValueAsString(driver.contentDescriptor(false)),
|
||||||
@@ -701,10 +703,8 @@ class IosDriverBackend(private val udid: String) : DriverBackend {
|
|||||||
override fun recentLogs(sinceUnixMillis: Long, minLevel: String): List<LogLine> = emptyList()
|
override fun recentLogs(sinceUnixMillis: Long, minLevel: String): List<LogLine> = emptyList()
|
||||||
|
|
||||||
override fun waitForIdle(durationMillis: Long) = withReconnect {
|
override fun waitForIdle(durationMillis: Long) = withReconnect {
|
||||||
val maestroBudget = durationMillis / 2
|
driver.waitForAppToSettle(null, null, durationMillis.toInt())
|
||||||
val stabilityBudget = durationMillis - maestroBudget
|
pollUntilStable(STABILITY_POLL_CAP_MILLIS) {
|
||||||
driver.waitForAppToSettle(null, null, maestroBudget.toInt())
|
|
||||||
pollUntilStable(stabilityBudget) {
|
|
||||||
structuralHash(
|
structuralHash(
|
||||||
com.fasterxml.jackson.module.kotlin.jacksonObjectMapper()
|
com.fasterxml.jackson.module.kotlin.jacksonObjectMapper()
|
||||||
.writeValueAsString(driver.contentDescriptor(false)),
|
.writeValueAsString(driver.contentDescriptor(false)),
|
||||||
|
|||||||
Reference in new issue
Block a user