You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2021/01/14 14:20:48 UTC
[groovy] branch master updated: Tweak resource management in GINQ
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 <su...@apache.org>
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.')
+ }
}