Uploaded image for project: 'Groovy'
  1. Groovy
  2. GROOVY-9397

CLONE - Closures are maybe not Threadsafe

    XMLWordPrintableJSON

Details

    • Bug
    • Status: Closed
    • Major
    • Resolution: Fixed
    • 2.4.10
    • 3.0.8, 4.0.0-alpha-3
    • None
    • None
    • Gradle 3.5

    Description

      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:

            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
      

      When this task is executed e. g. with five source files, sometimes all works fine, sometime the build fails with

      ...
      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
      

      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:

      "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)
      

      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.

      Attachments

        Issue Links

          Activity

            People

              daniel_sun Daniel Sun
              vampire Björn Kautler
              Votes:
              0 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved:

                Time Tracking

                  Estimated:
                  Original Estimate - Not Specified
                  Not Specified
                  Remaining:
                  Remaining Estimate - 0h
                  0h
                  Logged:
                  Time Spent - 50m
                  50m