Skip to content

perf: skip reloadWorkspace() when compile tasks are all UP-TO-DATE#222

Merged
wenytang-ms merged 1 commit intodevelopfrom
perf/skip-reload-on-noop-compile
Apr 3, 2026
Merged

perf: skip reloadWorkspace() when compile tasks are all UP-TO-DATE#222
wenytang-ms merged 1 commit intodevelopfrom
perf/skip-reload-on-noop-compile

Conversation

@wenytang-ms
Copy link
Copy Markdown
Contributor

Problem

After every buildTargetCompile, the server unconditionally calls reloadWorkspace() to detect source root changes from code generation frameworks (e.g., Protocol Buffers). However, reloadWorkspace() is expensive — it does a full Gradle Tooling API model query (getGradleSourceSets()) which takes ~1-2s per call.

This is particularly wasteful during Eclipse auto-build cycles. After the initial compile, subsequent auto-builds produce all UP-TO-DATE tasks (no code changed), but reloadWorkspace() still runs every time:

auto-build → BuildServerBuilder.build()
  → BSP buildTargetCompile → all tasks UP-TO-DATE (~100ms)
  → reloadWorkspace() → full Tooling API round-trip (~1-2s) ← wasted!
  → model unchanged → no didChange
  → but auto-build may trigger again due to other resource changes
  → repeat 3-4 times before converging

This adds ~4-8s of unnecessary Gradle Tooling API overhead after each test run or debug launch.

Root Cause

BuildTargetService.compile() always schedules reloadWorkspace() after compilation, regardless of whether any work was actually done:

if (!Boolean.getBoolean("bsp.plugin.reloadworkspace.disabled")) {
    CompletableFuture.runAsync(this::reloadWorkspace);  // always runs
}

The information about whether tasks were UP-TO-DATE is already available in CompileProgressReporter.statusChanged() via TaskSuccessResult.isUpToDate() and TaskSkippedResult, but compile() doesn't use it.

Fix

  • Add a hasExecutedWork flag to CompileProgressReporter that tracks whether any task performed actual work (not skipped, not UP-TO-DATE).
  • In compile(), only call reloadWorkspace() when reporter.hasExecutedWork() is true.

When all tasks are UP-TO-DATE, no new files were produced, so the project model cannot have changed — reloadWorkspace() is safely skipped.

Risk Assessment

Risk Level Mitigation
Missing model changes after UP-TO-DATE compile None UP-TO-DATE means Gradle confirmed no outputs changed. Model is derived from Gradle project structure, not compile outputs.
Code generation (protobuf, annotation processors) None If codegen tasks produce new files, they won't be UP-TO-DATE — hasExecutedWork will be true and reload will happen normally.
External changes to build files None External changes cause tasks to not be UP-TO-DATE on next compile.

Performance Impact

For a workspace with 5 Gradle subprojects after running a test:

Metric Before After
Redundant reloadWorkspace() calls 3-4 0
Wasted Gradle Tooling API time ~4-8s ~0s

Related: microsoft/vscode-gradle#1794 (fixed the infinite rebuild loop; this PR reduces remaining convergence overhead)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Improves compile-time performance by avoiding expensive workspace model reloads after buildTargetCompile when Gradle reports that all relevant tasks were skipped or UP-TO-DATE.

Changes:

  • Track whether any compile-related Gradle task performed real work via CompileProgressReporter.hasExecutedWork().
  • Conditionally schedule reloadWorkspace() only when work was executed.
  • Add informational logging around the reload decision.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
server/src/main/java/com/microsoft/java/bs/core/internal/services/BuildTargetService.java Only schedules reloadWorkspace() after compile when tasks executed work; adds logging.
server/src/main/java/com/microsoft/java/bs/core/internal/reporter/CompileProgressReporter.java Adds a flag to detect non-UP-TO-DATE/non-skipped task outcomes and exposes it to callers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

After every buildTargetCompile, the server unconditionally calls
reloadWorkspace() to detect source root changes from code generation
frameworks. However, reloadWorkspace() is expensive — it does a full
Gradle Tooling API model query (getGradleSourceSets) which takes 1-2s.

When all compile tasks are UP-TO-DATE (no work done), the project
model cannot have changed, so reloadWorkspace() is unnecessary. This
commonly happens during auto-build cycles where Eclipse triggers
repeated incremental builds with no code changes.

Track whether any compile task performed actual work via the existing
TaskSuccessResult.isUpToDate() / TaskSkippedResult checks in
CompileProgressReporter. Skip reloadWorkspace() when all tasks were
no-op, saving ~1-2s per redundant auto-build cycle.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@wenytang-ms wenytang-ms requested a review from chagong April 2, 2026 09:16
@wenytang-ms wenytang-ms force-pushed the perf/skip-reload-on-noop-compile branch from 01b5d95 to 5ff123c Compare April 2, 2026 09:16
@wenytang-ms wenytang-ms merged commit 60a6e0b into develop Apr 3, 2026
7 checks passed
@wenytang-ms wenytang-ms deleted the perf/skip-reload-on-noop-compile branch April 3, 2026 01:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants