You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@groovy.apache.org by "Benjamin Roe (Jira)" <ji...@apache.org> on 2021/03/03 12:25:00 UTC

[jira] [Commented] (GROOVY-9397) CLONE - Closures are maybe not Threadsafe

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

Benjamin Roe commented on GROOVY-9397:
--------------------------------------

In case this is useful to anyone, I can reproduce this issue with this code:
{code:java}
import java.util.concurrent.Executors
def executor = Executors.newCachedThreadPool()
final Closure action = { -> }
while (true) {
  executor.execute(action)
  executor.execute(() -> action.respondsTo('test'))
} {code}
Looking at ClosureMetaClass I suspect the problem is in the way loadMetaInfo() resets the initialized state of the object with no locking to prevent other threads seeing the Closure as temporarily uninitialized. I'm unsure of the safest/most performant way of fixing the problem. I tested with "Groovy Version: 3.0.7 JVM: 11.0.7 Vendor: AdoptOpenJDK OS: Mac OS X"

> CLONE - Closures are maybe not Threadsafe
> -----------------------------------------
>
>                 Key: GROOVY-9397
>                 URL: https://issues.apache.org/jira/browse/GROOVY-9397
>             Project: Groovy
>          Issue Type: Bug
>    Affects Versions: 2.4.10
>         Environment: Gradle 3.5
>            Reporter: Björn Kautler
>            Priority: Major
>
> I just upgraded our Gradle build from 1.12 (including Groovy 1.8.6) to 3.5 (including Groovy 2.4.10).
> Now I get a very strange behavior for stuff that is there for years already and it looks to me as if this is a Groovy bug, maybe a non-threadsafeness of Closures.
> I have a custom task that contains the following code:
> {code}
>       def sourceFilesSize = getSourceFiles().files.size()
>       def poolSize = Runtime.runtime.availableProcessors()
>       def executor = new ThreadPoolExecutor(poolSize, poolSize, 0, SECONDS, new ArrayBlockingQueue<Runnable>([sourceFilesSize, 1].max()))
>       def tasks = []
>       inputs.outOfDate { toTransform ->
>          tasks.add executor.submit {
>             project.exec {
>                if (gscPath) { // here starts com.empic.build.tasks.Ghostscript$_exec_closure8$_closure11
>                   environment 'GSC', gscPath
>                   def tempDir = "$temporaryDir/${Thread.currentThread().name}"
>                   project.file(tempDir).mkdirs()
>                   environment 'TEMP', tempDir
>                }
>                executable executablePath
>                def arguments = [toTransform.file.absolutePath]
>                if ((sourceFilesSize == 1) && getDestinationFile()) {
>                   arguments << getDestinationFile().absolutePath
>                } else if (destinationDirectory) {
>                   arguments << new File(destinationDirectory, fileNameMapping(toTransform.file.name)).absolutePath
>                } else {
>                   arguments << new File(toTransform.file.parentFile, fileNameMapping(toTransform.file.name)).absolutePath
>                }
>                args arguments // here ends com.empic.build.tasks.Ghostscript$_exec_closure8$_closure11
>             }
>          }
>       }
>       executor.shutdown()
>       executor.awaitTermination 1, HOURS
>       tasks*.get() // here is line 137
> {code}
> When this task is executed e. g. with five source files, sometimes all works fine, sometime the build fails with
> {noformat}
> ...
> Caused by: java.util.concurrent.ExecutionException: java.lang.IllegalStateException: initialize must be called for meta class of class com.empic.build.tasks.Ghostscript$_exec_closure8$_closure11(class org.codehaus.groovy.runtime.metaclass.ClosureMetaClass) to complete initialisation process before any invocation or field/property access can be done
>         at com.empic.build.tasks.Ghostscript.exec(Ghostscript.groovy:137)
>         at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:73)
>         ... 80 more
> Caused by: java.lang.IllegalStateException: initialize must be called for meta class of class com.empic.build.tasks.Ghostscript$_exec_closure8$_closure11(class org.codehaus.groovy.runtime.metaclass.ClosureMetaClass) to complete initialisation process before any invocation or field/property access can be done
> {noformat}
> Actually I was not able to reproduce the problem locally, but if I run the build on our CI server and attach a debugger, I indeed was able to reproduce the problem with a relatively high reproduction rate. (as in every second to third build approximately, opposed to once every 100 builds)
> I logged the current thread when the {{initialized}} property is set to {{true}} and when it is requested with non-suspending breakpoints, but this seems to have caused the timing to change enough already that I was not able to reproduce the error within approximately the first two dozens of tries.
> I was able to successfully break at the throwing of the {{IllegalStateException}} though. This happens in {{groovy.lang.MetaClassImpl.checkInitalised}}.
> The stacktrace at this point is:
> {noformat}
> "pool-2-thread-3@10709" prio=5 tid=0x47 nid=NA runnable
>   java.lang.Thread.State: RUNNABLE
> 	  at groovy.lang.MetaClassImpl.checkInitalised(MetaClassImpl.java:1647)
> 	  at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:257)
> 	  at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1027)
> 	  at groovy.lang.Closure.call(Closure.java:414)
> 	  at groovy.lang.Closure.call(Closure.java:408)
> 	  at groovy.lang.Closure.run(Closure.java:495)
> 	  at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
> 	  at java.util.concurrent.FutureTask.run(FutureTask.java:266)
> 	  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
> 	  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
> 	  at java.lang.Thread.run(Thread.java:745)
> {noformat}
> with the closure for which we are at the {{ClosureMetaClass}} in the topmost frame being the mentioned {{com.empic.build.tasks.Ghostscript$_exec_closure8$_closure11}}.



--
This message was sent by Atlassian Jira
(v8.3.4#803005)