3030import org .apache .logging .log4j .Logger ;
3131import org .exist .util .ConfigurationHelper ;
3232
33+ import javax .annotation .Nullable ;
3334import java .io .BufferedReader ;
3435import java .io .IOException ;
3536import java .io .InputStreamReader ;
3839import java .nio .file .Paths ;
3940import java .util .*;
4041import java .util .List ;
42+ import java .util .regex .Matcher ;
43+ import java .util .regex .Pattern ;
4144
4245import static com .evolvedbinary .j8fu .Either .Left ;
4346import static com .evolvedbinary .j8fu .Either .Right ;
4952@ NotThreadSafe
5053class WindowsServiceManager implements ServiceManager {
5154
55+ /**
56+ * See <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html#BABHDABI">Java - Non-Standard Options</a>.
57+ */
58+ private static final Pattern JAVA_CMDLINE_MEMORY_STRING = Pattern .compile ("([0-9]+)(g|G|m|M|k|K)?.*" );
59+
5260 private static final Logger LOG = LogManager .getLogger (WindowsServiceManager .class );
5361 private static final String PROCRUN_SRV_EXE = "prunsrv-x86_64.exe" ;
5462 private static final String SC_EXE = "sc.exe" ;
@@ -86,8 +94,8 @@ public void install() throws ServiceManagerException {
8694 .orElse (existHome .resolve ("etc" ).resolve ("conf.xml" ));
8795
8896 final Properties launcherProperties = ConfigurationUtility .loadProperties ();
89- final Optional <String > maxMemory = Optional .ofNullable (launcherProperties .getProperty (LAUNCHER_PROPERTY_MAX_MEM )).map ( s -> s + "m" );
90- final String minMemory = launcherProperties .getProperty (LAUNCHER_PROPERTY_MIN_MEM , "128" ) + "m" ;
97+ final Optional <String > maxMemory = Optional .ofNullable (launcherProperties .getProperty (LAUNCHER_PROPERTY_MAX_MEM )).flatMap ( WindowsServiceManager :: asJavaCmdlineMemoryString );
98+ final Optional < String > minMemory = asJavaCmdlineMemoryString ( launcherProperties .getProperty (LAUNCHER_PROPERTY_MIN_MEM , "128" )) ;
9199
92100 final StringBuilder jvmOptions = new StringBuilder ();
93101 jvmOptions .append ("-Dfile.encoding=UTF-8" );
@@ -117,7 +125,6 @@ public void install() throws ServiceManagerException {
117125 "--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!
118126 "--Jvm=" + findJvm ().orElse ("auto" ),
119127 "--Classpath=\" " + existHome .resolve ("lib" ).toAbsolutePath ().toString ().replace ('\\' , '/' ) + "/*\" " ,
120- "--JvmMs=" + minMemory ,
121128 "--StartMode=jvm" ,
122129 "--StartClass=org.exist.service.ExistDbDaemon" ,
123130 "--StartMethod=start" ,
@@ -127,7 +134,8 @@ public void install() throws ServiceManagerException {
127134 "--JvmOptions=\" " + jvmOptions + "\" " ,
128135 "--StartParams=\" " + configFile .toAbsolutePath ().toString () + "\" "
129136 );
130- maxMemory .ifPresent (xmx -> args .add ("--JvmMx=" + xmx ));
137+ minMemory .flatMap (WindowsServiceManager ::asPrunSrvMemoryString ).ifPresent (xms -> args .add ("--JvmMs=" + xms ));
138+ maxMemory .flatMap (WindowsServiceManager ::asPrunSrvMemoryString ).ifPresent (xmx -> args .add ("--JvmMx=" + xmx ));
131139
132140 try {
133141 final Tuple2 <Integer , String > execResult = run (args , true );
@@ -367,4 +375,75 @@ private Tuple2<Integer, String> run(List<String> args, final boolean elevated) t
367375 final int exitValue = process .waitFor ();
368376 return Tuple (exitValue , output .toString ());
369377 }
378+
379+ /**
380+ * Transform the supplied memory string into a string
381+ * that is compatible with the Java command line arguments for -Xms and -Xmx.
382+ *
383+ * See <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html#BABHDABI">Java - Non-Standard Options</a>.
384+ *
385+ * @param memoryString the memory string.
386+ *
387+ * @return a memory string compatible with java.exe.
388+ */
389+ static Optional <String > asJavaCmdlineMemoryString (final String memoryString ) {
390+ // should optionally end in g|G|m|M|k|K
391+ final Matcher mtcJavaCmdlineMemoryString = JAVA_CMDLINE_MEMORY_STRING .matcher (memoryString );
392+ if (!mtcJavaCmdlineMemoryString .matches ()) {
393+ // invalid java cmdline memory string
394+ return Optional .empty ();
395+ }
396+
397+ final String value = mtcJavaCmdlineMemoryString .group (1 );
398+ @ Nullable final String mnemonic = mtcJavaCmdlineMemoryString .group (2 );
399+
400+ if (mnemonic == null ) {
401+ // no mnemonic supplied, assume `m` for megabytes
402+ return Optional .of (value + "m" );
403+ }
404+
405+ // valid mnemonic supplied, so return as is (excluding any additional cruft)
406+ return Optional .of (value + mnemonic );
407+ }
408+
409+ /**
410+ * Converts a memory string for the Java command line arguments -Xms or -Xmx, into
411+ * a memory string that is understood by prunsrv.exe.
412+ * prunsrv.exe expects an integer in megabytes.
413+ *
414+ * @param javaCmdlineMemoryString the memory strig as would be given to the Java command line.
415+ *
416+ * @return a memory string suitable for use with prunsrv.exe.
417+ */
418+ static Optional <String > asPrunSrvMemoryString (final String javaCmdlineMemoryString ) {
419+ // should optionally end in g|G|m|M|k|K
420+ final Matcher mtcJavaCmdlineMemoryString = JAVA_CMDLINE_MEMORY_STRING .matcher (javaCmdlineMemoryString );
421+ if (!mtcJavaCmdlineMemoryString .matches ()) {
422+ // invalid java cmdline memory string
423+ return Optional .empty ();
424+ }
425+
426+ long value = Integer .valueOf (mtcJavaCmdlineMemoryString .group (1 )).longValue ();
427+ @ Nullable String mnemonic = mtcJavaCmdlineMemoryString .group (2 );
428+ if (mnemonic == null ) {
429+ mnemonic = "m" ;
430+ }
431+
432+ switch (mnemonic .toLowerCase ()) {
433+ case "k" :
434+ value = value / 1024 ;
435+ break ;
436+
437+ case "g" :
438+ value = value * 1024 ;
439+ break ;
440+
441+ case "m" :
442+ default :
443+ // do nothing, megabytes is the default!
444+ break ;
445+ }
446+
447+ return Optional .of (Long .toString (value ));
448+ }
370449}
0 commit comments