groovy-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Björn Kautler (JIRA) <j...@apache.org>
Subject [jira] [Created] (GROOVY-8213) Closures are maybe not Threadsafe
Date Thu, 01 Jun 2017 11:47:04 GMT
Björn Kautler created GROOVY-8213:
-------------------------------------

             Summary: Closures are maybe not Threadsafe
                 Key: GROOVY-8213
                 URL: https://issues.apache.org/jira/browse/GROOVY-8213
             Project: Groovy
          Issue Type: Bug
    Affects Versions: 2.4.10
         Environment: Gradle 3.5
            Reporter: Björn Kautler


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
(v6.3.15#6346)

Mime
View raw message