Skip to content

Make sites upgrade and repository cleanup run in parallel#3926

Open
jmendeza wants to merge 6 commits into
craftercms:support/4.xfrom
jmendeza:feature/8681-4x
Open

Make sites upgrade and repository cleanup run in parallel#3926
jmendeza wants to merge 6 commits into
craftercms:support/4.xfrom
jmendeza:feature/8681-4x

Conversation

@jmendeza
Copy link
Copy Markdown
Member

Make sites upgrade and repository cleanup run in parallel
craftercms/craftercms#8681

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Review Change Stack

Warning

Rate limit exceeded

@jmendeza has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 50 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a37b0ddc-4acf-4116-99b2-fb2145b4814b

📥 Commits

Reviewing files that changed from the base of the PR and between df2c965 and 3356859.

📒 Files selected for processing (1)
  • src/main/java/org/craftercms/studio/impl/v2/upgrade/StudioUpgradeManagerImpl.java

Walkthrough

This PR refactors startup cleanup and site upgrade workflows to support concurrent execution. Configuration properties for parallel thread counts and timeouts are added, dependency injection is converted from setter-based to constructor-based, and repository startup cleanup is reimplemented using ExecutorService for parallel site processing with proper lock management.

Changes

Concurrent Site Startup Cleanup and Upgrade Configuration

Layer / File(s) Summary
Configuration Properties and Constructor Dependency Injection
src/main/resources/crafter/studio/studio-config.yaml, src/main/java/org/craftercms/studio/impl/v2/repository/RepositoryStartupCleanup.java, src/main/java/org/craftercms/studio/impl/v2/upgrade/StudioUpgradeManagerImpl.java, src/main/resources/crafter/studio/studio-services-context.xml, src/main/resources/crafter/studio/studio-upgrade-context.xml
New concurrency configuration properties (studio.upgrade.sites.threadCount, studio.upgrade.sites.timeoutSeconds, studio.configuration.site.startup.cleanup.threadCount, .timeoutSeconds) are defined in YAML. RepositoryStartupCleanup and StudioUpgradeManagerImpl constructors are extended with int parameters for executor thread count and timeout values. Spring bean wiring is updated to inject these configuration values via constructor arguments, and public setter methods are removed from RepositoryStartupCleanup in favor of constructor-based injection with final fields.
Concurrent Repository Unlock and Cleanup Implementation
src/main/java/org/craftercms/studio/impl/v2/repository/RepositoryStartupCleanup.java
Startup repository cleanup is refactored from a sequential per-site stream loop to ExecutorService-backed concurrent execution with the configured thread count. A new unlockSiteRepositories(Site site) helper method performs per-site sandbox repository unlock and corrupted index removal with try/finally lock release semantics, and conditionally repeats for published repositories when present. The unlockSitesRepositories method now declares throws InterruptedException to propagate timeout-based termination when awaiting executor completion.
Parallel Site Upgrade Execution
src/main/java/org/craftercms/studio/impl/v2/upgrade/StudioUpgradeManagerImpl.java
StudioUpgradeManagerImpl now accepts executor thread count and timeout constructor parameters and overrides upgrade() to perform per-site upgrades concurrently using a fixed thread pool, waits up to the configured timeout, forces shutdown on timeout, aggregates per-site failures, and rethrows a combined UpgradeException if any failures occur.
Site Bootstrap Event Publishing
src/main/java/org/craftercms/studio/impl/v2/utils/spring/context/SiteBootstrapStateProviderImpl.java
SiteBootstrapStateProviderImpl implements ApplicationEventPublisherAware, stores the ApplicationEventPublisher, and publishes a SiteReadyEvent (siteId and site UUID) when a site is marked ready.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • craftercms/studio#3877: Modifies RepositoryStartupCleanup's per-site repository unlock and cleanup, overlapping published-repo handling.
  • craftercms/studio#3751: Touches corrupted .git/index removal logic in RepositoryStartupCleanup; related to git-index cleanup changes.
  • craftercms/studio#3781: Overlaps RepositoryStartupCleanup listener and unlock flow modifications.

Suggested reviewers

  • sumerjabri
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main objective: enabling parallel execution of sites upgrade and repository cleanup operations.
Description check ✅ Passed The description is related to the changeset, referencing the issue and stating the intent to parallelize sites upgrade and repository cleanup.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/org/craftercms/studio/impl/v2/repository/RepositoryStartupCleanup.java (1)

79-83: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve interrupt status and handle timeout termination explicitly.

InterruptedException is currently swallowed, and timeout completion is not enforced. This can make startup behavior non-deterministic under contention.

Proposed fix
 public void unlockRepositories() {
     logger.debug("Clean up git lock for all repositories.");
     try {
         unlockSitesRepositories();
+    } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+        logger.warn("Repository startup cleanup interrupted", e);
     } catch (Exception e) {
         logger.error("Error cleaning up git lock", e);
     }
 }

 protected void unlockSitesRepositories() throws InterruptedException {
-    try (ExecutorService taskExecutor = Executors.newFixedThreadPool(executorThreadCount)) {
-        for (Site site : siteService.getSitesByState(READY)) {
-            taskExecutor.execute(() -> unlockSiteRepositories(site));
-        }
-        taskExecutor.shutdown();
-        taskExecutor.awaitTermination(executorTimeoutSeconds, SECONDS);
-    }
+    ExecutorService taskExecutor = Executors.newFixedThreadPool(executorThreadCount);
+    try {
+        for (Site site : siteService.getSitesByState(READY)) {
+            taskExecutor.execute(() -> unlockSiteRepositories(site));
+        }
+        taskExecutor.shutdown();
+        if (!taskExecutor.awaitTermination(executorTimeoutSeconds, SECONDS)) {
+            logger.warn("Repository startup cleanup timed out after {} seconds", executorTimeoutSeconds);
+            taskExecutor.shutdownNow();
+        }
+    } finally {
+        if (!taskExecutor.isShutdown()) {
+            taskExecutor.shutdownNow();
+        }
+    }
 }

Also applies to: 91-93

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/org/craftercms/studio/impl/v2/repository/RepositoryStartupCleanup.java`
around lines 79 - 83, The current catch-all around unlockSitesRepositories()
(and the similar block at lines 91-93) swallows InterruptedException and doesn't
enforce timeouts; update both call sites to catch InterruptedException
separately, call Thread.currentThread().interrupt() to preserve the interrupt
status, and log a clear message, and enforce a bounded wait by invoking the
unlock logic via a cancellable task (e.g., submit unlockSitesRepositories() to
an ExecutorService and use Future.get(timeout, TimeUnit.SECONDS) or
CompletableFuture.orTimeout) so timeouts result in task cancellation and an
explicit timeout log/cleanup; keep the existing generic Exception catch for
other errors but ensure interrupt and timeout branches are handled first.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@src/main/java/org/craftercms/studio/impl/v2/repository/RepositoryStartupCleanup.java`:
- Around line 79-83: The current catch-all around unlockSitesRepositories() (and
the similar block at lines 91-93) swallows InterruptedException and doesn't
enforce timeouts; update both call sites to catch InterruptedException
separately, call Thread.currentThread().interrupt() to preserve the interrupt
status, and log a clear message, and enforce a bounded wait by invoking the
unlock logic via a cancellable task (e.g., submit unlockSitesRepositories() to
an ExecutorService and use Future.get(timeout, TimeUnit.SECONDS) or
CompletableFuture.orTimeout) so timeouts result in task cancellation and an
explicit timeout log/cleanup; keep the existing generic Exception catch for
other errors but ensure interrupt and timeout branches are handled first.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d5594f7d-a26d-4e35-97e4-f5b72d874cdd

📥 Commits

Reviewing files that changed from the base of the PR and between 886848b and a9ee127.

📒 Files selected for processing (5)
  • src/main/java/org/craftercms/studio/impl/v2/repository/RepositoryStartupCleanup.java
  • src/main/java/org/craftercms/studio/impl/v2/upgrade/StudioUpgradeManagerImpl.java
  • src/main/resources/crafter/studio/studio-config.yaml
  • src/main/resources/crafter/studio/studio-services-context.xml
  • src/main/resources/crafter/studio/studio-upgrade-context.xml

