[ 
https://issues.apache.org/jira/browse/GROOVY-11792?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18098589#comment-18098589
 ] 

ASF GitHub Bot commented on GROOVY-11792:
-----------------------------------------

testlens-app[bot] commented on PR #2732:
URL: https://github.com/apache/groovy/pull/2732#issuecomment-5062892010

   ## 🚨 TestLens detected 1 failed test 🚨
   
   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
   
   #### [Build and test / lts \(17, 
macos-latest\)](https://github.com/apache/groovy/actions/runs/30039685242/job/89316265315?pr=2732)
 > :test
   
   | Test | Runs | Flakiness |
   |---|---|--:|
   | ParallelAnnotationTest > testParallelForRunsConcurrently\(\) | ❌ | 
1% đźźˇ |
   
   🏷️ Commit: 2e90e7f66daf81cb5ab8b637d4d7dc96abb7b199
   ▶️ Tests:  50060 executed
   🟡 Checks: 6/29 completed
   
   ### Test Failures
   
   <details>
   
   <summary><strong>ParallelAnnotationTest > 
testParallelForRunsConcurrently()</strong> (:test in <a 
href="https://github.com/apache/groovy/actions/runs/30039685242/job/89316265315?pr=2732";>Build
 and test / lts (17, macos-latest)</a>)</summary>
   
   ```
   java.lang.reflect.UndeclaredThrowableException
        at 
java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native
 Method)
        at 
java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
        at 
java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at 
java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500)
        at 
java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:481)
        at 
java.base/java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:562)
        at 
java.base/java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:591)
        at 
java.base/java.util.concurrent.ForkJoinTask.join(ForkJoinTask.java:672)
        at 
org.codehaus.groovy.runtime.ParallelCollectionExtensions.lambda$eachParallel$1(ParallelCollectionExtensions.java:78)
        at 
org.codehaus.groovy.runtime.ParallelCollectionExtensions.withCurrentFJP(ParallelCollectionExtensions.java:289)
        at 
org.codehaus.groovy.runtime.ParallelCollectionExtensions.eachParallel(ParallelCollectionExtensions.java:77)
        at TestScript35.run(TestScript35.groovy:11)
        at groovy.lang.GroovyShell.evaluate(GroovyShell.java:553)
        at groovy.lang.GroovyShell.evaluate(GroovyShell.java:588)
        at groovy.lang.GroovyShell.evaluate(GroovyShell.java:572)
        at groovy.test.GroovyAssert.assertScript(GroovyAssert.java:106)
        at groovy.test.GroovyAssert.assertScript(GroovyAssert.java:96)
        at 
groovy.concurrent.ParallelAnnotationTest.testParallelForRunsConcurrently(ParallelAnnotationTest.groovy:139)
   Caused by: java.lang.reflect.UndeclaredThrowableException
        at jdk.proxy2/jdk.proxy2.$Proxy57.accept(Unknown Source)
        at 
java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
        at 
java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:992)
        at 
java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
        at 
java.base/java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:290)
        at 
java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:754)
        at 
java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
        at 
java.base/java.util.concurrent.ForkJoinPool$WorkQueue.helpComplete(ForkJoinPool.java:1234)
        at 
java.base/java.util.concurrent.ForkJoinPool.helpComplete(ForkJoinPool.java:1926)
        at 
java.base/java.util.concurrent.ForkJoinTask.awaitDone(ForkJoinTask.java:433)
        at 
java.base/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:687)
        at 
java.base/java.util.stream.ForEachOps$ForEachOp.evaluateParallel(ForEachOps.java:159)
        at 
java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateParallel(ForEachOps.java:173)
        at 
java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
        at 
java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
        at 
java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:765)
        at 
org.codehaus.groovy.runtime.ParallelCollectionExtensions.lambda$eachParallel$0(ParallelCollectionExtensions.java:78)
        at 
java.base/java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(ForkJoinTask.java:1375)
        at 
java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
        at 
java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1193)
        at 
java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1666)
        at 
java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1633)
        at 
java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)
   Caused by: java.util.concurrent.TimeoutException
        at 
java.base/java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:259)
        at 
java.base/java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:437)
        at java.base/java.lang.reflect.Method.invoke(Method.java:569)
        at 
org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:504)
        at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:298)
        at 
org.codehaus.groovy.vmplugin.v8.IndyInterface.invokeColdReflective(IndyInterface.java:469)
        at TestScript35$_run_closure1.doCall(TestScript35.groovy:14)
        at java.base/java.lang.reflect.Method.invoke(Method.java:569)
        at groovy.lang.Closure.call(Closure.java:563)
        at 
org.codehaus.groovy.runtime.ConvertedClosure.invokeCustom(ConvertedClosure.java:55)
        at 
org.codehaus.groovy.runtime.ConversionHandler.invoke(ConversionHandler.java:115)
        ... 23 more
   ```
   
   </details>
   
   ### Muted Tests
   > [!NOTE]
   > Checks are currently running using the configuration below.
   
   Select tests to mute in this pull request:
   
   🔲 ParallelAnnotationTest > testParallelForRunsConcurrently\(\) <!

> for-in loop variable captured by closure/AIC sees final value, not 
> per-iteration value
> --------------------------------------------------------------------------------------
>
>                 Key: GROOVY-11792
>                 URL: https://issues.apache.org/jira/browse/GROOVY-11792
>             Project: Groovy
>          Issue Type: Bug
>            Reporter: Daniel Sun
>            Priority: Major
>
> h2. Problem
> When a *for-in* (enhanced for-each) loop variable is shared with a deferred 
> closure or anonymous inner class (AIC), all captures observe the *final* loop 
> value after the loop finishes, instead of the value from the iteration that 
> created the capture.
> Classic {{for}} / {{while}} loops are a separate construct and are not the 
> subject of this report.
> This is the same class of surprise as Java’s historical “effectively final 
> loop variable” / deferred-lambda story, but Groovy’s shared variables use a 
> single {{groovy.lang.Reference}} updated in place across for-in iterations, 
> so deferred use always sees the last assignment.
> h2. Expected behaviour
> Each deferred capture should observe the for-in value (and index, when 
> present) from the {*}iteration in which it was created{*}.
> h2. Actual behaviour
> Every deferred capture sees the *last* iteration’s value.
> h2. Reproducer
> {code:groovy}
> import java.util.function.Supplier
> def numbers = [1, 2, 3]
> List suppliers = []
> for (n in numbers) {
>     Supplier s = { n * n }
>     suppliers << s
> }
> // Expected: [1, 4, 9]
> // Actual:   [9, 9, 9]
> assert suppliers.collect { it.get() } == [1, 4, 9]
> {code}
> Colon syntax ({{{}for (Integer n : numbers){}}}), {{{}@CompileStatic{}}}, 
> indexed for-in ({{{}for (i, v in …){}}}), array/enumeration SC paths, and AIC 
> capture (see GROOVY-11818) show the same pattern.
> h2. Workaround
> Introduce a fresh local per iteration so the closure captures a non-shared 
> (or newly shared) binding:
> {code:groovy}
> for (n in numbers) {
>     def local = n
>     suppliers << { local * local }
> }
> {code}
> h2. Root cause (classgen)
> For-in loop heads store into a *single* shared {{Reference}} for 
> closure-shared loop variables ({{{}OperandStack.storeVar{}}} → 
> {{{}Reference#set{}}}). Closures/AICs created in the body capture that one 
> holder; later iterations overwrite it.
> Relevant area: {{org.codehaus.groovy.classgen.asm}} ({{{}StatementWriter{}}}, 
> {{{}StaticTypesStatementWriter{}}}, {{{}CompileStack{}}}, 
> {{{}WriterController{}}}).
> h2. Resolution approach
>  * When per-iteration capture is enabled (default), each for-in iteration 
> that stores a *holder* loop variable allocates a *fresh* 
> {{groovy.lang.Reference}} (value and shared index).
>  * Within the *same* iteration, assignment to the loop variable remains 
> visible to captures created there (still one {{Reference}} per iteration).
>  * Non-shared loop variables and non-loop shared variables are unchanged.
>  * Dynamic and static compilation for-in paths (iterator, SC array, SC 
> enumeration) share the same store/increment helpers.
> h3. Language-compatibility opt-out
> This changes observable capture semantics (default-on). Historical “single 
> shared {{Reference}} / final value” behaviour can be restored with either:
>  * system property: {{groovy.for.loop.capture=false}}
>  * {{{}CompilerConfiguration{}}}: put {{Boolean.FALSE}} for key 
> {{CompilerConfiguration.FOR_LOOP_CAPTURE}} ({{{}"forLoopCapture"{}}}) in the 
> optimization-options map
> Notes:
>  * This is a *language-compatibility* switch, not a performance optimization.
>  * Setting optimization option {{"all"}} to {{false}} does *not* disable 
> for-in recapture.
> h2. Related issues
>  * GROOVY-11818 — for-in variable captured by anonymous inner class (same 
> root cause)
>  * GROOVY-11751 — indexed for-in with shared index (holder index 
> store/increment)



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to