The GitHub Actions job "Code Style" on grails-core.git/grails8-groovy6-canary 
has failed.
Run started by GitHub user jamesfredley (triggered by jamesfredley).

Head commit for run:
ddc7ea20c6f783a66a684603e298c85c3a461ce2 / James Fredley 
<[email protected]>
fix: serialise GSP compilation under Groovy 6 to dodge ListHashMap race

Every CI job that compiled GSPs against Groovy 6.0.0-SNAPSHOT failed
with a Groovy compiler stack like:

  General error during instruction selection: Index 3 out of bounds for length 3
  java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
    at org.codehaus.groovy.util.ListHashMap.toMap(ListHashMap.java:207)
    at org.codehaus.groovy.util.ListHashMap.put(ListHashMap.java:146)
    at java.base/java.util.Map.computeIfAbsent(Map.java:1067)
    at 
org.codehaus.groovy.ast.NodeMetaDataHandler.getNodeMetaData(NodeMetaDataHandler.java:65)
    at 
org.codehaus.groovy.ast.AnnotationNode.isTargetAllowed(AnnotationNode.java:168)
    at 
org.codehaus.groovy.classgen.ExtendedVerifier.visitAnnotations(ExtendedVerifier.java:354)
    at 
org.codehaus.groovy.classgen.ExtendedVerifier.visitConstructor(ExtendedVerifier.java:216)
  ...
  at 
org.grails.web.pages.GroovyPageForkedCompiler.main(GroovyPageForkedCompiler.groovy:106)

`AnnotationNode.isTargetAllowed` was added in Groovy 6 (GROOVY-11838) to
honour the new default annotation targets and uses
`NodeMetaDataHandler.getNodeMetaData` (a `Map.computeIfAbsent` over an
internal `ListHashMap`) on shared `Annotation*` AST nodes. That cache
is touched concurrently by the Grails `GroovyPageCompiler` thread pool
(`Executors.newFixedThreadPool(availableProcessors() * 2)`) once shared
annotations like `@Inject`, `@CompileStatic`, etc. are seen by more
than one GSP compile at the same time, which is exactly the case for
test apps that pull in Spring/Grails compiled output. `ListHashMap` is
not designed for concurrent mutation, so the resize fails with an
`ArrayIndexOutOfBoundsException` and the entire GSP compile aborts.

Replace the unconditional `availableProcessors() * 2` thread pool with
a small `computeGspCompilerParallelism()` helper that:

* defaults to 1 worker on Groovy 6 (eliminates the race),
* defaults to `availableProcessors() * 2` on Groovy 5 and earlier
  (preserves prior behaviour),
* honours `-Dgrails.gsp.compiler.parallelism=N` so callers can opt back
  into parallel GSP compilation once Groovy 6 fixes the race (or
  experimentally tune it down on Groovy 5).

Trade-off: a small wall-clock increase on Groovy 6 GSP compilation in
exchange for deterministic behaviour. The control knob is a single
system property, so this is easy to revert once the upstream Groovy
fix is available.

Verified locally:
  ./gradlew :grails-gsp-core:compileGroovy --rerun-tasks -> BUILD SUCCESSFUL

Assisted-by: claude-code:claude-opus-4-7

Report URL: https://github.com/apache/grails-core/actions/runs/24934605888

With regards,
GitHub Actions via GitBox

Reply via email to