[FEATURE] Support virtual threads and client locks

This commit is contained in:
Gravita 2024-02-03 17:08:07 +07:00
parent 34ac6a0f28
commit 7060697bad
21 changed files with 222 additions and 144 deletions

View file

@ -422,21 +422,6 @@ public void syncUpdatesDir(Collection<String> dirs) throws IOException {
updatesManager.syncUpdatesDir(dirs);
}
public void restart() {
ProcessBuilder builder = new ProcessBuilder();
if (config.startScript != null) builder.command(Collections.singletonList(config.startScript));
else throw new IllegalArgumentException("Please create start script and link it as startScript in config.");
builder.directory(this.dir.toFile());
builder.inheritIO();
builder.redirectErrorStream(true);
builder.redirectOutput(Redirect.PIPE);
try {
builder.start();
} catch (IOException e) {
logger.error("Restart failed", e);
}
}
public void registerObject(String name, Object object) {
if (object instanceof Reconfigurable) {
reconfigurableManager.registerReconfigurable(name, (Reconfigurable) object);
@ -449,11 +434,6 @@ public void unregisterObject(String name, Object object) {
}
}
public void fullyRestart() {
restart();
JVMHelper.RUNTIME.exit(0);
}
public enum ReloadType {
NO_AUTH,

View file

@ -63,27 +63,7 @@ public LaunchServerBuilder setLaunchServerConfigManager(LaunchServer.LaunchServe
public LaunchServer build() throws Exception {
directories.collect();
if (launchServerConfigManager == null) {
launchServerConfigManager = new LaunchServer.LaunchServerConfigManager() {
@Override
public LaunchServerConfig readConfig() {
throw new UnsupportedOperationException();
}
@Override
public LaunchServerRuntimeConfig readRuntimeConfig() {
throw new UnsupportedOperationException();
}
@Override
public void writeConfig(LaunchServerConfig config) {
throw new UnsupportedOperationException();
}
@Override
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) {
throw new UnsupportedOperationException();
}
};
launchServerConfigManager = new NullLaunchServerConfigManager();
}
if (keyAgreementManager == null) {
keyAgreementManager = new KeyAgreementManager(directories.keyDirectory);
@ -99,4 +79,26 @@ public LaunchServerBuilder setCertificateManager(CertificateManager certificateM
public void setKeyAgreementManager(KeyAgreementManager keyAgreementManager) {
this.keyAgreementManager = keyAgreementManager;
}
private static class NullLaunchServerConfigManager implements LaunchServer.LaunchServerConfigManager {
@Override
public LaunchServerConfig readConfig() {
throw new UnsupportedOperationException();
}
@Override
public LaunchServerRuntimeConfig readRuntimeConfig() {
throw new UnsupportedOperationException();
}
@Override
public void writeConfig(LaunchServerConfig config) {
throw new UnsupportedOperationException();
}
@Override
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) {
throw new UnsupportedOperationException();
}
}
}

View file

@ -122,57 +122,7 @@ public static void main(String[] args) throws Exception {
}
}
LaunchServer.LaunchServerConfigManager launchServerConfigManager = new LaunchServer.LaunchServerConfigManager() {
@Override
public LaunchServerConfig readConfig() throws IOException {
LaunchServerConfig config1;
try (BufferedReader reader = IOHelper.newReader(configFile)) {
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerConfig.class);
}
return config1;
}
@Override
public LaunchServerRuntimeConfig readRuntimeConfig() throws IOException {
LaunchServerRuntimeConfig config1;
try (BufferedReader reader = IOHelper.newReader(runtimeConfigFile)) {
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerRuntimeConfig.class);
}
return config1;
}
@Override
public void writeConfig(LaunchServerConfig config) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (Writer writer = IOHelper.newWriter(output)) {
if (Launcher.gsonManager.configGson != null) {
Launcher.gsonManager.configGson.toJson(config, writer);
} else {
logger.error("Error writing LaunchServer config file. Gson is null");
}
}
byte[] bytes = output.toByteArray();
if(bytes.length > 0) {
IOHelper.write(configFile, bytes);
}
}
@Override
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (Writer writer = IOHelper.newWriter(output)) {
if (Launcher.gsonManager.configGson != null) {
Launcher.gsonManager.configGson.toJson(config, writer);
} else {
logger.error("Error writing LaunchServer runtime config file. Gson is null");
}
}
byte[] bytes = output.toByteArray();
if(bytes.length > 0) {
IOHelper.write(runtimeConfigFile, bytes);
}
}
};
LaunchServer.LaunchServerConfigManager launchServerConfigManager = new BasicLaunchServerConfigManager(configFile, runtimeConfigFile);
LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories();
directories.dir = dir;
LaunchServer server = new LaunchServerBuilder()
@ -284,4 +234,64 @@ public static void generateConfigIfNotExists(Path configFile, CommandHandler com
Launcher.gsonManager.configGson.toJson(newConfig, writer);
}
}
private static class BasicLaunchServerConfigManager implements LaunchServer.LaunchServerConfigManager {
private final Path configFile;
private final Path runtimeConfigFile;
public BasicLaunchServerConfigManager(Path configFile, Path runtimeConfigFile) {
this.configFile = configFile;
this.runtimeConfigFile = runtimeConfigFile;
}
@Override
public LaunchServerConfig readConfig() throws IOException {
LaunchServerConfig config1;
try (BufferedReader reader = IOHelper.newReader(configFile)) {
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerConfig.class);
}
return config1;
}
@Override
public LaunchServerRuntimeConfig readRuntimeConfig() throws IOException {
LaunchServerRuntimeConfig config1;
try (BufferedReader reader = IOHelper.newReader(runtimeConfigFile)) {
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerRuntimeConfig.class);
}
return config1;
}
@Override
public void writeConfig(LaunchServerConfig config) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (Writer writer = IOHelper.newWriter(output)) {
if (Launcher.gsonManager.configGson != null) {
Launcher.gsonManager.configGson.toJson(config, writer);
} else {
logger.error("Error writing LaunchServer config file. Gson is null");
}
}
byte[] bytes = output.toByteArray();
if(bytes.length > 0) {
IOHelper.write(configFile, bytes);
}
}
@Override
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (Writer writer = IOHelper.newWriter(output)) {
if (Launcher.gsonManager.configGson != null) {
Launcher.gsonManager.configGson.toJson(config, writer);
} else {
logger.error("Error writing LaunchServer runtime config file. Gson is null");
}
}
byte[] bytes = output.toByteArray();
if(bytes.length > 0) {
IOHelper.write(runtimeConfigFile, bytes);
}
}
}
}

View file

@ -47,6 +47,9 @@ protected boolean showApplyDialog(String text) throws IOException {
protected Downloader downloadWithProgressBar(String taskName, List<Downloader.SizedFile> list, String baseUrl, Path targetDir) throws Exception {
long total = 0;
for (Downloader.SizedFile file : list) {
if(file.size < 0) {
continue;
}
total += file.size;
}
long totalFiles = list.size();

View file

@ -1,25 +0,0 @@
package pro.gravit.launchserver.command.basic;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
public final class RestartCommand extends Command {
public RestartCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return null;
}
@Override
public String getUsageDescription() {
return "Restart LaunchServer";
}
@Override
public void invoke(String... args) {
server.fullyRestart();
}
}

View file

@ -6,6 +6,9 @@
import pro.gravit.launchserver.command.modules.LoadModuleCommand;
import pro.gravit.launchserver.command.modules.ModulesCommand;
import pro.gravit.launchserver.command.service.*;
import pro.gravit.launchserver.command.sync.*;
import pro.gravit.launchserver.command.tools.SignDirCommand;
import pro.gravit.launchserver.command.tools.SignJarCommand;
import pro.gravit.utils.command.BaseCommandCategory;
import pro.gravit.utils.command.basic.ClearCommand;
import pro.gravit.utils.command.basic.GCCommand;
@ -19,7 +22,6 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand
basic.registerCommand("version", new VersionCommand(server));
basic.registerCommand("build", new BuildCommand(server));
basic.registerCommand("stop", new StopCommand(server));
basic.registerCommand("restart", new RestartCommand(server));
basic.registerCommand("debug", new DebugCommand(server));
basic.registerCommand("clear", new ClearCommand(handler));
basic.registerCommand("gc", new GCCommand());
@ -34,10 +36,7 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand
updates.registerCommand("unindexAsset", new UnindexAssetCommand(server));
updates.registerCommand("downloadAsset", new DownloadAssetCommand(server));
updates.registerCommand("downloadClient", new DownloadClientCommand(server));
updates.registerCommand("syncBinaries", new SyncBinariesCommand(server));
updates.registerCommand("syncUpdates", new SyncUpdatesCommand(server));
updates.registerCommand("syncProfiles", new SyncProfilesCommand(server));
updates.registerCommand("syncUP", new SyncUPCommand(server));
updates.registerCommand("sync", new SyncCommand(server));
updates.registerCommand("saveProfiles", new SaveProfilesCommand(server));
updates.registerCommand("makeProfile", new MakeProfileCommand(server));
Category updatesCategory = new Category(updates, "updates", "Update and Sync Management");
@ -50,11 +49,16 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand
service.registerCommand("notify", new NotifyCommand(server));
service.registerCommand("component", new ComponentCommand(server));
service.registerCommand("clients", new ClientsCommand(server));
service.registerCommand("signJar", new SignJarCommand(server));
service.registerCommand("signDir", new SignDirCommand(server));
service.registerCommand("securitycheck", new SecurityCheckCommand(server));
service.registerCommand("token", new TokenCommand(server));
Category serviceCategory = new Category(service, "service", "Managing LaunchServer Components");
handler.registerCategory(serviceCategory);
//Register tools commands
BaseCommandCategory tools = new BaseCommandCategory();
tools.registerCommand("signJar", new SignJarCommand(server));
tools.registerCommand("signDir", new SignDirCommand(server));
Category toolsCategory = new Category(tools, "tools", "Other tools");
handler.registerCategory(toolsCategory);
}
}

View file

@ -1,4 +1,4 @@
package pro.gravit.launchserver.command.hash;
package pro.gravit.launchserver.command.sync;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

View file

@ -0,0 +1,30 @@
package pro.gravit.launchserver.command.sync;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
public class SyncCommand extends Command {
public SyncCommand(LaunchServer server) {
super(server);
this.childCommands.put("profiles", new SyncProfilesCommand(server));
this.childCommands.put("binaries", new SyncBinariesCommand(server));
this.childCommands.put("updates", new SyncUpdatesCommand(server));
this.childCommands.put("up", new SyncUPCommand(server));
this.childCommands.put("launchermodules", new SyncLauncherModulesCommand(server));
}
@Override
public String getArgsDescription() {
return "[updates/profiles/up/binaries/launchermodules] [args...]";
}
@Override
public String getUsageDescription() {
return "sync specified objects";
}
@Override
public void invoke(String... args) throws Exception {
invokeSubcommands(args);
}
}

View file

@ -1,31 +1,31 @@
package pro.gravit.launchserver.launchermodules;
package pro.gravit.launchserver.command.sync;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.utils.command.Command;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
public class SyncLauncherModulesCommand extends Command {
private final LauncherModuleLoader mod;
private transient final Logger logger = LogManager.getLogger();
public SyncLauncherModulesCommand(LauncherModuleLoader mod) {
this.mod = mod;
public SyncLauncherModulesCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return "Resync launcher modules";
return null;
}
@Override
public String getUsageDescription() {
return "[]";
return "Resync launcher modules";
}
@Override
public void invoke(String... args) throws Exception {
mod.syncModules();
server.launcherModuleLoader.syncModules();
logger.info("Launcher Modules synced");
}
}

View file

@ -1,4 +1,4 @@
package pro.gravit.launchserver.command.hash;
package pro.gravit.launchserver.command.sync;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

View file

@ -1,4 +1,4 @@
package pro.gravit.launchserver.command.hash;
package pro.gravit.launchserver.command.sync;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

View file

@ -1,4 +1,4 @@
package pro.gravit.launchserver.command.hash;
package pro.gravit.launchserver.command.sync;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

View file

@ -1,4 +1,4 @@
package pro.gravit.launchserver.command.service;
package pro.gravit.launchserver.command.tools;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

View file

@ -1,4 +1,4 @@
package pro.gravit.launchserver.command.service;
package pro.gravit.launchserver.command.tools;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

View file

