Script Mapper Performance Issues

Closes #11005
This commit is contained in:
mposolda 2022-03-30 08:30:30 +02:00 committed by Pedro Igor
parent 834a276767
commit fb81242658
3 changed files with 56 additions and 12 deletions

View file

@ -24,6 +24,7 @@ import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.keycloak.models.ScriptModel;
import org.keycloak.services.ServicesLogger;
/**
* A {@link ScriptingProvider} that uses a {@link ScriptEngineManager} to evaluate scripts with a {@link ScriptEngine}.
@ -32,14 +33,10 @@ import org.keycloak.models.ScriptModel;
*/
public class DefaultScriptingProvider implements ScriptingProvider {
private final ScriptEngineManager scriptEngineManager;
private final DefaultScriptingProviderFactory factory;
DefaultScriptingProvider(ScriptEngineManager scriptEngineManager) {
if (scriptEngineManager == null) {
throw new IllegalStateException("scriptEngineManager must not be null!");
}
this.scriptEngineManager = scriptEngineManager;
DefaultScriptingProvider(DefaultScriptingProviderFactory factory) {
this.factory = factory;
}
/**
@ -69,7 +66,7 @@ public class DefaultScriptingProvider implements ScriptingProvider {
throw new IllegalArgumentException("script must not be null or empty");
}
ScriptEngine engine = createPreparedScriptEngine(scriptModel);
ScriptEngine engine = getPreparedScriptEngine(scriptModel);
if (engine instanceof Compilable) {
return new CompiledEvaluatableScriptAdapter(scriptModel, tryCompile(scriptModel, (Compilable) engine));
@ -99,13 +96,26 @@ public class DefaultScriptingProvider implements ScriptingProvider {
/**
* Looks-up a {@link ScriptEngine} with prepared {@link Bindings} for the given {@link ScriptModel Script}.
*/
private ScriptEngine createPreparedScriptEngine(ScriptModel script) {
private ScriptEngine getPreparedScriptEngine(ScriptModel script) {
// Try to lookup shared engine in the cache first
if (factory.isEnableScriptEngineCache()) {
ScriptEngine scriptEngine = factory.getScriptEngineCache().get(script.getMimeType());
if (scriptEngine != null) return scriptEngine;
}
ScriptEngine scriptEngine = lookupScriptEngineFor(script);
if (scriptEngine == null) {
throw new IllegalStateException("Could not find ScriptEngine for script: " + script);
}
ServicesLogger.LOGGER.scriptEngineCreated(scriptEngine.getFactory().getEngineName(), scriptEngine.getFactory().getEngineVersion(), script.getMimeType());
// Nashorn scriptEngine is ok to cache and share across multiple threads
if (factory.isEnableScriptEngineCache()) {
factory.getScriptEngineCache().put(script.getMimeType(), scriptEngine);
}
return scriptEngine;
}
@ -116,7 +126,7 @@ public class DefaultScriptingProvider implements ScriptingProvider {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(DefaultScriptingProvider.class.getClassLoader());
return scriptEngineManager.getEngineByMimeType(script.getMimeType());
return factory.getScriptEngineManager().getEngineByMimeType(script.getMimeType());
}
finally {
Thread.currentThread().setContextClassLoader(cl);

View file

@ -16,10 +16,16 @@
*/
package org.keycloak.scripting;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.protocol.oidc.OIDCWellKnownProviderFactory;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
/**
@ -27,20 +33,40 @@ import javax.script.ScriptEngineManager;
*/
public class DefaultScriptingProviderFactory implements ScriptingProviderFactory {
private static final Logger logger = Logger.getLogger(DefaultScriptingProviderFactory.class);
static final String ID = "script-based-auth";
private ScriptEngineManager scriptEngineManager;
private boolean enableScriptEngineCache;
// Key is mime-type. Value is engine for the particular mime-type. Cache can be used when the scriptEngine can be shared across multiple threads / requests (which is the case for nashorn)
private Map<String, ScriptEngine> scriptEngineCache;
@Override
public ScriptingProvider create(KeycloakSession session) {
lazyInit();
return new DefaultScriptingProvider(scriptEngineManager);
return new DefaultScriptingProvider(this);
}
@Override
public void init(Config.Scope config) {
//NOOP
this.enableScriptEngineCache = config.getBoolean("enable-script-engine-cache", true);
logger.debugf("Enable script engine cache: %b", this.enableScriptEngineCache);
}
ScriptEngineManager getScriptEngineManager() {
return scriptEngineManager;
}
boolean isEnableScriptEngineCache() {
return enableScriptEngineCache;
}
Map<String, ScriptEngine> getScriptEngineCache() {
return scriptEngineCache;
}
@Override
@ -63,6 +89,9 @@ public class DefaultScriptingProviderFactory implements ScriptingProviderFactory
synchronized (this) {
if (scriptEngineManager == null) {
scriptEngineManager = new ScriptEngineManager();
if (enableScriptEngineCache) {
scriptEngineCache = new ConcurrentHashMap<>();
}
}
}
}

View file

@ -462,4 +462,9 @@ public interface ServicesLogger extends BasicLogger {
@LogMessage(level = ERROR)
@Message(id=105, value="Response_mode 'query.jwt' is allowed only when the authorization response token is encrypted")
void responseModeQueryJwtNotAllowed();
@LogMessage(level = INFO)
@Message(id=106, value="Created script engine '%s', version '%s' for the mime type '%s'")
@Once
void scriptEngineCreated(String engineName, String engineVersion, String mimeType);
}