studio.upgrade.db.population.timeoutSeconds: 7200

# Number of threads for site upgrades (meaning the number of sites that will be upgraded in parallel)
studio.upgrade.sites.threadCount: 10
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@russdanner @sumerjabri please chime in on these defaults for the thead count and timeout

##################################################
studio.configuration.site.startup.cleanup.enabled: true
# Number of threads for site startup cleanup (meaning the number of sites that will be cleaned in parallel on startup)
studio.configuration.site.startup.cleanup.threadCount: 10
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@russdanner @sumerjabri please chime in on these defaults for the thead count and timeout

@jmendeza
Copy link
Copy Markdown
Member Author

@coderabbitai is this PR ready to move forward?

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me review the actual code changes in detail before giving you an assessment.


🏁 Script executed:

#!/bin/bash
# Read the main changed Java files
cat src/main/java/org/craftercms/studio/impl/v2/repository/RepositoryStartupCleanup.java
echo "---FILE SEPARATOR---"
cat src/main/java/org/craftercms/studio/impl/v2/upgrade/StudioUpgradeManagerImpl.java
echo "---FILE SEPARATOR---"
cat src/main/resources/crafter/studio/studio-config.yaml | grep -A 20 "startup.cleanup\|upgrade.sites"
echo "---FILE SEPARATOR---"
cat src/main/resources/crafter/studio/studio-services-context.xml | grep -A 30 "repositoryStartupCleanup"
echo "---FILE SEPARATOR---"
cat src/main/resources/crafter/studio/studio-upgrade-context.xml | grep -A 30 "upgradeManager\|siteUpgradeThreadCount"

Length of output: 27027


🏁 Script executed:

#!/bin/bash
# Check Java version requirements
cat pom.xml | grep -E "java.version|maven.compiler.source|maven.compiler.target|release" | head -20
echo "---"
# Check if there are existing uses of ExecutorService try-with-resources elsewhere
rg "try.*ExecutorService\|ExecutorService.*AutoCloseable" --type java -C2 | head -30
echo "---"
# Check GitRepositoryHelper.getRepository signature for null handling
rg "getRepository" --type java -n -C3 | grep -A3 "public.*getRepository" | head -40

Length of output: 4466


🏁 Script executed:

#!/bin/bash
# Check the full getRepository implementation to see if it can return null
sed -n '136,175p' src/main/java/org/craftercms/studio/api/v2/utils/GitRepositoryHelper.java

echo "---"
# Check Java version in pom.xml more broadly
grep -E "java|jdk|compiler|release|source|target" pom.xml | grep -iv "<!--" | head -30

echo "---"
# Check .mvn or other Java version hints
cat .mvn/jvm.config 2>/dev/null || echo "no jvm.config"
cat .mvn/maven.config 2>/dev/null || echo "no maven.config"
find . -name "*.properties" -path "*maven*" -exec cat {} \; 2>/dev/null | head -20

Length of output: 2948


🏁 Script executed:

#!/bin/bash
# Look for Java version in parent pom or properties
grep -n "java.version\|<release>\|<source>\|<target>\|jdk" pom.xml | head -30
echo "---"
# Check craftercms parent pom reference
grep -n "parent\|crafter" pom.xml | head -10
echo "---"
# Check for Dockerfile or CI files that indicate Java version
find . -name "Dockerfile*" -o -name "*.yml" -path "*github*" -o -name "*.yaml" -path "*github*" | head -10
echo "---"
find . -maxdepth 3 -name "*.yml" -path "*github/workflows*" | xargs grep -l "java" 2>/dev/null | head -5
echo "---"
# Check if there's a toolchains.xml or .java-version
find . -maxdepth 3 -name ".java-version" -o -name "toolchains.xml" 2>/dev/null

Length of output: 584


@jmendeza The implementation is well-structured overall — constructor injection is clean, interrupt handling is correct (Thread.currentThread().interrupt() restores the flag properly), and try/finally for lock release is correct. However, there are a few issues that should be addressed before moving forward:


🔴 Critical — ExecutorService try-with-resources nullifies the timeout

Both RepositoryStartupCleanup.unlockSitesRepositories() and StudioUpgradeManagerImpl.upgrade() follow this pattern:

