[groovy] branch master updated: Tweak resource management in GINQ

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[groovy] branch master updated: Tweak resource management in GINQ

Daniel.Sun
This is an automated email from the ASF dual-hosted git repository.

sunlan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new ab55e6e  Tweak resource management in GINQ
ab55e6e is described below

commit ab55e6ecd7bb651fb4c2b214e570e728d8d1a8ba
Author: Daniel Sun <[hidden email]>
AuthorDate: Thu Jan 14 22:19:46 2021 +0800

    Tweak resource management in GINQ
---
 .../apache/groovy/ginq/GinqGroovyMethods.groovy    | 41 ++++++++++++++++++++++
 .../collection/runtime/QueryableHelper.groovy      | 28 ++++++++++-----
 .../groovy-ginq/src/spec/doc/ginq-userguide.adoc   | 18 ++++++++++
 .../test/org/apache/groovy/ginq/GinqTest.groovy    | 39 ++++++++++++++++++++
 .../org/apache/groovy/ginq/GinqErrorTest.groovy    | 11 ++++++
 5 files changed, 128 insertions(+), 9 deletions(-)

diff --git a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/GinqGroovyMethods.groovy b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/GinqGroovyMethods.groovy
index 7535c37..36afc7c 100644
--- a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/GinqGroovyMethods.groovy
+++ b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/GinqGroovyMethods.groovy
@@ -24,13 +24,21 @@ import org.apache.groovy.ginq.dsl.GinqAstOptimizer
 import org.apache.groovy.ginq.dsl.GinqAstVisitor
 import org.apache.groovy.ginq.dsl.expression.GinqExpression
 import org.apache.groovy.ginq.provider.collection.GinqAstWalker
+import org.apache.groovy.ginq.provider.collection.runtime.QueryableHelper
 import org.apache.groovy.lang.annotation.Incubating
 import org.codehaus.groovy.ast.ASTNode
 import org.codehaus.groovy.ast.ClassHelper
+import org.codehaus.groovy.ast.expr.ArgumentListExpression
+import org.codehaus.groovy.ast.expr.ClassExpression
 import org.codehaus.groovy.ast.expr.ClosureExpression
+import org.codehaus.groovy.ast.expr.ConstantExpression
 import org.codehaus.groovy.ast.expr.Expression
 import org.codehaus.groovy.ast.expr.MapEntryExpression
 import org.codehaus.groovy.ast.expr.MapExpression
+import org.codehaus.groovy.ast.expr.MethodCallExpression
+import org.codehaus.groovy.ast.expr.VariableExpression
+import org.codehaus.groovy.ast.stmt.BlockStatement
+import org.codehaus.groovy.ast.stmt.ExpressionStatement
 import org.codehaus.groovy.ast.stmt.Statement
 import org.codehaus.groovy.control.SourceUnit
 import org.codehaus.groovy.control.messages.SyntaxErrorMessage
@@ -38,7 +46,9 @@ import org.codehaus.groovy.macro.runtime.Macro
 import org.codehaus.groovy.macro.runtime.MacroContext
 import org.codehaus.groovy.syntax.SyntaxException
 
