/*
* SPDX-FileCopyrightText: 2023 The Refinery Authors
*
* SPDX-License-Identifier: EPL-2.0
*/
package tools.refinery.language.web.xtext.server;
import com.google.inject.Singleton;
import org.eclipse.xtext.ide.ExecutorServiceProvider;
import org.eclipse.xtext.web.server.model.XtextWebDocumentAccess;
import org.jetbrains.annotations.NotNull;
import tools.refinery.language.web.semantics.SemanticsService;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
@Singleton
public class ThreadPoolExecutorServiceProvider extends ExecutorServiceProvider {
private static final String DOCUMENT_LOCK_EXECUTOR;
private static final AtomicInteger POOL_ID = new AtomicInteger(1);
private final Map scheduledInstanceCache =
Collections.synchronizedMap(new HashMap<>());
private final int executorThreadCount;
private final int lockExecutorThreadCount;
private final int semanticsExecutorThreadCount;
static {
var lookup = MethodHandles.lookup();
MethodHandle getter;
try {
var privateLookup = MethodHandles.privateLookupIn(XtextWebDocumentAccess.class, lookup);
getter = privateLookup.findStaticGetter(XtextWebDocumentAccess.class, "DOCUMENT_LOCK_EXECUTOR",
String.class);
} catch (IllegalAccessException | NoSuchFieldException e) {
throw new IllegalStateException("Failed to find getter", e);
}
try {
DOCUMENT_LOCK_EXECUTOR = (String) getter.invokeExact();
} catch (Error e) {
// Rethrow JVM errors.
throw e;
} catch (Throwable e) {
throw new IllegalStateException("Failed to get DOCUMENT_LOCK_EXECUTOR", e);
}
}
public ThreadPoolExecutorServiceProvider() {
executorThreadCount = getCount("REFINERY_XTEXT_THREAD_COUNT").orElse(0);
lockExecutorThreadCount = getCount("REFINERY_XTEXT_LOCKING_THREAD_COUNT").orElse(executorThreadCount);
semanticsExecutorThreadCount = getCount("REFINERY_XTEXT_SEMANTICS_THREAD_COUNT").orElse(executorThreadCount);
}
private static Optional getCount(String name) {
return Optional.ofNullable(System.getenv(name)).map(Integer::parseUnsignedInt);
}
public ScheduledExecutorService getScheduled(String key) {
return scheduledInstanceCache.computeIfAbsent(key, this::createScheduledInstance);
}
@Override
protected ExecutorService createInstance(String key) {
String name = "xtext-" + POOL_ID.getAndIncrement();
if (key != null) {
name = name + "-" + key;
}
var threadFactory = new Factory(name, 5);
int size = getSize(key);
if (size == 0) {
return Executors.newCachedThreadPool(threadFactory);
}
return Executors.newFixedThreadPool(size, threadFactory);
}
protected ScheduledExecutorService createScheduledInstance(String key) {
String name = "xtext-scheduled-" + POOL_ID.getAndIncrement();
if (key != null) {
name = name + "-" + key;
}
var threadFactory = new Factory(name, 5);
return Executors.newScheduledThreadPool(1, threadFactory);
}
private int getSize(String key) {
if (SemanticsService.SEMANTICS_EXECUTOR.equals(key)) {
return semanticsExecutorThreadCount;
} else if (DOCUMENT_LOCK_EXECUTOR.equals(key)) {
return lockExecutorThreadCount;
} else {
return executorThreadCount;
}
}
@Override
public void dispose() {
super.dispose();
synchronized (scheduledInstanceCache) {
for (var instance : scheduledInstanceCache.values()) {
instance.shutdown();
}
scheduledInstanceCache.clear();
}
}
private static class Factory implements ThreadFactory {
// We have to explicitly store the {@link ThreadGroup} to create a {@link ThreadFactory}.
@SuppressWarnings("squid:S3014")
private final ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
private final AtomicInteger threadId = new AtomicInteger(1);
private final String namePrefix;
private final int priority;
public Factory(String name, int priority) {
namePrefix = name + "-thread-";
this.priority = priority;
}
@Override
public Thread newThread(@NotNull Runnable runnable) {
var thread = new Thread(threadGroup, runnable, namePrefix + threadId.getAndIncrement());
if (thread.isDaemon()) {
thread.setDaemon(false);
}
if (thread.getPriority() != priority) {
thread.setPriority(priority);
}
return thread;
}
}
}