@ -41,7 +41,6 @@ public final class LaunchServerConfig {
public NettyConfig netty;
public LauncherConf launcher;
public JarSignerConf sign;
public String startScript;
private transient LaunchServer server = null;
private transient AuthProviderPair authDefault;
@ -49,7 +48,6 @@ public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) {
LaunchServerConfig newConfig = new LaunchServerConfig();
newConfig.mirrors = new String[]{"https://mirror.gravitlauncher.com/5.6.x/", "https://gravit-launcher-mirror.storage.googleapis.com/"};
newConfig.env = LauncherConfig.LauncherEnvironment.STD;
newConfig.startScript = JVMHelper.OS_TYPE.equals(JVMHelper.OS.MUSTDIE) ? "." + File.separator + "start.bat" : "." + File.separator + "start.sh";
newConfig.auth = new HashMap<>();
AuthProviderPair a = new AuthProviderPair(new RejectAuthCoreProvider(),
new RequestTextureProvider("http://example.com/skins/%username%.png", "http://example.com/cloaks/%username%.png")
@ -263,6 +261,12 @@ public static class NettyPerformanceConfig {
public int workerThread;
public int schedulerThread;
public int maxWebSocketRequestBytes = 1024 * 1024;
public boolean disableThreadSafeClientObject;
public NettyExecutorType executorType = NettyExecutorType.VIRTUAL_THREADS;
public enum NettyExecutorType {
NONE, DEFAULT, WORK_STEAL, VIRTUAL_THREADS
}
}
public static class NettyBindAddress {

View file

@ -44,7 +44,6 @@ public void init() {
logger.error(e);
}
}
server.commandHandler.registerCommand("syncLauncherModules", new SyncLauncherModulesCommand(this));
MainBuildTask mainTask = server.launcherBinary.getTaskByClass(MainBuildTask.class).get();
mainTask.preBuildHook.registerHook((buildContext) -> {
for (ModuleEntity e : launcherModules) {

View file

@ -10,8 +10,11 @@
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Client {
ReadWriteLock lock = new ReentrantReadWriteLock();
public String auth_id;
public long timestamp;
public AuthResponse.ConnectTypes type;

View file

@ -39,6 +39,8 @@
import java.lang.reflect.Type;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.BiConsumer;
public class WebSocketService {
@ -51,11 +53,18 @@ public class WebSocketService {
private final LaunchServer server;
private final Gson gson;
private transient final Logger logger = LogManager.getLogger();
private ExecutorService executors;
public WebSocketService(ChannelGroup channels, LaunchServer server) {
this.channels = channels;
this.server = server;
this.gson = Launcher.gsonManager.gson;
executors = switch (server.config.netty.performance.executorType) {
case NONE -> null;
case DEFAULT -> Executors.newCachedThreadPool();
case WORK_STEAL -> Executors.newWorkStealingPool();
case VIRTUAL_THREADS -> Executors.newVirtualThreadPerTaskExecutor();
};
}
public static void registerResponses() {
@ -126,7 +135,41 @@ public void process(ChannelHandlerContext ctx, TextWebSocketFrame frame, Client
sendObject(ctx.channel(), event, WebSocketEvent.class);
return;
}
process(context, response, client, ip);
var safeStatus = server.config.netty.performance.disableThreadSafeClientObject ?
WebSocketServerResponse.ThreadSafeStatus.NONE : response.getThreadSafeStatus();
if(executors == null) {
process(safeStatus, client, ip, context, response);
} else {
executors.submit(() -> {
process(safeStatus, client, ip, context, response);
});
}
}
private void process(WebSocketServerResponse.ThreadSafeStatus safeStatus, Client client, String ip, WebSocketRequestContext context, WebSocketServerResponse response) {
switch (safeStatus) {
case NONE -> {
process(context, response, client, ip);
}
case READ -> {
var lock = client.lock.readLock();
lock.lock();
try {
process(context, response, client, ip);
} finally {
lock.unlock();
}
}
case READ_WRITE -> {
var lock = client.lock.writeLock();
lock.lock();
try {
process(context, response, client, ip);
} finally {
lock.unlock();
}
}
}
}
void process(WebSocketRequestContext context, WebSocketServerResponse response, Client client, String ip) {

View file

@ -8,4 +8,12 @@ public interface WebSocketServerResponse extends WebSocketRequest {
String getType();
void execute(ChannelHandlerContext ctx, Client client) throws Exception;
default ThreadSafeStatus getThreadSafeStatus() {
return ThreadSafeStatus.READ;
}
enum ThreadSafeStatus {
NONE, READ, READ_WRITE
}
}

View file

@ -1,4 +1,4 @@
{
"features": ["nojava8support"],
"info": ["Java below 17 not supported"]
"features": [],
"info": []
}

View file

@ -21,6 +21,7 @@
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
@ -159,6 +160,16 @@ public CompletableFuture<Void> downloadFile(URI uri, Path path) {
});
}
public CompletableFuture<Void> downloadFile(String url, Path path, DownloadCallback callback, ExecutorService executor) throws Exception {
return downloadFiles(new ArrayList<>(List.of(new SizedFile(url, path.getFileName().toString()))), null,
path.getParent(), callback, executor, 1);
}
public CompletableFuture<Void> downloadFile(String url, Path path, long size, DownloadCallback callback, ExecutorService executor) throws Exception {
return downloadFiles(new ArrayList<>(List.of(new SizedFile(url, path.getFileName().toString(), size))), null,
path.getParent(), callback, executor, 1);
}
public CompletableFuture<Void> downloadFiles(List<SizedFile> files, String baseURL, Path targetDir, DownloadCallback callback, ExecutorService executor, int threads) throws Exception {
// URI scheme
URI baseUri = baseURL == null ? null : new URI(baseURL);
@ -355,5 +366,11 @@ public SizedFile(String urlPath, String filePath, long size) {
this.filePath = filePath;
this.size = size;
}
public SizedFile(String urlPath, String filePath) {
this.urlPath = urlPath;
this.filePath = filePath;
this.size = -1;
}
}
}