Follow-up to #551 / PR #554.
RectManagerEdtGuard closes the Compose 1.12 RectManager EDT race for every app that does not register debounced rect callbacks. A documented residual remains: an app explicitly using Modifier.onLayoutRectChanged / onFirstVisible / onVisibilityChanged (with debounce) can still get one EDT dispatch if a layout pass runs >16 ms past a mid-pass re-arm with no layout/semantics notification in between — because a real (non-pinned) deadline lowers minDebounceDeadline and the pass-end scheduleDebounceCallback arms a live EDT timer that the guard can only cancel at the next observable hook.
Proposed full closure (opt-in, layered)
Swap RectManager.executeDelayed (private field) for a scene-thread implementation at owner creation:
- The
ExecuteDelayed implementation itself can be static (@Suppress("INVISIBLE_REFERENCE"), no dynamic Proxy) — it schedules delay(millis) on the scene's coroutine context, exactly like the guard's post-frame wakeup.
- The only reflective operation is a single
Field.set on RectManager.executeDelayed.
- GraalVM native-image: needs one reachability-metadata field entry for
androidx.compose.ui.spatial.RectManager; must verify on a real runGraalvmNative build that a reflective write to a final instance field works with the metadata (final-field writes are the sensitive part).
- Layered behavior: attempt the swap at first owner; on success the residual disappears entirely; on any failure (missing metadata, future Compose renames the field) log one warning and keep the current guard — never worse than today.
Exit criterion
This issue becomes obsolete if JetBrains fixes the upstream bug (desktop postDelayed runs on skiko's hardcoded MainUIDispatcher/EDT — their code carries TODO CMP-7153; a standalone no-framework reproducer crashing 3/3 off-EDT and never on the EDT has been prepared for the YouTrack report). Once the upstream fix ships, both the guard and this follow-up can be removed for that Compose version.
Follow-up to #551 / PR #554.
RectManagerEdtGuardcloses the Compose 1.12 RectManager EDT race for every app that does not register debounced rect callbacks. A documented residual remains: an app explicitly usingModifier.onLayoutRectChanged/onFirstVisible/onVisibilityChanged(with debounce) can still get one EDT dispatch if a layout pass runs >16 ms past a mid-pass re-arm with no layout/semantics notification in between — because a real (non-pinned) deadline lowersminDebounceDeadlineand the pass-endscheduleDebounceCallbackarms a live EDT timer that the guard can only cancel at the next observable hook.Proposed full closure (opt-in, layered)
Swap
RectManager.executeDelayed(private field) for a scene-thread implementation at owner creation:ExecuteDelayedimplementation itself can be static (@Suppress("INVISIBLE_REFERENCE"), no dynamic Proxy) — it schedulesdelay(millis)on the scene's coroutine context, exactly like the guard's post-frame wakeup.Field.setonRectManager.executeDelayed.androidx.compose.ui.spatial.RectManager; must verify on a realrunGraalvmNativebuild that a reflective write to a final instance field works with the metadata (final-field writes are the sensitive part).Exit criterion
This issue becomes obsolete if JetBrains fixes the upstream bug (desktop
postDelayedruns on skiko's hardcodedMainUIDispatcher/EDT — their code carriesTODO CMP-7153; a standalone no-framework reproducer crashing 3/3 off-EDT and never on the EDT has been prepared for the YouTrack report). Once the upstream fix ships, both the guard and this follow-up can be removed for that Compose version.