Skip to content

Commit

Permalink
Merge pull request #4685 from evolvedbinary/hotfix/windows-service-ma…
Browse files Browse the repository at this point in the history
…nager-mem

Windows Service installer - prunsrv.exe expects memory to be supplied as an integer
  • Loading branch information
joewiz authored Jan 13, 2023
2 parents 2a7085c + 2b2a4b9 commit b8e7349
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.apache.logging.log4j.Logger;
import org.exist.util.ConfigurationHelper;

import javax.annotation.Nullable;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
Expand All @@ -38,6 +39,8 @@
import java.nio.file.Paths;
import java.util.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.evolvedbinary.j8fu.Either.Left;
import static com.evolvedbinary.j8fu.Either.Right;
Expand All @@ -49,6 +52,11 @@
@NotThreadSafe
class WindowsServiceManager implements ServiceManager {

/**
* See <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html#BABHDABI">Java - Non-Standard Options</a>.
*/
private static final Pattern JAVA_CMDLINE_MEMORY_STRING = Pattern.compile("([0-9]+)(g|G|m|M|k|K)?.*");

private static final Logger LOG = LogManager.getLogger(WindowsServiceManager.class);
private static final String PROCRUN_SRV_EXE = "prunsrv-x86_64.exe";
private static final String SC_EXE = "sc.exe";
Expand Down Expand Up @@ -86,8 +94,8 @@ public void install() throws ServiceManagerException {
.orElse(existHome.resolve("etc").resolve("conf.xml"));

final Properties launcherProperties = ConfigurationUtility.loadProperties();
final Optional<String> maxMemory = Optional.ofNullable(launcherProperties.getProperty(LAUNCHER_PROPERTY_MAX_MEM)).map(s -> s + "m");
final String minMemory = launcherProperties.getProperty(LAUNCHER_PROPERTY_MIN_MEM, "128") + "m";
final Optional<String> maxMemory = Optional.ofNullable(launcherProperties.getProperty(LAUNCHER_PROPERTY_MAX_MEM)).flatMap(WindowsServiceManager::asJavaCmdlineMemoryString);
final Optional<String> minMemory = asJavaCmdlineMemoryString(launcherProperties.getProperty(LAUNCHER_PROPERTY_MIN_MEM, "128"));

final StringBuilder jvmOptions = new StringBuilder();
jvmOptions.append("-Dfile.encoding=UTF-8");
Expand Down Expand Up @@ -117,7 +125,6 @@ public void install() throws ServiceManagerException {
"--ServiceUser=LocalSystem", // TODO(AR) this changed from `LocalSystem` to `NT Authority\LocalService` in procrun 1.2.0, however our service won't seem to start under that account... we need to investigate!
"--Jvm=" + findJvm().orElse("auto"),
"--Classpath=\"" + existHome.resolve("lib").toAbsolutePath().toString().replace('\\', '/') + "/*\"",
"--JvmMs=" + minMemory,
"--StartMode=jvm",
"--StartClass=org.exist.service.ExistDbDaemon",
"--StartMethod=start",
Expand All @@ -127,7 +134,8 @@ public void install() throws ServiceManagerException {
"--JvmOptions=\"" + jvmOptions + "\"",
"--StartParams=\"" + configFile.toAbsolutePath().toString() + "\""
);
maxMemory.ifPresent(xmx -> args.add("--JvmMx=" + xmx));
minMemory.flatMap(WindowsServiceManager::asPrunSrvMemoryString).ifPresent(xms -> args.add("--JvmMs=" + xms));
maxMemory.flatMap(WindowsServiceManager::asPrunSrvMemoryString).ifPresent(xmx -> args.add("--JvmMx=" + xmx));

try {
final Tuple2<Integer, String> execResult = run(args, true);
Expand Down Expand Up @@ -367,4 +375,75 @@ private Tuple2<Integer, String> run(List<String> args, final boolean elevated) t
final int exitValue = process.waitFor();
return Tuple(exitValue, output.toString());
}

/**
* Transform the supplied memory string into a string
* that is compatible with the Java command line arguments for -Xms and -Xmx.
*
* See <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html#BABHDABI">Java - Non-Standard Options</a>.
*
* @param memoryString the memory string.
*
* @return a memory string compatible with java.exe.
*/
static Optional<String> asJavaCmdlineMemoryString(final String memoryString) {
// should optionally end in g|G|m|M|k|K
final Matcher mtcJavaCmdlineMemoryString = JAVA_CMDLINE_MEMORY_STRING.matcher(memoryString);
if (!mtcJavaCmdlineMemoryString.matches()) {
// invalid java cmdline memory string
return Optional.empty();
}

final String value = mtcJavaCmdlineMemoryString.group(1);
@Nullable final String mnemonic = mtcJavaCmdlineMemoryString.group(2);

if (mnemonic == null) {
// no mnemonic supplied, assume `m` for megabytes
return Optional.of(value + "m");
}

// valid mnemonic supplied, so return as is (excluding any additional cruft)
return Optional.of(value + mnemonic);
}

/**
* Converts a memory string for the Java command line arguments -Xms or -Xmx, into
* a memory string that is understood by prunsrv.exe.
* prunsrv.exe expects an integer in megabytes.
*
* @param javaCmdlineMemoryString the memory strig as would be given to the Java command line.
*
* @return a memory string suitable for use with prunsrv.exe.
*/
static Optional<String> asPrunSrvMemoryString(final String javaCmdlineMemoryString) {
// should optionally end in g|G|m|M|k|K
final Matcher mtcJavaCmdlineMemoryString = JAVA_CMDLINE_MEMORY_STRING.matcher(javaCmdlineMemoryString);
if (!mtcJavaCmdlineMemoryString.matches()) {
// invalid java cmdline memory string
return Optional.empty();
}

long value = Integer.valueOf(mtcJavaCmdlineMemoryString.group(1)).longValue();
@Nullable String mnemonic = mtcJavaCmdlineMemoryString.group(2);
if (mnemonic == null) {
mnemonic = "m";
}

switch (mnemonic.toLowerCase()) {
case "k":
value = value / 1024;
break;

case "g":
value = value * 1024;
break;

case "m":
default:
// do nothing, megabytes is the default!
break;
}

return Optional.of(Long.toString(value));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* eXist-db Open Source Native XML Database
* Copyright (C) 2001 The eXist-db Authors
*
* info@exist-db.org
* http://www.exist-db.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.exist.launcher;

import org.junit.jupiter.api.Test;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class WindowsServiceManagerTest {

@Test
public void asJavaCmdlineMemoryString() {
assertEquals(Optional.of("1024k"), WindowsServiceManager.asJavaCmdlineMemoryString("1024k"));
assertEquals(Optional.of("1024K"), WindowsServiceManager.asJavaCmdlineMemoryString("1024K"));
assertEquals(Optional.of("1024m"), WindowsServiceManager.asJavaCmdlineMemoryString("1024m"));
assertEquals(Optional.of("1024M"), WindowsServiceManager.asJavaCmdlineMemoryString("1024M"));
assertEquals(Optional.of("1024g"), WindowsServiceManager.asJavaCmdlineMemoryString("1024g"));
assertEquals(Optional.of("1024G"), WindowsServiceManager.asJavaCmdlineMemoryString("1024G"));

// default to MB
assertEquals(Optional.of("128m"), WindowsServiceManager.asJavaCmdlineMemoryString("128"));

// ignore junk
assertEquals(Optional.empty(), WindowsServiceManager.asJavaCmdlineMemoryString("One"));

// if the unit is unknown, fallback to MB
assertEquals(Optional.of("1024m"), WindowsServiceManager.asJavaCmdlineMemoryString("1024t"));
assertEquals(Optional.of("1024m"), WindowsServiceManager.asJavaCmdlineMemoryString("1024T"));
assertEquals(Optional.of("1024m"), WindowsServiceManager.asJavaCmdlineMemoryString("1024z"));
assertEquals(Optional.of("1024m"), WindowsServiceManager.asJavaCmdlineMemoryString("1024Z"));
}

@Test
public void asPrunSrvMemoryString() {
assertEquals(Optional.of("1"), WindowsServiceManager.asPrunSrvMemoryString("1024k"));
assertEquals(Optional.of("1"), WindowsServiceManager.asPrunSrvMemoryString("1024K"));
assertEquals(Optional.of("1024"), WindowsServiceManager.asPrunSrvMemoryString("1024m"));
assertEquals(Optional.of("1024"), WindowsServiceManager.asPrunSrvMemoryString("1024M"));
assertEquals(Optional.of("1048576"), WindowsServiceManager.asPrunSrvMemoryString("1024g"));
assertEquals(Optional.of("1048576"), WindowsServiceManager.asPrunSrvMemoryString("1024G"));

// default to MB
assertEquals(Optional.of("128"), WindowsServiceManager.asPrunSrvMemoryString("128"));

// ignore junk
assertEquals(Optional.empty(), WindowsServiceManager.asPrunSrvMemoryString("One"));

// if the unit is unknown, fallback to MB
assertEquals(Optional.of("1024"), WindowsServiceManager.asPrunSrvMemoryString("1024t"));
assertEquals(Optional.of("1024"), WindowsServiceManager.asPrunSrvMemoryString("1024T"));
assertEquals(Optional.of("1024"), WindowsServiceManager.asPrunSrvMemoryString("1024z"));
assertEquals(Optional.of("1024"), WindowsServiceManager.asPrunSrvMemoryString("1024Z"));
}
}

0 comments on commit b8e7349

Please sign in to comment.