
io.cloudslang.runtime.impl.python.PythonExecutionPooledAndCachedEngine Maven / Gradle / Ivy
The newest version!
/*
* Copyright © 2014-2017 EntIT Software LLC, a Micro Focus company (L.P.)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudslang.runtime.impl.python;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import io.cloudslang.dependency.api.services.DependencyService;
import io.cloudslang.runtime.api.python.PythonEvaluationResult;
import io.cloudslang.runtime.api.python.PythonExecutionResult;
import io.cloudslang.runtime.impl.ExecutionEngine;
import io.cloudslang.runtime.impl.python.pool.ViburEmbeddedPythonPoolService;
import io.cloudslang.runtime.impl.python.pool.ViburEmbeddedPythonPoolServiceImpl;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.vibur.objectpool.util.ConcurrentLinkedQueueCollection;
import java.io.Serializable;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import static io.cloudslang.runtime.api.python.enums.PythonStrategy.JYTHON;
import static io.cloudslang.runtime.api.python.enums.PythonStrategy.PYTHON_EXECUTOR;
import static io.cloudslang.runtime.api.python.enums.PythonStrategy.getPythonStrategy;
import static java.lang.Integer.getInteger;
import static java.lang.Math.max;
import static java.lang.Runtime.getRuntime;
import static java.util.concurrent.Executors.newFixedThreadPool;
/**
* Uses io.cloudslang.runtime.impl.python.EmbeddedPythonExecutorWrapper that brings more security
* than io.cloudslang.runtime.impl.python.PythonExecutor
* For python that has no dependencies, it uses a pool of EmbeddedPythonExecutorWrapper instances,
* that are totally isolated not using ThreadLocal at all.
* For python with dependencies, it uses a dedicated cache of EmbeddedPythonExecutorWrapper.
*/
public class PythonExecutionPooledAndCachedEngine extends ExecutionEngine implements PythonExecutionEngine {
@Autowired
private DependencyService dependencyService;
@Autowired
@Qualifier("numberOfExecutionThreads")
private Integer numberOfThreads;
private ViburEmbeddedPythonPoolService pythonExecutorPool;
private Cache cachedExecutors;
@PostConstruct
public void init() {
this.cachedExecutors = Caffeine.newBuilder()
.maximumSize(getInteger("jython.executor.cacheSize", numberOfThreads * 4 / 3))
.build();
doSetPythonExecutorPool();
}
private void doSetPythonExecutorPool() {
final boolean useExternalPython = getPythonStrategy(System.getProperty("python.expressionsEval"), PYTHON_EXECUTOR) != JYTHON;
// 25% of number of thread, in case of external python expression evaluation
// 75% of number of threads in case of jython expression evaluation
int defaultPoolSize = useExternalPython ? max(2, numberOfThreads / 4) : max(2, numberOfThreads * 3 / 4);
int maxPoolSize = getInteger("jython.executor.maxPoolSize", defaultPoolSize);
if (maxPoolSize > 100) { // 100 = hard limit for EmbeddedPythonExecutorWrapper pools
maxPoolSize = 100;
}
ExecutorService executorService = newFixedThreadPool(max(2, getRuntime().availableProcessors()));
ConcurrentLinkedQueueCollection collection =
new ConcurrentLinkedQueueCollection<>();
for (int i = 0; i < maxPoolSize; i++) {
executorService.submit(() -> {
try {
collection.offerLast(new EmbeddedPythonExecutorWrapper());
} catch (Exception ignored) {
}
});
}
try {
executorService.shutdown();
//noinspection ResultOfMethodCallIgnored
executorService.awaitTermination(7, TimeUnit.MINUTES);
executorService.shutdownNow();
} catch (Exception ignored) {
}
while (collection.size() < maxPoolSize) {
collection.offerLast(new EmbeddedPythonExecutorWrapper());
}
this.pythonExecutorPool = new ViburEmbeddedPythonPoolServiceImpl(collection, maxPoolSize, maxPoolSize);
}
@Override
public PythonExecutionResult exec(Set dependencies, String script, Map vars) {
Set resultedDependencies = dependencyService.getDependencies(dependencies);
// When dependencies are not set we take an EmbeddedPythonExecutorWrapper from the 'pythonExecutorPool' pool
// When dependencies are set we take an EmbeddedPythonExecutorWrapper from the 'cachedExecutors' cache
if (resultedDependencies.isEmpty()) {
EmbeddedPythonExecutorWrapper pooledPythonExecutor = null;
try {
pooledPythonExecutor = pythonExecutorPool.tryTakeWithTimeout();
return pooledPythonExecutor.exec(script, vars);
} finally {
doRestoreExecutor(pooledPythonExecutor);
}
} else {
String dependenciesKey = generatedDependenciesKey(dependencies);
EmbeddedPythonExecutorWrapper executor = cachedExecutors.getIfPresent(dependenciesKey);
if (executor == null) {
EmbeddedPythonExecutorWrapper newPythonExecutor = new EmbeddedPythonExecutorWrapper(resultedDependencies);
executor = cachedExecutors.get(dependenciesKey, k -> newPythonExecutor);
}
// At this point executor cannot be null, since it was created in in the function k -> newPythonExecutor
// That is the reason we don't want to call an extra Objects.requireNotNull(executor)
return executor.exec(script, vars);
}
}
@Override
public PythonEvaluationResult eval(String prepareEnvironmentScript, String script, Map vars) {
EmbeddedPythonExecutorWrapper embeddedPythonExecutor = null;
try {
embeddedPythonExecutor = pythonExecutorPool.tryTakeWithTimeout();
return embeddedPythonExecutor.eval(prepareEnvironmentScript, script, vars);
} finally {
doRestoreExecutor(embeddedPythonExecutor);
}
}
@Override
public PythonEvaluationResult test(String prepareEnvironmentScript, String script,
Map vars, long timeout) {
EmbeddedPythonExecutorWrapper embeddedPythonExecutor = null;
try {
embeddedPythonExecutor = pythonExecutorPool.tryTakeWithTimeout();
// For Jython test is identical with eval
return embeddedPythonExecutor.eval(prepareEnvironmentScript, script, vars);
} finally {
doRestoreExecutor(embeddedPythonExecutor);
}
}
private void doRestoreExecutor(EmbeddedPythonExecutorWrapper pooledPythonExecutor) {
try {
pythonExecutorPool.restore(pooledPythonExecutor);
} catch (Exception ignored) {
}
}
@PreDestroy
public void closeResources() {
pythonExecutorPool.close();
try {
cachedExecutors.asMap().values().forEach(EmbeddedPythonExecutorWrapper::close);
} catch (Exception ignored) {
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy