Skip to content

Groovy class loaders and script engine tweaks to reduce leaks#857

Merged
sumerjabri merged 2 commits into
craftercms:developfrom
jmendeza:bugfix/1266-e-5x
Jul 21, 2026
Merged

Groovy class loaders and script engine tweaks to reduce leaks#857
sumerjabri merged 2 commits into
craftercms:developfrom
jmendeza:bugfix/1266-e-5x

Conversation

@jmendeza

@jmendeza jmendeza commented Jul 20, 2026

Copy link
Copy Markdown
Member

Groovy class loaders and script engine tweaks to reduce leaks
https://github.com/craftersoftware/craftercms/issues/1266

Summary by CodeRabbit

  • Bug Fixes
    • Improved cleanup of scripting resources and class loaders during shutdown and configuration changes.
    • Prevented resource leaks when rebuilding or partially creating site contexts.
    • Improved error handling so cleanup failures are logged without interrupting context management.
  • Tests
    • Enhanced test cleanup to release scripting and application resources reliably.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jmendeza, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 114a0932-e1a3-490f-8e20-9a57c2ff0277

📥 Commits

Reviewing files that changed from the base of the PR and between e29de81 and 38c8ceb.

📒 Files selected for processing (1)
  • src/main/java/org/craftercms/engine/service/context/SiteContextFactory.java

Walkthrough

Script factories now expose destruction hooks and close Groovy classloaders. Site context shutdown and creation-failure paths invoke these hooks, while context rebuilds handle destruction failures through the manager.

Changes

Resource lifecycle management

Layer / File(s) Summary
Script factory classloader lifecycle
src/main/java/org/craftercms/engine/scripting/ScriptFactory.java, src/main/java/org/craftercms/engine/scripting/impl/GroovyScriptFactory.java, src/test/java/org/craftercms/engine/scripting/impl/GroovyScriptFactoryTest.java
ScriptFactory adds a default destroy() contract. GroovyScriptFactory applies compiler configuration through a helper and closes replaced or active Groovy classloaders. Tests clean up the script factory and classloader.
Site context construction and cleanup
src/main/java/org/craftercms/engine/service/context/SiteContext.java, src/main/java/org/craftercms/engine/service/context/SiteContextFactory.java
SiteContext.destroy() destroys the script factory before closing and nulling its classloader. SiteContextFactory wires resources and cleans up partially created contexts while preserving store-context destruction.
Context rebuild destruction handling
src/main/java/org/craftercms/engine/service/context/SiteContextManager.java
Context rebuilding uses destroyContext for the old context and logs destruction failures.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: sumerjabri

Sequence Diagram(s)

sequenceDiagram
  participant SiteContextFactory
  participant SiteContext
  participant ScriptFactory
  participant StoreService
  SiteContextFactory->>SiteContext: initialize and wire context resources
  SiteContextFactory->>SiteContext: destroy partially created context on failure
  SiteContext->>ScriptFactory: destroy script factory
  SiteContextFactory->>StoreService: destroy store context
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 reflects the main change: Groovy class loader and script engine leak reduction.
Description check ✅ Passed The description includes a brief summary and the linked ticket reference required by the template.
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.
✨ 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.

@jmendeza

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/main/java/org/craftercms/engine/service/context/SiteContext.java (1)

626-633: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Format the inline comment and consistently null out references.

Consider moving the inline comment to its own line for better readability. Additionally, it is a good practice to clear the applicationContext reference in a finally block to prevent lingering references if the SiteContext object stays in memory longer than expected, keeping it consistent with the handling of scriptFactory and classLoader below it.

♻️ Proposed refactor
 				if (applicationContext != null) {
 					try {
 						applicationContext.close();
 					} catch (Exception e) {
 						logger.error("Error while closing application context for {}", this, e);
-					}
-				}                // Close the GroovyScriptEngine's ScriptClassLoader (child) before the parent site class loader
+					} finally {
+						applicationContext = null;
+					}
+				}
+
+				// Close the GroovyScriptEngine's ScriptClassLoader (child) before the parent site class loader
 				if (scriptFactory != null) {
🤖 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/engine/service/context/SiteContext.java` around
lines 626 - 633, Move the GroovyScriptEngine comment onto its own line before
the corresponding cleanup block, and update the applicationContext cleanup
around its existing close call to clear the reference in a finally block,
matching the nulling behavior already used for scriptFactory and classLoader.
🤖 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/engine/service/context/SiteContextFactory.java`:
- Around line 385-391: Update the context cleanup paths in SiteContextFactory so
any exception from storeService.destroyContext(context) is caught and added as a
suppressed exception to the original context-creation exception e, rather than
propagated. Apply this to both the partial-context cleanup inside the destroyEx
catch and the direct cleanup in the else branch, while preserving the existing
cleanup error logging.

---

Nitpick comments:
In `@src/main/java/org/craftercms/engine/service/context/SiteContext.java`:
- Around line 626-633: Move the GroovyScriptEngine comment onto its own line
before the corresponding cleanup block, and update the applicationContext
cleanup around its existing close call to clear the reference in a finally
block, matching the nulling behavior already used for scriptFactory and
classLoader.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e286e1b8-fcf5-460e-8b0a-511c0803e113

📥 Commits

Reviewing files that changed from the base of the PR and between 024d5e0 and e29de81.

📒 Files selected for processing (6)
  • src/main/java/org/craftercms/engine/scripting/ScriptFactory.java
  • src/main/java/org/craftercms/engine/scripting/impl/GroovyScriptFactory.java
  • src/main/java/org/craftercms/engine/service/context/SiteContext.java
  • src/main/java/org/craftercms/engine/service/context/SiteContextFactory.java
  • src/main/java/org/craftercms/engine/service/context/SiteContextManager.java
  • src/test/java/org/craftercms/engine/scripting/impl/GroovyScriptFactoryTest.java

Comment thread src/main/java/org/craftercms/engine/service/context/SiteContextFactory.java Outdated
@jmendeza

Copy link
Copy Markdown
Member Author

@coderabbitai is this ready to move forward to a human reviewer? Ignore codacy and travis

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="craftercms/engine"
pr="857"

echo "== Pull request metadata =="
gh pr view "$pr" --repo "$repo" --json number,title,isDraft,mergeable,reviewDecision,statusCheckRollup,reviews \
  --jq '{
    number,
    title,
    isDraft,
    mergeable,
    reviewDecision,
    checks: [
      .statusCheckRollup[]
      | select(
          ((.name // "") | ascii_downcase | contains("codacy") | not)
          and ((.name // "") | ascii_downcase | contains("travis") | not)
        )
      | {
          name,
          status: (.status // .state // ""),
          conclusion: (.conclusion // ""),
          workflow: (.workflowName // "")
        }
    ],
    reviews: [
      .reviews[]
      | {author: .author.login, state, submittedAt}
    ]
  }'

echo
echo "== Unresolved review threads =="
gh api graphql \
  -F owner="craftercms" \
  -F name="engine" \
  -F number="$pr" \
  -f query='
query($owner: String!, $name: String!, $number: Int!) {
  repository(owner: $owner, name: $name) {
    pullRequest(number: $number) {
      reviewThreads(first: 100) {
        nodes {
          isResolved
          comments(first: 10) {
            nodes {
              author { login }
              body
              path
              line
              originalLine
              createdAt
            }
          }
        }
      }
    }
  }
}' \
  --jq '
    .data.repository.pullRequest.reviewThreads.nodes[]
    | select(.isResolved == false)
    | {
        path: (.comments.nodes[0].path // ""),
        line: (.comments.nodes[0].line // .comments.nodes[0].originalLine // 0),
        author: (.comments.nodes[0].author.login // ""),
        body: (.comments.nodes[0].body // ""),
        createdAt: (.comments.nodes[0].createdAt // "")
      }'

Length of output: 2563


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="craftercms/engine"
pr="857"

head_sha=$(gh pr view "$pr" --repo "$repo" --json headRefOid --jq '.headRefOid')
echo "== Head SHA =="
echo "$head_sha"

echo
echo "== Complete status-check rollup =="
gh pr view "$pr" --repo "$repo" --json statusCheckRollup --jq '.statusCheckRollup'

echo
echo "== Check runs =="
gh api "repos/$repo/commits/$head_sha/check-runs?per_page=100" \
  --jq '.check_runs[] | {
    name,
    status,
    conclusion,
    details_url: .details_url,
    app: .app.slug
  }'

echo
echo "== Legacy commit statuses =="
gh api "repos/$repo/commits/$head_sha/status" \
  --jq '.statuses[] | {
    context,
    state,
    target_url,
    creator: .creator.login
  }'

Length of output: 2531


@jmendeza Yes — from the checks reviewed, this is ready to move forward to a human reviewer.

  • No unresolved review threads.
  • GitGuardian passed.
  • Ignoring Codacy and Travis as requested, there are no other failing CI checks.
  • The PR is mergeable.

Before requesting human review, mark it Ready for review since it is currently a draft. GitHub also shows REVIEW_REQUIRED, so a human approval is still needed. The remaining failed CodeRabbit legacy status may need to be waived or rerun depending on branch-protection rules, despite there being no unresolved CodeRabbit findings.

@jmendeza
jmendeza marked this pull request as ready for review July 20, 2026 21:34
@jmendeza
jmendeza requested a review from sumerjabri as a code owner July 20, 2026 21:34
@sumerjabri
sumerjabri merged commit d963278 into craftercms:develop Jul 21, 2026
1 of 4 checks passed
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.

2 participants