From fff96e18b653c0e9ffd38a2982e500f7fbefca7d Mon Sep 17 00:00:00 2001 From: lvca Date: Wed, 25 Oct 2023 13:27:06 -0400 Subject: [PATCH] feat: HTTP, added new global configuration to specify the number of IO thread for the HTTP server (arcadedb.server.httpsIoThreads) --- .../com/arcadedb/GlobalConfiguration.java | 14 ++- .../com/arcadedb/server/http/HttpServer.java | 92 ++++++++----------- .../arcadedb/server/StaticBaseServerTest.java | 7 +- 3 files changed, 54 insertions(+), 59 deletions(-) diff --git a/engine/src/main/java/com/arcadedb/GlobalConfiguration.java b/engine/src/main/java/com/arcadedb/GlobalConfiguration.java index 570501a504..67d1100207 100644 --- a/engine/src/main/java/com/arcadedb/GlobalConfiguration.java +++ b/engine/src/main/java/com/arcadedb/GlobalConfiguration.java @@ -78,13 +78,14 @@ public void run() { String.class, "default", new Callable<>() { @Override public Object call(final Object value) { + final int cores = Runtime.getRuntime().availableProcessors(); + final String v = value.toString(); if (v.equalsIgnoreCase("default")) { // NOT MUCH TO DO HERE, THIS IS THE DEFAULT OPTION } else if (v.equalsIgnoreCase("high-performance")) { ASYNC_OPERATIONS_QUEUE_IMPL.setValue("fast"); - final int cores = Runtime.getRuntime().availableProcessors(); if (cores > 1) // USE ONLY HALF OF THE CORES MINUS ONE ASYNC_WORKER_THREADS.setValue((cores / 2) - 1); @@ -102,10 +103,12 @@ public Object call(final Object value) { SQL_STATEMENT_CACHE.setValue(16); HA_REPLICATION_QUEUE_SIZE.setValue(8); ASYNC_OPERATIONS_QUEUE_IMPL.setValue("standard"); + SERVER_HTTP_IO_THREADS.setValue(cores > 8 ? 4 : 2); } else if (v.equalsIgnoreCase("low-cpu")) { ASYNC_WORKER_THREADS.setValue(1); ASYNC_OPERATIONS_QUEUE_IMPL.setValue("standard"); + SERVER_HTTP_IO_THREADS.setValue(cores > 8 ? 4 : 2); } else throw new IllegalArgumentException("Profile '" + v + "' not available"); @@ -324,6 +327,10 @@ public Object call(final Object value) { "TCP/IP port number used for incoming HTTPS connections. Specify a single port or a range ``. Default is 2490-2499 to accept a range of ports in case they are occupied.", String.class, "2490-2499"), + SERVER_HTTP_IO_THREADS("arcadedb.server.httpsIoThreads", SCOPE.SERVER, + "Number of threads to use in the HTTP servers. The default number for most of the use cases is 2 threads per cpus (or 1 per virtual core)", + Integer.class, 0, null, (value) -> Runtime.getRuntime().availableProcessors()), + SERVER_HTTP_TX_EXPIRE_TIMEOUT("arcadedb.server.httpTxExpireTimeout", SCOPE.SERVER, "Timeout in seconds for a HTTP transaction to expire. This timeout is computed from the latest command against the transaction", Long.class, 30), @@ -485,7 +492,10 @@ public static void resetAll() { * Reset the configuration to the default value. */ public void reset() { - value = defValue; + if (callbackIfNoSet != null) + value = callbackIfNoSet.call(null); + else + value = defValue; } public static void dumpConfiguration(final PrintStream out) { diff --git a/server/src/main/java/com/arcadedb/server/http/HttpServer.java b/server/src/main/java/com/arcadedb/server/http/HttpServer.java index a0d5322ed4..b96e2a46aa 100644 --- a/server/src/main/java/com/arcadedb/server/http/HttpServer.java +++ b/server/src/main/java/com/arcadedb/server/http/HttpServer.java @@ -69,18 +69,19 @@ import static io.undertow.UndertowOptions.SHUTDOWN_TIMEOUT; public class HttpServer implements ServerPlugin { - private final ArcadeDBServer server; + private final ArcadeDBServer server; private final HttpSessionManager sessionManager; - private final JsonSerializer jsonSerializer = new JsonSerializer(); - private final WebSocketEventBus webSocketEventBus; - private Undertow undertow; - private String listeningAddress; - private String host; - private int httpPortListening; + private final JsonSerializer jsonSerializer = new JsonSerializer(); + private final WebSocketEventBus webSocketEventBus; + private Undertow undertow; + private String listeningAddress; + private String host; + private int httpPortListening; public HttpServer(final ArcadeDBServer server) { this.server = server; - this.sessionManager = new HttpSessionManager(server.getConfiguration().getValueAsInteger(GlobalConfiguration.SERVER_HTTP_TX_EXPIRE_TIMEOUT) * 1000L); + this.sessionManager = new HttpSessionManager( + server.getConfiguration().getValueAsInteger(GlobalConfiguration.SERVER_HTTP_TX_EXPIRE_TIMEOUT) * 1000L); this.webSocketEventBus = new WebSocketEventBus(this.server); } @@ -109,10 +110,12 @@ public void startService() { final int[] httpPortRange = extractPortRange(configuredHTTPPort); final Object configuredHTTPSPort = configuration.getValue(GlobalConfiguration.SERVER_HTTPS_INCOMING_PORT); - final int[] httpsPortRange = configuredHTTPSPort != null && !configuredHTTPSPort.toString().isEmpty() ? extractPortRange(configuredHTTPSPort) : null; + final int[] httpsPortRange = + configuredHTTPSPort != null && !configuredHTTPSPort.toString().isEmpty() ? extractPortRange(configuredHTTPSPort) : null; - LogManager.instance().log(this, Level.INFO, "- Starting HTTP Server (host=%s port=%s httpsPort=%s)...", host, configuredHTTPPort, - httpsPortRange != null ? configuredHTTPSPort : "-"); + LogManager.instance() + .log(this, Level.INFO, "- Starting HTTP Server (host=%s port=%s httpsPort=%s)...", host, configuredHTTPPort, + httpsPortRange != null ? configuredHTTPSPort : "-"); final PathHandler routes = new PathHandler(); @@ -151,7 +154,8 @@ public void startService() { .addHttpListener(httpPortListening, host)// .setHandler(routes)// .setSocketOption(Options.READ_TIMEOUT, configuration.getValueAsInteger(GlobalConfiguration.NETWORK_SOCKET_TIMEOUT)) - .setWorkerThreads( 500 ) + .setIoThreads(configuration.getValueAsInteger(GlobalConfiguration.SERVER_HTTP_IO_THREADS))// + .setWorkerThreads(500)// .setServerOption(SHUTDOWN_TIMEOUT, 5000); if (configuration.getValueAsBoolean(GlobalConfiguration.NETWORK_USE_SSL)) { @@ -185,7 +189,8 @@ public void startService() { } httpPortListening = -1; - final String msg = String.format("Unable to listen to a HTTP port in the configured port range %d - %d", httpPortRange[0], httpPortRange[1]); + final String msg = String.format("Unable to listen to a HTTP port in the configured port range %d - %d", httpPortRange[0], + httpPortRange[1]); LogManager.instance(). log(this, Level.SEVERE, msg); @@ -212,7 +217,7 @@ private int[] extractPortRange(final Object configuredPort) { } } - return new int[]{portFrom, portTo}; + return new int[] { portFrom, portTo }; } public HttpSessionManager getSessionManager() { @@ -242,33 +247,20 @@ public WebSocketEventBus getWebSocketEventBus() { private SSLContext createSSLContext() throws Exception { ContextConfiguration configuration = server.getConfiguration(); - String keystorePath = validateStoreProperty(configuration, - NETWORK_SSL_KEYSTORE, - "SSL key store path is empty" - ); - String keystorePassword = validateStoreProperty(configuration, - NETWORK_SSL_KEYSTORE_PASSWORD, - "SSL key store password is empty" - ); + String keystorePath = validateStoreProperty(configuration, NETWORK_SSL_KEYSTORE, "SSL key store path is empty"); + String keystorePassword = validateStoreProperty(configuration, NETWORK_SSL_KEYSTORE_PASSWORD, + "SSL key store password is empty"); - String truststorePath = validateStoreProperty(configuration, - NETWORK_SSL_TRUSTSTORE, - "SSL trust store path is empty" - ); - String truststorePassword = validateStoreProperty(configuration, - NETWORK_SSL_TRUSTSTORE_PASSWORD, - "SSL trust store password is empty" - ); + String truststorePath = validateStoreProperty(configuration, NETWORK_SSL_TRUSTSTORE, "SSL trust store path is empty"); + String truststorePassword = validateStoreProperty(configuration, NETWORK_SSL_TRUSTSTORE_PASSWORD, + "SSL trust store password is empty"); - KeyStore keyStore = configureSSLForKeystore(keystorePath, - keystorePassword); + KeyStore keyStore = configureSSLForKeystore(keystorePath, keystorePassword); - KeyStore trustStore = configureSSLForTruststore(truststorePath, - truststorePassword); + KeyStore trustStore = configureSSLForTruststore(truststorePath, truststorePassword); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStore, - keystorePassword.toCharArray()); + keyManagerFactory.init(keyStore, keystorePassword.toCharArray()); KeyManager[] keyManagers = keyManagerFactory.getKeyManagers(); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); @@ -276,37 +268,27 @@ private SSLContext createSSLContext() throws Exception { TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); SSLContext sslContext = SSLContext.getInstance(TlsProtocol.getLatestTlsVersion().getTlsVersion()); - sslContext.init(keyManagers, - trustManagers, - null - ); + sslContext.init(keyManagers, trustManagers, null); return sslContext; } - private KeyStore configureSSLForKeystore(String keystorePath, - String keystorePassword) + private KeyStore configureSSLForKeystore(String keystorePath, String keystorePassword) throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException { - return SslUtils.loadKeystoreFromStream(SocketFactory.getAsStream(keystorePath), - keystorePassword, - SslUtils.getDefaultKeystoreTypeForKeystore(() -> PKCS12) - ); + return SslUtils.loadKeystoreFromStream(SocketFactory.getAsStream(keystorePath), keystorePassword, + SslUtils.getDefaultKeystoreTypeForKeystore(() -> PKCS12)); } - private KeyStore configureSSLForTruststore(String truststorePath, - String truststorePassword) + private KeyStore configureSSLForTruststore(String truststorePath, String truststorePassword) throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException { - return SslUtils.loadKeystoreFromStream(SocketFactory.getAsStream(truststorePath), - truststorePassword, - SslUtils.getDefaultKeystoreTypeForTruststore(() -> JKS) - ); + return SslUtils.loadKeystoreFromStream(SocketFactory.getAsStream(truststorePath), truststorePassword, + SslUtils.getDefaultKeystoreTypeForTruststore(() -> JKS)); } - private String validateStoreProperty(ContextConfiguration contextConfiguration, - GlobalConfiguration configurationKey, - String errorMessage) { + private String validateStoreProperty(ContextConfiguration contextConfiguration, GlobalConfiguration configurationKey, + String errorMessage) { String storePropertyValue = contextConfiguration.getValueAsString(configurationKey); if ((storePropertyValue == null) || storePropertyValue.isEmpty()) { throw new ServerSecurityException(errorMessage); diff --git a/server/src/test/java/com/arcadedb/server/StaticBaseServerTest.java b/server/src/test/java/com/arcadedb/server/StaticBaseServerTest.java index 044cf896c7..ea6572d682 100644 --- a/server/src/test/java/com/arcadedb/server/StaticBaseServerTest.java +++ b/server/src/test/java/com/arcadedb/server/StaticBaseServerTest.java @@ -39,6 +39,7 @@ public void setTestConfiguration() { GlobalConfiguration.TEST.setValue(true); GlobalConfiguration.SERVER_ROOT_PATH.setValue("./target"); GlobalConfiguration.SERVER_ROOT_PASSWORD.setValue(DEFAULT_PASSWORD_FOR_TESTS); + GlobalConfiguration.SERVER_HTTP_IO_THREADS.setValue(2); } @BeforeEach @@ -58,8 +59,10 @@ public void endTest() { } protected static void testLog(final String msg, final Object... args) { - LogManager.instance().log(StaticBaseServerTest.class, Level.FINE, "***********************************************************************************"); + LogManager.instance().log(StaticBaseServerTest.class, Level.FINE, + "***********************************************************************************"); LogManager.instance().log(StaticBaseServerTest.class, Level.FINE, "TEST: " + msg, args); - LogManager.instance().log(StaticBaseServerTest.class, Level.FINE, "***********************************************************************************"); + LogManager.instance().log(StaticBaseServerTest.class, Level.FINE, + "***********************************************************************************"); } }