+import static org.codehaus.groovy.ast.ClassHelper.makeCached
 import static org.codehaus.groovy.ast.tools.GeneralUtils.asX
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX
 
 /**
  * Declare GINQ macro methods
@@ -90,6 +100,32 @@ class GinqGroovyMethods {
 
     static Expression transformGinqCode(SourceUnit sourceUnit, MapExpression ginqConfigurationMapExpression, Statement code) {
         GinqAstBuilder ginqAstBuilder = new GinqAstBuilder(sourceUnit)
+
+        if (code instanceof BlockStatement) {
+            List<Statement> statementList = code.statements
+            if (1 == statementList.size()) {
+                Statement statement = statementList[0]
+                if (statement instanceof ExpressionStatement) {
+                    def expression = ((ExpressionStatement) statement).expression
+                    if (expression instanceof MethodCallExpression && ((MethodCallExpression) expression).methodAsString == 'shutdown') {
+                        List<Expression> argExpressionList = ((ArgumentListExpression) ((MethodCallExpression) expression).arguments).expressions
+                        if (1 == argExpressionList.size()) {
+                            Expression argExpression = argExpressionList[0]
+                            if (argExpression instanceof VariableExpression) {
+                                int mode = SHUTDOWN_OPTION_LIST.indexOf(argExpression.text)
+                                if (-1 == mode) {
+                                    collectErrors("Invalid option: ${argExpression.text}. (supported options: ${SHUTDOWN_OPTION_LIST})", argExpression, sourceUnit)
+                                }
+                                return shutdownMethodCallExpression(mode)
+                            }
+                        }
+                    } else if (expression instanceof VariableExpression && ((VariableExpression) expression).text == 'shutdown') {
+                        return shutdownMethodCallExpression(SHUTDOWN_OPTION_LIST.indexOf('immediate'))
+                    }
+                }
+            }
+        }
+
         code.visit(ginqAstBuilder)
         GinqExpression ginqExpression = ginqAstBuilder.getGinqExpression()
 
@@ -107,6 +143,10 @@ class GinqGroovyMethods {
         return (Expression) ginqAstWalker.visitGinqExpression(ginqExpression)
     }
 
+    private static MethodCallExpression shutdownMethodCallExpression(int mode) {
+        return callX(new ClassExpression(makeCached(QueryableHelper)), 'shutdown', new ConstantExpression(mode))
+    }
+
     private static Map<String, String> createConfiguration(SourceUnit sourceUnit, MapExpression ginqConfigurationMapExpression) {
         Map<String, String> configuration = [:]
         if (!ginqConfigurationMapExpression) return configuration
@@ -137,6 +177,7 @@ class GinqGroovyMethods {
     private static final String CONF_AST_WALKER = 'astWalker'
     private static final String CONF_OPTIMIZE = 'optimize'
     private static final List<String> CONF_LIST = [CONF_PARALLEL, CONF_AST_WALKER, CONF_OPTIMIZE]
+    private static final List<String> SHUTDOWN_OPTION_LIST = ['immediate', 'abort']
     private static final String DEFAULT_AST_WALKER_CLASS_NAME = GinqAstWalker.class.name
     private static final String TRUE_STR = 'true'
 }
diff --git a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableHelper.groovy b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableHelper.groovy
index bb458d5..b5b1bf2 100644
--- a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableHelper.groovy
+++ b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableHelper.groovy
@@ -23,6 +23,7 @@ import groovy.transform.CompileStatic
 import java.util.concurrent.CompletableFuture
 import java.util.concurrent.ExecutorService
 import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
 import java.util.function.Function
 import java.util.function.Supplier
 import java.util.stream.Collectors
@@ -99,19 +100,28 @@ class QueryableHelper {
         (T) VAR_HOLDER.get().remove(name)
     }
 
+    /**
+     * Shutdown to release resources
+     *
+     * @param mode 0: immediate, 1: abort
+     */
+    static shutdown(int mode) {
+        if (0 == mode) {
+            THREAD_POOL.shutdown()
+            while (!THREAD_POOL.awaitTermination(250, TimeUnit.MILLISECONDS)) {
+                // do nothing, just wait to terminate
+            }
+        } else if (1 == mode) {
+            THREAD_POOL.shutdownNow()
+        } else {
+            throw new IllegalArgumentException("Invalid mode: $mode")
+        }
+    }
+
     private static final ThreadLocal<Map<String, Object>> VAR_HOLDER = ThreadLocal.<Map<String, Object>> withInitial(() -> new LinkedHashMap<>())
     private static final ExecutorService THREAD_POOL = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())
     private static final String PARALLEL = "parallel"
     private static final String TRUE_STR = "true"
 
-    static {
-        Runtime.addShutdownHook {
-            try {
-                THREAD_POOL.shutdownNow()
-            } catch (ignored) {
-            }
-        }
-    }
-
     private QueryableHelper() {}
 }
diff --git a/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc b/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
index 04bb22c..27b2a80 100644
--- a/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
+++ b/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
@@ -697,6 +697,24 @@ Parallel querying is especially efficient when querying big data sources. It is
 include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_tips_08,indent=0]
 ----
 
+As parallel querying will use a shared thread pool, the following code should be executed to release resources before all GINQ statements are completed, or JVM will not exit. The code will wait util all tasks of theads are completed.
+[source, groovy]
+----
+include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_tips_09,indent=0]
+----
+
+The following code is equivalent to the above code:
+[source, groovy]
+----
+include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_tips_10,indent=0]
+----
+
+Shutdown without waiting tasks to complete:
+[source, groovy]
+----
+include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_tips_11,indent=0]
+----
+
 ==== Customize GINQ
 
 For advanced users, you could customize GINQ behaviour by specifying your own target code generator.
diff --git a/subprojects/groovy-ginq/src/spec/test/org/apache/groovy/ginq/GinqTest.groovy b/subprojects/groovy-ginq/src/spec/test/org/apache/groovy/ginq/GinqTest.groovy
index 881205c..af8c7d2 100644
--- a/subprojects/groovy-ginq/src/spec/test/org/apache/groovy/ginq/GinqTest.groovy
+++ b/subprojects/groovy-ginq/src/spec/test/org/apache/groovy/ginq/GinqTest.groovy
@@ -5694,6 +5694,45 @@ class GinqTest {
         '''
     }
 
+    @Test
+    void "testGinq - shutdown - 0"() {
+        assertScript '''
+            import org.apache.groovy.ginq.provider.collection.runtime.QueryableHelper
+// tag::ginq_tips_09[]
+            GQ {
+                shutdown
+            }
+// end::ginq_tips_09[]
+            assert QueryableHelper.THREAD_POOL.isShutdown() && QueryableHelper.THREAD_POOL.isTerminated()
+        '''
+    }
+
+    @Test
+    void "testGinq - shutdown - 1"() {
+        assertScript '''
+            import org.apache.groovy.ginq.provider.collection.runtime.QueryableHelper
+// tag::ginq_tips_10[]
+            GQ {
+                shutdown immediate
+            }
+// end::ginq_tips_10[]
+            assert QueryableHelper.THREAD_POOL.isShutdown() && QueryableHelper.THREAD_POOL.isTerminated()
+        '''
+    }
+
+    @Test
+    void "testGinq - shutdown - 2"() {
+        assertScript '''
+            import org.apache.groovy.ginq.provider.collection.runtime.QueryableHelper
+// tag::ginq_tips_11[]
+            GQ {
+                shutdown abort
+            }
+// end::ginq_tips_11[]
+            assert QueryableHelper.THREAD_POOL.isShutdown()
+        '''
+    }
+
     private static void assertGinqScript(String script) {
         String deoptimizedScript = script.replaceAll(/\bGQ\s*[{]/, 'GQ(optimize:false) {')
         List<String> scriptList = [deoptimizedScript, script]
diff --git a/subprojects/groovy-ginq/src/test/groovy/org/apache/groovy/ginq/GinqErrorTest.groovy b/subprojects/groovy-ginq/src/test/groovy/org/apache/groovy/ginq/GinqErrorTest.groovy
index 83037ae..53c0200 100644
--- a/subprojects/groovy-ginq/src/test/groovy/org/apache/groovy/ginq/GinqErrorTest.groovy
+++ b/subprojects/groovy-ginq/src/test/groovy/org/apache/groovy/ginq/GinqErrorTest.groovy
@@ -565,4 +565,15 @@ class GinqErrorTest {
 
         assert err.toString().contains('Invalid option: xxx. (supported options: [parallel, astWalker, optimize]) @ line 1, column 16.')
     }
+
+    @Test
+    void "testGinq - shutdown - 1"() {
+        def err = shouldFail '''\
+            GQ {
+                shutdown zzz
+            }
+        '''
+
+        assert err.toString().contains('Invalid option: zzz. (supported options: [immediate, abort]) @ line 2, column 26.')
+    }
 }

Apache Groovy committer & PMC member

Blog: http://blog.sunlan.me
Twitter: @daniel_sun