diff --git a/src/main/java/org/elasticsearch/script/NativeScriptEngineService.java b/src/main/java/org/elasticsearch/script/NativeScriptEngineService.java index 5094917606cb9..9fe4e02a7db8e 100644 --- a/src/main/java/org/elasticsearch/script/NativeScriptEngineService.java +++ b/src/main/java/org/elasticsearch/script/NativeScriptEngineService.java @@ -93,4 +93,9 @@ public Object unwrap(Object value) { @Override public void close() { } + + @Override + public void scriptRemoved(CompiledScript script) { + // Nothing to do here + } } \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/script/ScriptEngineService.java b/src/main/java/org/elasticsearch/script/ScriptEngineService.java index d213601bc1de5..25a1a817db3b7 100644 --- a/src/main/java/org/elasticsearch/script/ScriptEngineService.java +++ b/src/main/java/org/elasticsearch/script/ScriptEngineService.java @@ -46,4 +46,11 @@ public interface ScriptEngineService { Object unwrap(Object value); void close(); + + /** + * Handler method called when a script is removed from the Guava cache. + * + * The passed script may be null if it has already been garbage collected. + * */ + void scriptRemoved(@Nullable CompiledScript script); } diff --git a/src/main/java/org/elasticsearch/script/ScriptService.java b/src/main/java/org/elasticsearch/script/ScriptService.java index ecb788b8a23bd..732dab3a3764b 100644 --- a/src/main/java/org/elasticsearch/script/ScriptService.java +++ b/src/main/java/org/elasticsearch/script/ScriptService.java @@ -22,9 +22,12 @@ import com.google.common.base.Charsets; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import com.google.common.cache.RemovalListener; +import com.google.common.cache.RemovalNotification; import com.google.common.collect.ImmutableMap; import org.elasticsearch.ElasticsearchIllegalArgumentException; import org.elasticsearch.ElasticsearchIllegalStateException; +import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; @@ -64,12 +67,15 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; +import static com.google.common.collect.Lists.newArrayList; + /** * */ @@ -212,7 +218,7 @@ public ScriptService(Settings settings, Environment env, Set builder = ImmutableMap.builder(); @@ -489,6 +496,30 @@ private boolean dynamicScriptEnabled(String lang) { } } + /** + * A small listener for the script cache that calls each + * {@code ScriptEngineService}'s {@code scriptRemoved} method when the + * script has been removed from the cache + */ + private class ScriptCacheRemovalListener implements RemovalListener { + + @Override + public void onRemoval(RemovalNotification notification) { + if (logger.isDebugEnabled()) { + logger.debug("notifying script services of script removal due to: [{}]", notification.getCause()); + } + List errors = newArrayList(); + for (ScriptEngineService service : scriptEngines.values()) { + try { + service.scriptRemoved(notification.getValue()); + } catch (Exception e) { + errors.add(e); + } + } + ExceptionsHelper.maybeThrowRuntimeAndSuppress(errors); + } + } + private class ScriptChangesListener extends FileChangesListener { private Tuple scriptNameExt(File file) { diff --git a/src/main/java/org/elasticsearch/script/expression/ExpressionScriptEngineService.java b/src/main/java/org/elasticsearch/script/expression/ExpressionScriptEngineService.java index 28951f8b2732f..dcf83df668025 100644 --- a/src/main/java/org/elasticsearch/script/expression/ExpressionScriptEngineService.java +++ b/src/main/java/org/elasticsearch/script/expression/ExpressionScriptEngineService.java @@ -33,6 +33,7 @@ import org.elasticsearch.index.mapper.FieldMapper; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.core.NumberFieldMapper; +import org.elasticsearch.script.CompiledScript; import org.elasticsearch.script.ExecutableScript; import org.elasticsearch.script.ScriptEngineService; import org.elasticsearch.script.SearchScript; @@ -154,4 +155,9 @@ public Object unwrap(Object value) { @Override public void close() {} + + @Override + public void scriptRemoved(CompiledScript script) { + // Nothing to do + } } diff --git a/src/main/java/org/elasticsearch/script/groovy/GroovyScriptEngineService.java b/src/main/java/org/elasticsearch/script/groovy/GroovyScriptEngineService.java index 299d90e71a9c6..bbaf772dd85ce 100644 --- a/src/main/java/org/elasticsearch/script/groovy/GroovyScriptEngineService.java +++ b/src/main/java/org/elasticsearch/script/groovy/GroovyScriptEngineService.java @@ -43,7 +43,6 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.script.*; import org.elasticsearch.search.lookup.SearchLookup; -import org.elasticsearch.search.suggest.term.TermSuggestion; import java.io.IOException; import java.math.BigDecimal; @@ -89,6 +88,16 @@ public void close() { } } + @Override + public void scriptRemoved(@Nullable CompiledScript script) { + // script could be null, meaning the script has already been garbage collected + if (script == null || "groovy".equals(script.lang())) { + // Clear the cache, this removes old script versions from the + // cache to prevent running out of PermGen space + loader.clearCache(); + } + } + @Override public String[] types() { return new String[]{"groovy"}; @@ -313,4 +322,4 @@ public Expression transform(Expression expr) { } } -} \ No newline at end of file +} diff --git a/src/main/java/org/elasticsearch/script/mustache/MustacheScriptEngineService.java b/src/main/java/org/elasticsearch/script/mustache/MustacheScriptEngineService.java index 3c795530e1a21..e03e07ed500b6 100644 --- a/src/main/java/org/elasticsearch/script/mustache/MustacheScriptEngineService.java +++ b/src/main/java/org/elasticsearch/script/mustache/MustacheScriptEngineService.java @@ -26,6 +26,7 @@ import org.elasticsearch.common.io.UTF8StreamWriter; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.script.CompiledScript; import org.elasticsearch.script.ExecutableScript; import org.elasticsearch.script.ScriptEngineService; import org.elasticsearch.script.SearchScript; @@ -148,6 +149,11 @@ public void close() { // Nothing to do here } + @Override + public void scriptRemoved(CompiledScript script) { + // Nothing to do here + } + /** * Used at query execution time by script service in order to execute a query template. * */ diff --git a/src/test/java/org/elasticsearch/script/ScriptServiceTests.java b/src/test/java/org/elasticsearch/script/ScriptServiceTests.java index 6dee370e416ba..091d21f50e3f4 100644 --- a/src/test/java/org/elasticsearch/script/ScriptServiceTests.java +++ b/src/test/java/org/elasticsearch/script/ScriptServiceTests.java @@ -132,6 +132,11 @@ public Object unwrap(Object value) { public void close() { } + + @Override + public void scriptRemoved(CompiledScript script) { + // Nothing to do here + } } }