try (ExecutorService taskExecutor = Executors.newFixedThreadPool(executorThreadCount)) {
    // submit tasks...
    taskExecutor.shutdown();
    taskExecutor.awaitTermination(executorTimeoutSeconds, SECONDS); // ← timeout
} // ← close() is called here

CrafterCMS 4.2+ requires Java 21, where ExecutorService implements AutoCloseable. However, the Java 21 default ExecutorService.close() implementation calls shutdown() (already a no-op at this point) and then loops on awaitTermination(1L, TimeUnit.DAYS) — effectively waiting indefinitely for any tasks still running after the explicit timeout. This completely defeats the purpose of executorTimeoutSeconds.

The fix is to either:

  • Remove the explicit shutdown() / awaitTermination() calls and rely solely on close() (which provides no bounded timeout), or
  • Recommended: Drop try-with-resources and use shutdownNow() when the timeout is exceeded:
ExecutorService taskExecutor = Executors.newFixedThreadPool(executorThreadCount);
try {
    // submit tasks...
    taskExecutor.shutdown();
    if (!taskExecutor.awaitTermination(executorTimeoutSeconds, SECONDS)) {
        logger.warn("Timed out waiting for tasks to complete after {}s, forcing shutdown", executorTimeoutSeconds);
        taskExecutor.shutdownNow();
    }
} catch (InterruptedException e) {
    taskExecutor.shutdownNow();
    Thread.currentThread().interrupt();
    // handle...
}

🟡 Medium — awaitTermination return value ignored

In both classes, the boolean returned by awaitTermination() is silently discarded. When a timeout occurs (returns false), there is no log warning indicating that some sites may not have been processed, making operational diagnosis harder.


🟡 Medium — Potential NullPointerException in removeIndexIfCorrupted

GitRepositoryHelper.getRepository() can return null (it logs a warning and returns null when buildSiteRepo() fails). The removeIndexIfCorrupted method calls repo.getWorkTree() unconditionally:

Repository repo = helper.getRepository(siteId, repository);
File repoDir = repo.getWorkTree(); // ← NPE if repo is null

A null guard is needed before proceeding.


ℹ️ Misc

  • The PR is still in draft status. Once the above are addressed, please mark it ready for review.
  • The Spring XML wiring and studio-config.yaml defaults look correct and consistent with the constructor signatures.

@jmendeza
Copy link
Copy Markdown
Member Author

jmendeza commented May 19, 2026

@coderabbitai removeIndexIfCorrupted runs after unlockRepository. It there is an error getting the repository, it is unlockRepository the one to fail

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the unlockSiteRepositories method and related methods in RepositoryStartupCleanup
cat src/main/java/org/craftercms/studio/impl/v2/repository/RepositoryStartupCleanup.java
echo "---FILE SEPARATOR---"
# Check unlockRepository and removeIndexIfCorrupted implementations
rg "unlockRepository|removeIndexIfCorrupted|getRepository" --type java -n -C3 src/main/java/org/craftercms/studio/impl/v2/repository/RepositoryStartupCleanup.java

Length of output: 7733


@jmendeza Good point that the sequence matters — but the concern is still valid because the two methods use different helpers:

  • unlockRepository calls helper.buildRepoPath(repository, siteId) — this just constructs a filesystem path. It does not open a JGit Repository object, so it can succeed regardless of whether JGit can parse the repo.
  • removeIndexIfCorrupted calls helper.getRepository(siteId, repository) — this actually opens/initializes the JGit Repository object, which can return null when buildSiteRepo() fails (e.g., when the repo is in a state JGit cannot parse).

The scenario that makes this dangerous is precisely the one this code targets: a corrupted repository. unlockRepository can successfully remove the .git/index.lock file at the filesystem level, and then getRepository still returns null because JGit cannot open the corrupted repo — leaving repo.getWorkTree() on Line 142 to throw an NPE.

A null guard before Line 142 would be the minimal fix:

protected void removeIndexIfCorrupted(String siteId, GitRepositories repository) {
    logger.debug("Checking if repository '{}' for site '{}' is corrupted", repository, siteId);
    Repository repo = helper.getRepository(siteId, repository);
    if (repo == null) {
        logger.warn("Could not open repository '{}' for site '{}', skipping corruption check", repository, siteId);
        return;
    }
    File repoDir = repo.getWorkTree();
    // ...
}

@jmendeza
Copy link
Copy Markdown
Member Author

@coderabbitai please review and let me know if your concerns are satisfied now

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
cat src/main/java/org/craftercms/studio/impl/v2/repository/RepositoryStartupCleanup.java
echo "---FILE SEPARATOR---"
# Check relevant section of StudioUpgradeManagerImpl
rg "ExecutorService|awaitTermination|shutdownNow|shutdown|executorTimeout|siteUpgradeThread" --type java -n -C3 src/main/java/org/craftercms/studio/impl/v2/upgrade/StudioUpgradeManagerImpl.java
echo "---FILE SEPARATOR---"
# Check removeIndexIfCorrupted specifically
rg "removeIndexIfCorrupted|getRepository|getWorkTree" --type java -n -C5 src/main/java/org/craftercms/studio/impl/v2/repository/RepositoryStartupCleanup.java

Length of output: 12161


@jmendeza All three concerns are now fully addressed:

  1. ExecutorService try-with-resources removed — Both RepositoryStartupCleanup.unlockSitesRepositories() and StudioUpgradeManagerImpl.upgrade() now use the correct pattern: plain executor → shutdown() → checked awaitTermination()shutdownNow() on timeout, with InterruptedException handled correctly in both.

  2. awaitTermination return value checked — Both methods now log a warn and call shutdownNow() when the timeout is exceeded.

  3. Null guard added in removeIndexIfCorrupted — The repo == null check with an early return and warning log is in place before any dereference.

The implementation looks good. The only remaining step is to mark the PR out of draft status when you're ready.

@jmendeza jmendeza marked this pull request as ready for review May 19, 2026 23:30
@jmendeza jmendeza requested a review from sumerjabri as a code owner May 19, 2026 23:30
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/resources/crafter/studio/studio-upgrade-context.xml (1)

27-57: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Wire the two newly required constructor dependencies.

The StudioUpgradeManagerImpl constructor requires retryingRepositoryOperationFacade and siteBootstrapStateProvider, but this bean provides only 11 constructor args. Add the two missing refs or the upgradeManager bean will fail to instantiate at startup.

🔧 Proposed fix
     <bean id="upgradeManager" class="org.craftercms.studio.impl.v2.upgrade.StudioUpgradeManagerImpl">
         <constructor-arg name="dataSource" ref="dataSource"/>
         <constructor-arg name="integrityValidator" ref="crafter.databaseValidator"/>
         <constructor-arg name="contentRepository" ref="contentRepository"/>
         <constructor-arg name="studioConfiguration" ref="studioConfiguration"/>
         <constructor-arg name="instanceService" ref="instanceService"/>
@@
         <constructor-arg name="bpPipelineFactory">
             <bean class="org.craftercms.studio.impl.v2.upgrade.pipeline.PrototypeUpgradePipelineFactoryImpl"
                   parent="pipelineFactory">
@@
             </bean>
         </constructor-arg>
+        <constructor-arg name="retryingRepositoryOperationFacade" ref="studio.retryingRepositoryOperationFacade"/>
+        <constructor-arg name="siteBootstrapStateProvider" ref="siteBootstrapStateProvider"/>
         <constructor-arg name="executorThreadCount" value="#{studioConfiguration.getProperty('studio.upgrade.sites.threadCount')}"/>
         <constructor-arg name="executorTimeoutSeconds" value="#{studioConfiguration.getProperty('studio.upgrade.sites.timeoutSeconds')}"/>
     </bean>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/resources/crafter/studio/studio-upgrade-context.xml` around lines 27
- 57, The upgradeManager bean configuration is missing two constructor-arg
references required by
org.craftercms.studio.impl.v2.upgrade.StudioUpgradeManagerImpl: add
constructor-arg entries for the retryingRepositoryOperationFacade and
siteBootstrapStateProvider dependencies (use
ref="retryingRepositoryOperationFacade" and ref="siteBootstrapStateProvider")
alongside the existing args so the upgradeManager bean can be instantiated;
ensure the names match the constructor parameter names expected by
StudioUpgradeManagerImpl.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/org/craftercms/studio/impl/v2/upgrade/StudioUpgradeManagerImpl.java`:
- Around line 243-246: When
taskExecutor.awaitTermination(executorTimeoutSeconds, TimeUnit.SECONDS) returns
false, treat this as a failed upgrade: create a TimeoutException or new
Exception with a descriptive message about the global upgrade timeout, call
upgradeException.addSuppressed(timeoutException) and set
upgradeFailed.set(true), then proceed to call taskExecutor.shutdownNow(); update
the warning log (logger.warn) if desired but ensure the suppressed exception is
recorded and upgradeFailed is set so timeout handling mirrors the
InterruptedException branch (refer to taskExecutor.awaitTermination,
executorTimeoutSeconds, logger.warn, taskExecutor.shutdownNow, upgradeException,
and upgradeFailed).
- Around line 232-239: The taskExecutor.execute lambda only catches
UpgradeException so unchecked exceptions from upgrade(site) can terminate the
thread silently; update the lambda in taskExecutor.execute to also catch broad
throwables (e.g., add a catch (Throwable t) or catch (RuntimeException | Error)
as appropriate) and in that handler call logger.error with context, add the
throwable to upgradeException via addSuppressed, and set upgradeFailed.set(true)
so any non-UpgradeException failures are recorded and cause the overall upgrade
to fail.

In
`@src/main/java/org/craftercms/studio/impl/v2/utils/spring/context/SiteBootstrapStateProviderImpl.java`:
- Around line 53-55: The call to siteDAO.getSite(siteId) in
SiteBootstrapStateProviderImpl may return null, so before calling
site.getSiteUuid() add a null guard: check the returned Site from
siteDAO.getSite(siteId) and if null either log a warning via the same logger (or
applicationEventPublisher) and return early, or throw a controlled exception
(e.g., IllegalStateException with a clear message); only call
applicationEventPublisher.publishEvent(new SiteReadyEvent(siteId,
site.getSiteUuid())) when site is non-null. Ensure you reference the existing
variables/methods (siteDAO.getSite, SiteReadyEvent,
applicationEventPublisher.publishEvent) and keep behavior consistent with
surrounding error handling.
- Around line 52-55: The event is published unconditionally causing duplicate
SiteReadyEvent emissions; change markSiteAsReady to only publish when the site
actually transitions to ready by checking the boolean result of
getReadySites().add(siteId) and only call siteDAO.getSite(...) and
applicationEventPublisher.publishEvent(new SiteReadyEvent(...)) when add(...)
returns true (i.e., the site was newly added).

---

Outside diff comments:
In `@src/main/resources/crafter/studio/studio-upgrade-context.xml`:
- Around line 27-57: The upgradeManager bean configuration is missing two
constructor-arg references required by
org.craftercms.studio.impl.v2.upgrade.StudioUpgradeManagerImpl: add
constructor-arg entries for the retryingRepositoryOperationFacade and
siteBootstrapStateProvider dependencies (use
ref="retryingRepositoryOperationFacade" and ref="siteBootstrapStateProvider")
alongside the existing args so the upgradeManager bean can be instantiated;
ensure the names match the constructor parameter names expected by
StudioUpgradeManagerImpl.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a4194e44-a2a8-424c-84c0-cb5c45dc96a8

📥 Commits

Reviewing files that changed from the base of the PR and between a9ee127 and df2c965.

📒 Files selected for processing (6)
  • src/main/java/org/craftercms/studio/impl/v2/repository/RepositoryStartupCleanup.java
  • src/main/java/org/craftercms/studio/impl/v2/upgrade/StudioUpgradeManagerImpl.java
  • src/main/java/org/craftercms/studio/impl/v2/utils/spring/context/SiteBootstrapStateProviderImpl.java
  • src/main/resources/crafter/studio/studio-config.yaml
  • src/main/resources/crafter/studio/studio-services-context.xml
  • src/main/resources/crafter/studio/studio-upgrade-context.xml

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.

1 participant