Skip to content

Preserve KSP metadata context during delayed cache updates - #49

Merged
jbarr21 merged 1 commit into
uber/android/8.8.0from
jb/ksp-action-cache-context
Oct 20, 2026
Merged

jbarr21 merged 1 commit into
uber/android/8.8.0from
jb/ksp-action-cache-context

Conversation

@jbarr21

@jbarr21 jbarr21 commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fix unnecessary KotlinKsp execution caused by incorrect metadata during deferred local action-cache updates.

When KotlinCompile completes, Bazel updates the earlier KSP cache entry using KotlinCompile’s input metadata provider. That provider lacks KSP-specific tool inputs, corrupting the stored fingerprint and causing an unnecessary cache miss on the next revalidation.

  • Retain the originating KSP InputMetadataProvider and ArtifactExpander in immutable delayed-action context.
  • Use that context when completing the deferred KSP cache update.
  • Preserve cache invalidation when KSP tools or processors actually change.
  • No rules_kotlin change or resource-invalidation policy change is required.

Test Plan

Regression coverage

  • The three new regression cases fail on the stock base at the intended cache/checksum assertions and pass with the fix.
  • The complete ActionCacheCheckerTest class passes: 51 cases, zero failures.
  • Negative controls establish a healthy cache hit, then modify ksp2_invoker.jar or symbol-processing.jar and verify invalidation with DIFFERENT_FILES.

Android integration

Validated on jbarr-j2k using locally compiled stock/patched Bazel 8.8.0 and the same locally built rules_kotlin archive.

  • Bounded resource-value edit: unnecessary KSP worker executions decrease from 1 to 0. KSP execution inputs, arguments, environment, platform, and outputs match; all 24 project resource jars remain byte-identical.
  • Full Helix first-value-edit replay:
    • Build duration: 1430.416s → 1149.037s, saving 281.379s (19.671%).
    • KSP worker executions: 1,557 → 0.
    • Javac worker executions: 5,658 → 5,659.

The full-app result is one paired affected incremental build, not a clean-build or fleet-wide estimate. Measured edits used identical resource policies and worker limits, with network/disk cache fallback disabled. Warmup times were excluded.

Builds were serialized, telemetry and shared-cache uploads were disabled, and the Android checkout was restored afterward.

Revert Plan

Revert this PR.

No cache expunge or data migration is required. After applying the fix, existing malformed local cache entries may incur one final miss when first revalidated; subsequent updates preserve the correct KSP metadata.

Summary:
Intent:
- Prevent redundant KotlinKsp execution after resource-only edits when the KSP execution inputs are unchanged.

Changes:
- Retain the originating KSP InputMetadataProvider and ArtifactExpander in immutable delayed-action context. KotlinCompile's action-scoped metadata does not contain all KSP tool inputs; using it for the deferred KSP cache update corrupts the stored input fingerprint.
- Replay the KSP context when KotlinCompile completes the delayed local action-cache update. Preserve invalidation for actual KSP tool changes.
- Add regression coverage through the enabled tracker/callback path with disjoint action-scoped metadata providers. No rules_kotlin change or resource-avoidance-policy change is required.

Test Plan:
- Regression target: //src/test/java/com/google/devtools/build/lib/actions:ActionsTests, filtered to com.google.devtools.build.lib.actions.ActionCacheCheckerTest#delayedKspCacheUpdate.*. The three new cases fail on the stock base at the intended cache/checksum assertions and pass with this fix. Full ActionCacheCheckerTest class: 51 cases, zero failures. Tool-change controls first establish a healthy cache hit, then mutate ksp2_invoker.jar or symbol-processing.jar and assert DIFFERENT_FILES.
- Real Android replay on jbarr-j2k: use locally compiled stock/patched Bazel 8.8.0 via USE_LOCAL_BAZEL=1 and the same locally built rules_kotlin release archive. Seed //libraries/foundation/ui/uview-injection:src_release in arm64/Dagger context with actual KSP/Kotlin workers, then make an XML value edit. Stock unnecessarily executes one KSP worker; patched uses the local cache (zero KSP workers). All selected KSP execution inputs/argv/environment/platform/outputs match; 24 project resource jars are byte-identical. Duration: 5.639s to 2.431s.
- Paired Helix //apps/presidio/helix/app-apk:bin_debug first-value-edit replay: 1430.416s to 1149.037s (281.379s / 19.671% shorter); KSP workers 1557 to 0; Javac workers 5658 to 5659. Same source, rules archive and Java/Android tools; 24 jobs, pinned CPUs, same RAM/worker caps, shrink off, current unused_classes policy. Network/disk cache fallback disabled during measured edits. Warmup excluded (different cache sources). This is one paired affected incremental build, not a clean-build or fleet-wide estimate.
- Builds were serialized, test telemetry/shared-cache uploads disabled, and the Android checkout restored. No whole-Bazel-suite claim.

Revert Plan:
Revert this PR. Existing malformed local cache entries may incur one final miss when revalidated; no global cache expunge is required.
Comment on lines +307 to +310
delayedCachedActionsData.put(
getKey(action),
new DelayedCachedActionData(
action, token, inputMetadataProvider, outputMetadataStore, artifactExpander));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each pending entry now also holds KSP's full input metadata. If the matching KotlinCompile never runs (cache hit, failure, interrupted build), the entry stays in memory until KSP runs again for that target. Should we clear this map at the end of each build?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it already is, since the whole tracker is thrown away when the build ends

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! I assumed it was kept after the build

Comment on lines 893 to +898
updateActionCache(
delayedActionData.action,
delayedActionData.token,
inputMetadataProvider,
delayedActionData.inputMetadataProvider,
delayedActionData.outputMetadataStore,
artifactExpander,
delayedActionData.artifactExpander,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reads KSP's metadata after KSP has already finished. Is that still safe to use at this point?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it's safe since KSP's input data is saved before KSP runs and never changes afterward. reading it later gives the same values as reading it right away

@azad-uber-2 azad-uber-2 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jbarr21
jbarr21 merged commit cc5bdd4 into uber/android/8.8.0 Oct 20, 2026
3 checks passed
@jbarr21
jbarr21 deleted the jb/ksp-action-cache-context branch October 21, 2026 17:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants