Skip to content

Improve hot path virtual-thread and codec performance#15828

Open
jamesfredley wants to merge 3 commits into
8.0.xfrom
perf/vthread-static-compile-8.0.x
Open

Improve hot path virtual-thread and codec performance#15828
jamesfredley wants to merge 3 commits into
8.0.xfrom
perf/vthread-static-compile-8.0.x

Conversation

@jamesfredley

@jamesfredley jamesfredley commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR makes two Grails 8 hot paths friendlier to the JDK 21 runtime without changing the public API surface:

  • Replaces GrailsHttpSession wrapper monitor synchronization with a private ReentrantLock.
  • Static-compiles grails-codecs-core codec extension helpers and removes dynamic self-dispatch inside the codec implementations.
  • Keeps existing Groovy extension method binary descriptors intact by preserving Object return types and the existing mutable HEXDIGITS property shape.

Motivation

Virtual-thread friendliness

GrailsHttpSession previously serialized wrapper access with synchronized (this). That object monitor can pin a virtual-thread carrier if the servlet container or request/session access blocks while the monitor is held. A private ReentrantLock preserves the wrapper-level serialization semantics while avoiding monitor pinning, and it also makes lazy session creation occur under the same lock as subsequent session access.

Codec hot-path static compilation

The codec extension methods are small, frequently used helpers. Static compilation removes runtime Groovy dispatch inside MD5, SHA-1, SHA-256, Base64, Hex, and shared digest conversion paths. The implementation still keeps the extension methods compatible for existing compiled callers by preserving the original public method descriptors.

Compatibility notes

  • decodeHex keeps the previous Groovy truth behavior for falsy targets by using Groovy's runtime truth conversion helper under @CompileStatic.
  • String, primitive byte arrays, wrapper byte arrays, number lists, and null/NullObject cases remain covered.
  • No new dependencies or user-facing configuration changes are introduced.

Review feedback addressed

  • Restored the old decodeHex Groovy truth guard behavior for falsy inputs after Copilot review feedback.
  • Added regression coverage for falsy values against the static extension implementation.

Verification

  • ./gradlew :grails-codecs-core:test --tests "org.grails.web.codecs.HexCodecTests" :grails-codecs-core:codeStyle --rerun-tasks
  • ./gradlew :grails-codecs-core:test :grails-codecs-core:codeStyle --rerun-tasks
  • ./gradlew :grails-codecs-core:test :grails-web-common:test --tests "grails.web.servlet.mvc.GrailsHttpSessionSpec" :grails-codecs-core:codeStyle :grails-web-common:codeStyle --rerun-tasks
  • ./gradlew clean aggregateViolations :grails-test-report:check --continue
    • Violation reports were clean: Checkstyle, CodeNarc, PMD, SpotBugs.
    • The full gate still hits pre-existing unrelated failures on clean origin/8.0.x: grails-validation:compileGroovy, grails-data-graphql-core:compileGroovy, and MongoDB/Testcontainers environment failures. The two compile failures reproduce in a separate clean origin/8.0.x worktree.

Review gate

  • Current full branch review gate passed for diff hash 8102545e609fd576dfc616e73c59e4c7da847dbf with both Oracle and Codex reviewers green.
  • Copilot follow-up commit review gate passed for staged diff hash fcb2c9b25edffd17c4910e79aa23047e3169e608 with both Oracle and Codex reviewers green.

Use a ReentrantLock inside GrailsHttpSession so wrapper-level session access no longer pins virtual-thread carrier threads on the object monitor.

Static-compile the codecs-core extension helpers while preserving existing Groovy extension method descriptors.

Assisted-by: Hephaestus:openai/gpt-5.5 codex-review
Copilot AI review requested due to automatic review settings July 5, 2026 02:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR optimizes two hot paths in Grails 8: (1) GrailsHttpSession synchronization to be more virtual-thread friendly, and (2) common codec extension methods to reduce Groovy dynamic-dispatch overhead while keeping the public extension surface stable.

Changes:

  • Replace synchronized (this) session wrapper serialization with a private ReentrantLock and lock-protected lazy session creation.
  • Add a new spec asserting lazy servlet-session creation and correct delegation for attribute operations.
  • Static-compile and refactor codecs (Hex, Base64, MD5, SHA-1, SHA-256) to use direct helper calls and shared byte-conversion logic.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
grails-web-common/src/main/groovy/grails/web/servlet/mvc/GrailsHttpSession.java Switch wrapper synchronization to ReentrantLock and lock-protect lazy session creation/access.
grails-web-common/src/test/groovy/grails/web/servlet/mvc/GrailsHttpSessionSpec.groovy Add coverage for lazy session creation + delegation for attribute operations.
grails-codecs-core/src/main/groovy/org/grails/plugins/codecs/DigestUtils.groovy Static-compile and centralize Objectbyte[] conversion to support codec hot paths.
grails-codecs-core/src/main/groovy/org/grails/plugins/codecs/HexCodecExtensionMethods.groovy Static-compile + rewrite encode/decode loops for performance; preserve HEXDIGITS shape.
grails-codecs-core/src/main/groovy/org/grails/plugins/codecs/Base64CodecExtensionMethods.groovy Static-compile and route Byte[]/byte[] paths through shared byte conversion.
grails-codecs-core/src/main/groovy/org/grails/plugins/codecs/MD5CodecExtensionMethods.groovy Static-compile and replace dynamic chaining with direct helper calls.
grails-codecs-core/src/main/groovy/org/grails/plugins/codecs/MD5BytesCodecExtensionMethods.groovy Static-compile and keep digest behavior via DigestUtils.
grails-codecs-core/src/main/groovy/org/grails/plugins/codecs/SHA1CodecExtensionMethods.groovy Static-compile and replace dynamic chaining with direct helper calls.
grails-codecs-core/src/main/groovy/org/grails/plugins/codecs/SHA1BytesCodecExtensionMethods.groovy Static-compile and keep digest behavior via DigestUtils.
grails-codecs-core/src/main/groovy/org/grails/plugins/codecs/SHA256CodecExtensionMethods.groovy Static-compile and replace dynamic chaining with direct helper calls.
grails-codecs-core/src/main/groovy/org/grails/plugins/codecs/SHA256BytesCodecExtensionMethods.groovy Static-compile and keep digest behavior via DigestUtils.

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

@bito-code-review

Copy link
Copy Markdown

The current implementation of decodeHex uses a restrictive guard (theTarget == null || theTarget instanceof NullObject || theTarget.toString().length() == 0) which deviates from the previous Groovy-truthiness check (if (!theTarget) return null). This change can cause exceptions or unexpected behavior for inputs that were previously considered falsy in Groovy, such as 0 or empty collections. To restore the original contract and ensure compatibility with Groovy-truthiness, the guard should be updated to use the !theTarget check.

grails-codecs-core/src/main/groovy/org/grails/plugins/codecs/HexCodecExtensionMethods.groovy

static Object decodeHex(Object theTarget) {
        if (!theTarget) return null

jamesfredley and others added 2 commits July 5, 2026 08:57
Preserve the pre-existing Groovy truth guard under static compilation by using Groovy's runtime truth conversion helper.

Add Hex codec regression coverage for falsy inputs.

Assisted-by: Hephaestus:openai/gpt-5.5 codex-review

@jdaugherty jdaugherty left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't see anything major with this at first glance. I asked Fabled to take a look and the results are attached to the review.

The GrailsHttpSession lock conversion looks correct — the remaining synchronized (this) blocks are all in commented-out dead code, lazy session creation now happens under the same lock as access (an improvement over the old racy pattern), and the new spec covers the lazy-create/delegate path. The codec static compilation is a nice win too; my comments are mostly about a few places where the rewrite narrows the accepted input types compared to the old dynamic coercion, plus some test-convention and hot-loop nits.

}

private static byte toByte(Object value) {
return ((Number) value).byteValue()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This narrows the accepted element types compared to the old dynamic code. Previously src[i] = v went through Groovy's DefaultTypeTransformation coercion, which also accepted Character elements (and char[] arrays via the array path); now any non-Number element throws ClassCastException. If preserving the old tolerance matters, DefaultTypeTransformation.castToNumber(value).byteValue() keeps the coercion while staying statically compiled. If the narrowing is intentional, it's probably worth a line in the PR compatibility notes.

return md.digest()
}

protected static byte[] toByteArray(Object data) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: protected reads as "for subclasses", but the actual consumers (Base64CodecExtensionMethods, HexCodecExtensionMethods) rely on protected's package-access side effect. @PackageScope (or a brief comment) would make the intent clearer.

theTarget.each() {
result << HexCodecExtensionMethods.HEXDIGITS[(it & 0xF0) >> 4]
result << HexCodecExtensionMethods.HEXDIGITS[it & 0x0F]
byte[] bytes = theTarget instanceof String ? ((String) theTarget).getBytes(StandardCharsets.UTF_8) : DigestUtils.toByteArray(theTarget)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Behavior change for iterable targets that aren't Lists or arrays (e.g. a Set<Integer> or an Iterator): the old code element-wise hex-encoded anything via theTarget.each { ... }, but DigestUtils.toByteArray only special-cases byte[]/Byte[]/List/arrays and otherwise falls through to toString().getBytes(UTF_8) — so a Set of numbers now silently encodes its string representation instead of its elements. Consider handling Collection/Iterable in toByteArray (or at least calling this out in the compatibility notes, since the failure mode is silent wrong output rather than an exception).

for (int i = 0; i < str.size(); i += 2) {
int high = hexDigits.indexOf((int) str.charAt(i))
int low = hexDigits.indexOf((int) str.charAt(i + 1))
result[i.intdiv(2)] = (byte) ((high << 4) | low)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit for a perf-focused PR: i.intdiv(2) boxes on every iteration; i >> 1 (and str.length() >> 1 on line 58) avoids it. Same for str.size() vs plain str.length(). Also, on line 51 the explicit null/NullObject checks are redundant — DefaultTypeTransformation.castToBoolean already returns false for both.

static encodeAsMD5(theTarget) {
theTarget.encodeAsMD5Bytes()?.encodeAsHex()
static Object encodeAsMD5(Object theTarget) {
byte[] digest = (byte[]) MD5BytesCodecExtensionMethods.encodeAsMD5Bytes(theTarget)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just confirming this is intended: the old theTarget.encodeAsMD5Bytes()?.encodeAsHex() dispatched dynamically, so a runtime metaclass override of encodeAsMD5Bytes (e.g. an application-supplied codec replacing the default) would also change what encodeAsMD5 produced. The direct static call hardwires the default implementation. Same applies to the SHA-1/SHA-256 variants. Probably fine — it's the whole point of removing dynamic dispatch — but worth a note in the compatibility section.

Comment on lines +54 to +58
assertEquals(null, HexCodecExtensionMethods.decodeHex(''))
assertEquals(null, HexCodecExtensionMethods.decodeHex(0))
assertEquals(null, HexCodecExtensionMethods.decodeHex(false))
assertEquals(null, HexCodecExtensionMethods.decodeHex([]))
assertEquals(null, HexCodecExtensionMethods.decodeHex(new byte[0]))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Per the repo rule to test via public APIs, these could use the extension-method form users actually call — ''.decodeHex(), 0.decodeHex(), false.decodeHex(), [].decodeHex(), new byte[0].decodeHex() — which also exercises the extension-module registration path rather than the static class directly (matching the existing null.decodeHex() assertion above).

assertEquals(null, HexCodecExtensionMethods.decodeHex(new byte[0]))
}

void testRoundtrip() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pre-existing, but since this file is being touched: testRoundtrip has no @Test annotation, so it never runs — and it's exactly the round-trip coverage the new static implementations want. Note that enabling it as-is will likely fail on the first assertion: assertIterableEquals compares Integer expected values against the Byte elements from decodeHex(...).toList(), and Integer(65).equals(Byte(65)) is false — the expectations need a byte-typed list.


if (theTarget instanceof Byte[] || theTarget instanceof byte[]) {
return new String(Base64.encodeBase64(theTarget))
return new String(Base64.encodeBase64(DigestUtils.toByteArray(theTarget)), StandardCharsets.UTF_8)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

UTF_8 should be an added method argument that is defaulted to this value.

return Base64.decodeBase64(DigestUtils.toByteArray(theTarget))
}

return Base64.decodeBase64(theTarget.toString().getBytes(StandardCharsets.UTF_8))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think you should take the opportunity to make UTF_8 an added method argument that's defaulted

@testlens-app

testlens-app Bot commented Jul 7, 2026

Copy link
Copy Markdown

🚨 TestLens detected 24 failed tests 🚨

Here is what you can do:

  1. Inspect the test failures carefully.
  2. If you are convinced that some of the tests are flaky, you can mute them below.
  3. Finally, trigger a rerun by checking the rerun checkbox.

Test Summary

CI / Build Grails Forge (Java 21, indy=false) > :grails-forge-cli:test [grails-forge]

Test Runs Flakiness
CreateControllerCommandSpec > test app with controller 1% 🟡
CreateDomainClassCommandSpec > test app with domain 0% 🟢
CreateInterceptorCommandSpec > test app with interceptor 0% 🟢
CreateServiceCommandSpec > test app with service 0% 🟢
CreateTagLibCommandSpec > test app with taglib 0% 🟢

CI / Build Grails Forge (Java 21, indy=false) > :test-core:test [grails-forge]

Test Runs Flakiness
CreateAppSpec > test basic create-app build task 0% 🟢
CreateRestApiSpec > test rest-api app with default json-views 0% 🟢
ScaffoldingSpec > test generate-controller command 0% 🟢

CI / Build Grails Forge (Java 21, indy=true) > :grails-forge-cli:test [grails-forge]

Test Runs Flakiness
CreateControllerCommandSpec > test app with controller 1% 🟡
CreateDomainClassCommandSpec > test app with domain 0% 🟢
CreateInterceptorCommandSpec > test app with interceptor 0% 🟢
CreateServiceCommandSpec > test app with service 0% 🟢
CreateTagLibCommandSpec > test app with taglib 0% 🟢

CI / Build Grails Forge (Java 21, indy=true) > :test-core:test [grails-forge]

Test Runs Flakiness
CreateAppSpec > test basic create-app build task 0% 🟢
CreateRestApiSpec > test rest-api app with default json-views 0% 🟢
ScaffoldingSpec > test generate-controller command 0% 🟢

CI / Build Grails Forge (Java 25, indy=false) > :grails-forge-cli:test [grails-forge]

Test Runs Flakiness
CreateControllerCommandSpec > test app with controller 1% 🟡
CreateDomainClassCommandSpec > test app with domain 0% 🟢
CreateInterceptorCommandSpec > test app with interceptor 0% 🟢
CreateServiceCommandSpec > test app with service 0% 🟢
CreateTagLibCommandSpec > test app with taglib 0% 🟢

CI / Build Grails Forge (Java 25, indy=false) > :test-core:test [grails-forge]

Test Runs Flakiness
CreateAppSpec > test basic create-app build task 0% 🟢
CreateRestApiSpec > test rest-api app with default json-views 0% 🟢
ScaffoldingSpec > test generate-controller command 0% 🟢

🏷️ Commit: 0aa3569
▶️ Tests: 31058 executed
⚪️ Checks: 60/60 completed

Test Failures (first 10 of 24)

CreateAppSpec > test basic create-app build task (:test-core:test [grails-forge] in CI / Build Grails Forge (Java 21, indy=false))
org.gradle.testkit.runner.UnexpectedBuildFailure: Unexpected build execution failure in /tmp/test-app17265044849832404999 with arguments [build, -x, iT]

Output:

[Incubating] Problems report is available at: file:///tmp/test-app17265044849832404999/build/reports/problems/problems-report.html

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring root project 'test-app17265044849832404999'.
> Failed to notify project evaluation listener.
   > org/codehaus/plexus/util/xml/pull/XmlPullParserException

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights from a Build Scan (powered by Develocity).
> Get more help at https://help.gradle.org.

Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

BUILD FAILED in 54s

	at org.gradle.testkit.runner.internal.DefaultGradleRunner.lambda$build$2(DefaultGradleRunner.java:275)
	at org.gradle.testkit.runner.internal.DefaultGradleRunner.run(DefaultGradleRunner.java:374)
	at org.gradle.testkit.runner.internal.DefaultGradleRunner.build(DefaultGradleRunner.java:273)
	at org.grails.forge.utils.CommandSpec.executeGradle(CommandSpec.groovy:79)
	at org.grails.forge.create.CreateAppSpec.test basic create-app build task(CreateAppSpec.groovy:49)
CreateControllerCommandSpec > test app with controller (:grails-forge-cli:test [grails-forge] in CI / Build Grails Forge (Java 21, indy=false))
Condition not satisfied after 240.00 seconds and 238 attempts
	at spock.util.concurrent.PollingConditions.within(PollingConditions.java:205)
	at spock.util.concurrent.PollingConditions.eventually(PollingConditions.java:157)
	at org.grails.forge.cli.CommandSpec.testOutputContains(CommandSpec.groovy:64)
	at org.grails.forge.cli.command.CreateControllerCommandSpec.test app with controller(CreateControllerCommandSpec.groovy:73)
Caused by: Condition not satisfied:

output.toString().contains(value)
|      |          |        |
|      |          false    BUILD SUCCESSFUL
|      To honour the JVM settings for this build a single-use Daemon process will be forked. For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/gradle_daemon.html#sec:disabling_the_daemon in the Gradle documentation.
|      Daemon will be stopped at the end of the build 
|      gradle/actions: Writing build results to /home/runner/work/_temp/.gradle-actions/build-results/__run_3-1783442617831.json
|       
|      [Incubating] Problems report is available at: file:///tmp/grailsforgetmp4430627665030355529/build/reports/problems/problems-report.html
|       
|      Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.
|       
|      You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
|       
|      For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
To honour the JVM settings for this build a single-use Daemon process will be forked. For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/gradle_daemon.html#sec:disabling_the_daemon in the Gradle documentation.
Daemon will be stopped at the end of the build 
gradle/actions: Writing build results to /home/runner/work/_temp/.gradle-actions/build-results/__run_3-1783442617831.json
 
[Incubating] Problems report is available at: file:///tmp/grailsforgetmp4430627665030355529/build/reports/problems/problems-report.html
 
Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.
 
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
 
For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

	at org.grails.forge.cli.CommandSpec.testOutputContains_closure1(CommandSpec.groovy:65)
	at org.grails.forge.cli.CommandSpec.testOutputContains_closure1(CommandSpec.groovy)
	at spock.util.concurrent.PollingConditions.within(PollingConditions.java:185)
	... 3 more
CreateDomainClassCommandSpec > test app with domain (:grails-forge-cli:test [grails-forge] in CI / Build Grails Forge (Java 21, indy=false))
Condition not satisfied after 240.00 seconds and 240 attempts
	at spock.util.concurrent.PollingConditions.within(PollingConditions.java:205)
	at spock.util.concurrent.PollingConditions.eventually(PollingConditions.java:157)
	at org.grails.forge.cli.CommandSpec.testOutputContains(CommandSpec.groovy:64)
	at org.grails.forge.cli.command.CreateDomainClassCommandSpec.test app with domain(CreateDomainClassCommandSpec.groovy:86)
Caused by: Condition not satisfied:

output.toString().contains(value)
|      |          |        |
|      |          false    BUILD SUCCESSFUL
|      To honour the JVM settings for this build a single-use Daemon process will be forked. For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/gradle_daemon.html#sec:disabling_the_daemon in the Gradle documentation.
|      Daemon will be stopped at the end of the build 
|      gradle/actions: Writing build results to /home/runner/work/_temp/.gradle-actions/build-results/__run_3-1783442848596.json
|       
|      [Incubating] Problems report is available at: file:///tmp/grailsforgetmp12440275210572336673/build/reports/problems/problems-report.html
|       
|      Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.
|       
|      You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
|       
|      For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
To honour the JVM settings for this build a single-use Daemon process will be forked. For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/gradle_daemon.html#sec:disabling_the_daemon in the Gradle documentation.
Daemon will be stopped at the end of the build 
gradle/actions: Writing build results to /home/runner/work/_temp/.gradle-actions/build-results/__run_3-1783442848596.json
 
[Incubating] Problems report is available at: file:///tmp/grailsforgetmp12440275210572336673/build/reports/problems/problems-report.html
 
Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.
 
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
 
For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

	at org.grails.forge.cli.CommandSpec.testOutputContains_closure1(CommandSpec.groovy:65)
	at org.grails.forge.cli.CommandSpec.testOutputContains_closure1(CommandSpec.groovy)
	at spock.util.concurrent.PollingConditions.within(PollingConditions.java:185)
	... 3 more
CreateInterceptorCommandSpec > test app with interceptor (:grails-forge-cli:test [grails-forge] in CI / Build Grails Forge (Java 21, indy=false))
Condition not satisfied after 240.00 seconds and 241 attempts
	at spock.util.concurrent.PollingConditions.within(PollingConditions.java:205)
	at spock.util.concurrent.PollingConditions.eventually(PollingConditions.java:157)
	at org.grails.forge.cli.CommandSpec.testOutputContains(CommandSpec.groovy:64)
	at org.grails.forge.cli.command.CreateInterceptorCommandSpec.test app with interceptor(CreateInterceptorCommandSpec.groovy:84)
Caused by: Condition not satisfied:

output.toString().contains(value)
|      |          |        |
|      |          false    BUILD SUCCESSFUL
|      To honour the JVM settings for this build a single-use Daemon process will be forked. For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/gradle_daemon.html#sec:disabling_the_daemon in the Gradle documentation.
|      Daemon will be stopped at the end of the build 
|      gradle/actions: Writing build results to /home/runner/work/_temp/.gradle-actions/build-results/__run_3-1783443089012.json
|       
|      [Incubating] Problems report is available at: file:///tmp/grailsforgetmp4527236862726576576/build/reports/problems/problems-report.html
|       
|      Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.
|       
|      You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
|       
|      For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
To honour the JVM settings for this build a single-use Daemon process will be forked. For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/gradle_daemon.html#sec:disabling_the_daemon in the Gradle documentation.
Daemon will be stopped at the end of the build 
gradle/actions: Writing build results to /home/runner/work/_temp/.gradle-actions/build-results/__run_3-1783443089012.json
 
[Incubating] Problems report is available at: file:///tmp/grailsforgetmp4527236862726576576/build/reports/problems/problems-report.html
 
Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.
 
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
 
For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

	at org.grails.forge.cli.CommandSpec.testOutputContains_closure1(CommandSpec.groovy:65)
	at org.grails.forge.cli.CommandSpec.testOutputContains_closure1(CommandSpec.groovy)
	at spock.util.concurrent.PollingConditions.within(PollingConditions.java:185)
	... 3 more
CreateRestApiSpec > test rest-api app with default json-views (:test-core:test [grails-forge] in CI / Build Grails Forge (Java 21, indy=false))
org.gradle.testkit.runner.UnexpectedBuildFailure: Unexpected build execution failure in /tmp/testrestapi16656240023022940554 with arguments [build]

Output:

[Incubating] Problems report is available at: file:///tmp/testrestapi16656240023022940554/build/reports/problems/problems-report.html

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring root project 'testrestapi16656240023022940554'.
> Failed to notify project evaluation listener.
   > org/codehaus/plexus/util/xml/pull/XmlPullParserException

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights from a Build Scan (powered by Develocity).
> Get more help at https://help.gradle.org.

Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

BUILD FAILED in 55s

	at org.gradle.testkit.runner.internal.DefaultGradleRunner.lambda$build$2(DefaultGradleRunner.java:275)
	at org.gradle.testkit.runner.internal.DefaultGradleRunner.run(DefaultGradleRunner.java:374)
	at org.gradle.testkit.runner.internal.DefaultGradleRunner.build(DefaultGradleRunner.java:273)
	at org.grails.forge.utils.CommandSpec.executeGradle(CommandSpec.groovy:79)
	at org.grails.forge.create.CreateRestApiSpec.test rest-api app with default json-views(CreateRestApiSpec.groovy:30)
CreateServiceCommandSpec > test app with service (:grails-forge-cli:test [grails-forge] in CI / Build Grails Forge (Java 21, indy=false))
Condition not satisfied after 240.00 seconds and 248 attempts
	at spock.util.concurrent.PollingConditions.within(PollingConditions.java:205)
	at spock.util.concurrent.PollingConditions.eventually(PollingConditions.java:157)
	at org.grails.forge.cli.CommandSpec.testOutputContains(CommandSpec.groovy:64)
	at org.grails.forge.cli.command.CreateServiceCommandSpec.test app with service(CreateServiceCommandSpec.groovy:73)
Caused by: Condition not satisfied:

output.toString().contains(value)
|      |          |        |
|      |          false    TestServiceSpec > test something FAILED
|      To honour the JVM settings for this build a single-use Daemon process will be forked. For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/gradle_daemon.html#sec:disabling_the_daemon in the Gradle documentation.
|      Daemon will be stopped at the end of the build 
|      gradle/actions: Writing build results to /home/runner/work/_temp/.gradle-actions/build-results/__run_3-1783443329068.json
|       
|      [Incubating] Problems report is available at: file:///tmp/grailsforgetmp3892792305788309372/build/reports/problems/problems-report.html
|       
|      Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.
|       
|      You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
|       
|      For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
To honour the JVM settings for this build a single-use Daemon process will be forked. For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/gradle_daemon.html#sec:disabling_the_daemon in the Gradle documentation.
Daemon will be stopped at the end of the build 
gradle/actions: Writing build results to /home/runner/work/_temp/.gradle-actions/build-results/__run_3-1783443329068.json
 
[Incubating] Problems report is available at: file:///tmp/grailsforgetmp3892792305788309372/build/reports/problems/problems-report.html
 
Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.
 
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
 
For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

	at org.grails.forge.cli.CommandSpec.testOutputContains_closure1(CommandSpec.groovy:65)
	at org.grails.forge.cli.CommandSpec.testOutputContains_closure1(CommandSpec.groovy)
	at spock.util.concurrent.PollingConditions.within(PollingConditions.java:185)
	... 3 more
CreateTagLibCommandSpec > test app with taglib (:grails-forge-cli:test [grails-forge] in CI / Build Grails Forge (Java 21, indy=false))
Condition not satisfied after 240.00 seconds and 238 attempts
	at spock.util.concurrent.PollingConditions.within(PollingConditions.java:205)
	at spock.util.concurrent.PollingConditions.eventually(PollingConditions.java:157)
	at org.grails.forge.cli.CommandSpec.testOutputContains(CommandSpec.groovy:64)
	at org.grails.forge.cli.command.CreateTagLibCommandSpec.test app with taglib(CreateTagLibCommandSpec.groovy:79)
Caused by: Condition not satisfied:

output.toString().contains(value)
|      |          |        |
|      |          false    SimpleTagLibSpec > test simple tag as method FAILED
|      To honour the JVM settings for this build a single-use Daemon process will be forked. For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/gradle_daemon.html#sec:disabling_the_daemon in the Gradle documentation.
|      Daemon will be stopped at the end of the build 
|      gradle/actions: Writing build results to /home/runner/work/_temp/.gradle-actions/build-results/__run_3-1783443569362.json
|       
|      [Incubating] Problems report is available at: file:///tmp/grailsforgetmp16192954251307195502/build/reports/problems/problems-report.html
|       
|      Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.
|       
|      You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
|       
|      For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
To honour the JVM settings for this build a single-use Daemon process will be forked. For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/gradle_daemon.html#sec:disabling_the_daemon in the Gradle documentation.
Daemon will be stopped at the end of the build 
gradle/actions: Writing build results to /home/runner/work/_temp/.gradle-actions/build-results/__run_3-1783443569362.json
 
[Incubating] Problems report is available at: file:///tmp/grailsforgetmp16192954251307195502/build/reports/problems/problems-report.html
 
Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.
 
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
 
For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

	at org.grails.forge.cli.CommandSpec.testOutputContains_closure1(CommandSpec.groovy:65)
	at org.grails.forge.cli.CommandSpec.testOutputContains_closure1(CommandSpec.groovy)
	at spock.util.concurrent.PollingConditions.within(PollingConditions.java:185)
	... 3 more
ScaffoldingSpec > test generate-controller command (:test-core:test [grails-forge] in CI / Build Grails Forge (Java 21, indy=false))
org.gradle.testkit.runner.UnexpectedBuildFailure: Unexpected build execution failure in /tmp/testapp3506308189999524896 with arguments [runCommand, -Pargs=generate-controller example.grails.Bird]

Output:

[Incubating] Problems report is available at: file:///tmp/testapp3506308189999524896/build/reports/problems/problems-report.html

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring root project 'testapp3506308189999524896'.
> Failed to notify project evaluation listener.
   > org/codehaus/plexus/util/xml/pull/XmlPullParserException

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights from a Build Scan (powered by Develocity).
> Get more help at https://help.gradle.org.

Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

BUILD FAILED in 28s

	at org.gradle.testkit.runner.internal.DefaultGradleRunner.lambda$build$2(DefaultGradleRunner.java:275)
	at org.gradle.testkit.runner.internal.DefaultGradleRunner.run(DefaultGradleRunner.java:374)
	at org.gradle.testkit.runner.internal.DefaultGradleRunner.build(DefaultGradleRunner.java:273)
	at org.grails.forge.utils.CommandSpec.executeGradle(CommandSpec.groovy:84)
	at org.grails.forge.features.scaffolding.ScaffoldingSpec.test generate-controller command(ScaffoldingSpec.groovy:41)
CreateAppSpec > test basic create-app build task (:test-core:test [grails-forge] in CI / Build Grails Forge (Java 21, indy=true))
org.gradle.testkit.runner.UnexpectedBuildFailure: Unexpected build execution failure in /tmp/test-app4223858957852639015 with arguments [build, -x, iT]

Output:

[Incubating] Problems report is available at: file:///tmp/test-app4223858957852639015/build/reports/problems/problems-report.html

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring root project 'test-app4223858957852639015'.
> Failed to notify project evaluation listener.
   > org/codehaus/plexus/util/xml/pull/XmlPullParserException

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights from a Build Scan (powered by Develocity).
> Get more help at https://help.gradle.org.

Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

BUILD FAILED in 43s

	at org.gradle.testkit.runner.internal.DefaultGradleRunner.lambda$build$2(DefaultGradleRunner.java:275)
	at org.gradle.testkit.runner.internal.DefaultGradleRunner.run(DefaultGradleRunner.java:374)
	at org.gradle.testkit.runner.internal.DefaultGradleRunner.build(DefaultGradleRunner.java:273)
	at org.grails.forge.utils.CommandSpec.executeGradle(CommandSpec.groovy:79)
	at org.grails.forge.create.CreateAppSpec.test basic create-app build task(CreateAppSpec.groovy:49)
CreateControllerCommandSpec > test app with controller (:grails-forge-cli:test [grails-forge] in CI / Build Grails Forge (Java 21, indy=true))
Condition not satisfied after 240.00 seconds and 238 attempts
	at spock.util.concurrent.PollingConditions.within(PollingConditions.java:205)
	at spock.util.concurrent.PollingConditions.eventually(PollingConditions.java:157)
	at org.grails.forge.cli.CommandSpec.testOutputContains(CommandSpec.groovy:64)
	at org.grails.forge.cli.command.CreateControllerCommandSpec.test app with controller(CreateControllerCommandSpec.groovy:73)
Caused by: Condition not satisfied:

output.toString().contains(value)
|      |          |        |
|      |          false    BUILD SUCCESSFUL
|      To honour the JVM settings for this build a single-use Daemon process will be forked. For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/gradle_daemon.html#sec:disabling_the_daemon in the Gradle documentation.
|      Daemon will be stopped at the end of the build 
|      gradle/actions: Writing build results to /home/runner/work/_temp/.gradle-actions/build-results/__run_3-1783442429844.json
|       
|      [Incubating] Problems report is available at: file:///tmp/grailsforgetmp4460596145617059160/build/reports/problems/problems-report.html
|       
|      Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.
|       
|      You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
|       
|      For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
To honour the JVM settings for this build a single-use Daemon process will be forked. For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/gradle_daemon.html#sec:disabling_the_daemon in the Gradle documentation.
Daemon will be stopped at the end of the build 
gradle/actions: Writing build results to /home/runner/work/_temp/.gradle-actions/build-results/__run_3-1783442429844.json
 
[Incubating] Problems report is available at: file:///tmp/grailsforgetmp4460596145617059160/build/reports/problems/problems-report.html
 
Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.
 
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
 
For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

	at org.grails.forge.cli.CommandSpec.testOutputContains_closure1(CommandSpec.groovy:65)
	at org.grails.forge.cli.CommandSpec.testOutputContains_closure1(CommandSpec.groovy)
	at spock.util.concurrent.PollingConditions.within(PollingConditions.java:185)
	... 3 more

Muted Tests (first 20 of 24)

Select tests to mute in this pull request:

  • CreateAppSpec > test basic create-app build task
  • CreateControllerCommandSpec > test app with controller
  • CreateDomainClassCommandSpec > test app with domain
  • CreateInterceptorCommandSpec > test app with interceptor
  • CreateRestApiSpec > test rest-api app with default json-views
  • CreateServiceCommandSpec > test app with service
  • CreateTagLibCommandSpec > test app with taglib
  • ScaffoldingSpec > test generate-controller command

Reuse successful test results:

  • ♻️ Only rerun the tests that failed or were muted before

Click the checkbox to trigger a rerun:

  • Rerun jobs

Learn more about TestLens at testlens.app.

@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 62.00000% with 38 lines in your changes missing coverage. Please review.
✅ Project coverage is 49.3765%. Comparing base (2650b8c) to head (0aa3569).
⚠️ Report is 90 commits behind head on 8.0.x.

Files with missing lines Patch % Lines
...oovy/grails/web/servlet/mvc/GrailsHttpSession.java 58.8235% 21 Missing ⚠️
...ils/plugins/codecs/HexCodecExtensionMethods.groovy 37.5000% 8 Missing and 2 partials ⚠️
...roovy/org/grails/plugins/codecs/DigestUtils.groovy 76.9231% 5 Missing and 1 partial ⚠️
.../plugins/codecs/Base64CodecExtensionMethods.groovy 66.6667% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##                8.0.x     #15828        +/-   ##
==================================================
+ Coverage     49.2998%   49.3765%   +0.0767%     
- Complexity      16727      16794        +67     
==================================================
  Files            1985       1985                
  Lines           93252      93342        +90     
  Branches        16316      16341        +25     
==================================================
+ Hits            45973      46089       +116     
+ Misses          40166      40130        -36     
- Partials         7113       7123        +10     
Files with missing lines Coverage Δ
...lugins/codecs/MD5BytesCodecExtensionMethods.groovy 75.0000% <ø> (ø)
...ils/plugins/codecs/MD5CodecExtensionMethods.groovy 100.0000% <100.0000%> (ø)
...ugins/codecs/SHA1BytesCodecExtensionMethods.groovy 75.0000% <ø> (ø)
...ls/plugins/codecs/SHA1CodecExtensionMethods.groovy 75.0000% <100.0000%> (ø)
...ins/codecs/SHA256BytesCodecExtensionMethods.groovy 75.0000% <ø> (ø)
.../plugins/codecs/SHA256CodecExtensionMethods.groovy 75.0000% <100.0000%> (+25.0000%) ⬆️
.../plugins/codecs/Base64CodecExtensionMethods.groovy 60.0000% <66.6667%> (ø)
...roovy/org/grails/plugins/codecs/DigestUtils.groovy 71.4286% <76.9231%> (+13.0952%) ⬆️
...ils/plugins/codecs/HexCodecExtensionMethods.groovy 38.0952% <37.5000%> (+3.3126%) ⬆️
...oovy/grails/web/servlet/mvc/GrailsHttpSession.java 64.0625% <58.8235%> (+32.8125%) ⬆️

... and 18 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants