FELIX-2055: Use a nicer shutdown mechanism when stopping a managed instance
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@907993 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/AdminServiceImpl.java b/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/AdminServiceImpl.java
index f6b8eae..69c7f4c 100644
--- a/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/AdminServiceImpl.java
+++ b/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/AdminServiceImpl.java
@@ -48,6 +48,8 @@
private File storageLocation;
+ private long stopTimeout = 30000;
+
public File getStorageLocation() {
return storageLocation;
}
@@ -56,6 +58,14 @@
this.storageLocation = storage;
}
+ public long getStopTimeout() {
+ return stopTimeout;
+ }
+
+ public void setStopTimeout(long stopTimeout) {
+ this.stopTimeout = stopTimeout;
+ }
+
private Properties loadStorage(File location) throws IOException {
InputStream is = null;
try {
diff --git a/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/InstanceImpl.java b/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/InstanceImpl.java
index c3131c7..91fca88 100644
--- a/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/InstanceImpl.java
+++ b/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/InstanceImpl.java
@@ -16,14 +16,12 @@
*/
package org.apache.felix.karaf.admin.internal;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
+import java.io.*;
import java.net.Socket;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Properties;
import org.apache.felix.karaf.admin.Instance;
@@ -37,6 +35,20 @@
private static final Logger LOG = LoggerFactory.getLogger(InstanceImpl.class);
+ private static final String CONFIG_PROPERTIES_FILE_NAME = "config.properties";
+
+ private static final String KARAF_SHUTDOWN_PORT = "karaf.shutdown.port";
+
+ private static final String KARAF_SHUTDOWN_HOST = "karaf.shutdown.host";
+
+ private static final String KARAF_SHUTDOWN_PORT_FILE = "karaf.shutdown.port.file";
+
+ private static final String KARAF_SHUTDOWN_COMMAND = "karaf.shutdown.command";
+
+ private static final String KARAF_SHUTDOWN_PID_FILE = "karaf.shutdown.pid.file";
+
+ private static final String DEFAULT_SHUTDOWN_COMMAND = "SHUTDOWN";
+
private AdminServiceImpl service;
private String name;
private String location;
@@ -170,7 +182,11 @@
if (this.process == null) {
throw new IllegalStateException("Instance not started");
}
- this.process.destroy();
+ // Try a clean shutdown
+ cleanShutdown();
+ if (this.process != null) {
+ this.process.destroy();
+ }
}
public synchronized void destroy() throws Exception {
@@ -215,6 +231,44 @@
}
}
+ protected void cleanShutdown() {
+ try {
+ File file = new File(new File(location, "etc"), CONFIG_PROPERTIES_FILE_NAME);
+ URL configPropURL = file.toURI().toURL();
+ Properties props = loadPropertiesFile(configPropURL);
+ props.put("karaf.base", new File(location).getCanonicalPath());
+ props.put("karaf.home", System.getProperty("karaf.home"));
+ for (Enumeration e = props.propertyNames(); e.hasMoreElements();) {
+ String name = (String) e.nextElement();
+ props.setProperty(name,
+ substVars(props.getProperty(name), name, null, props));
+ }
+ int port = Integer.parseInt(props.getProperty(KARAF_SHUTDOWN_PORT, "0"));
+ String host = props.getProperty(KARAF_SHUTDOWN_HOST, "localhost");
+ String portFile = props.getProperty(KARAF_SHUTDOWN_PORT_FILE);
+ String shutdown = props.getProperty(KARAF_SHUTDOWN_COMMAND, DEFAULT_SHUTDOWN_COMMAND);
+ if (port == 0 && portFile != null) {
+ BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(portFile)));
+ String portStr = r.readLine();
+ port = Integer.parseInt(portStr);
+ r.close();
+ }
+ // We found the port, try to send the command
+ if (port > 0) {
+ Socket s = new Socket(host, port);
+ s.getOutputStream().write(shutdown.getBytes());
+ s.close();
+ long t = System.currentTimeMillis() + service.getStopTimeout();
+ do {
+ Thread.sleep(100);
+ checkProcess();
+ } while (System.currentTimeMillis() < t && process != null);
+ }
+ } catch (Exception e) {
+ LOG.debug("Unable to cleanly shutdown instance", e);
+ }
+ }
+
protected static boolean deleteFile(File fileToDelete) {
if (fileToDelete == null || !fileToDelete.exists()) {
return true;
@@ -241,4 +295,122 @@
result &= fileToDelete.delete();
return result;
}
+
+ protected static Properties loadPropertiesFile(URL configPropURL) throws Exception {
+ // Read the properties file.
+ Properties configProps = new Properties();
+ InputStream is = null;
+ try {
+ is = configPropURL.openConnection().getInputStream();
+ configProps.load(is);
+ is.close();
+ }
+ catch (Exception ex) {
+ System.err.println(
+ "Error loading config properties from " + configPropURL);
+ System.err.println("Main: " + ex);
+ try {
+ if (is != null) is.close();
+ }
+ catch (IOException ex2) {
+ // Nothing we can do.
+ }
+ return null;
+ }
+ return configProps;
+ }
+
+ private static final String DELIM_START = "${";
+ private static final String DELIM_STOP = "}";
+
+ protected static String substVars(String val, String currentKey,
+ Map<String, String> cycleMap, Properties configProps)
+ throws IllegalArgumentException {
+ // If there is currently no cycle map, then create
+ // one for detecting cycles for this invocation.
+ if (cycleMap == null) {
+ cycleMap = new HashMap<String, String>();
+ }
+
+ // Put the current key in the cycle map.
+ cycleMap.put(currentKey, currentKey);
+
+ // Assume we have a value that is something like:
+ // "leading ${foo.${bar}} middle ${baz} trailing"
+
+ // Find the first ending '}' variable delimiter, which
+ // will correspond to the first deepest nested variable
+ // placeholder.
+ int stopDelim = val.indexOf(DELIM_STOP);
+
+ // Find the matching starting "${" variable delimiter
+ // by looping until we find a start delimiter that is
+ // greater than the stop delimiter we have found.
+ int startDelim = val.indexOf(DELIM_START);
+ while (stopDelim >= 0) {
+ int idx = val.indexOf(DELIM_START, startDelim + DELIM_START.length());
+ if ((idx < 0) || (idx > stopDelim)) {
+ break;
+ } else if (idx < stopDelim) {
+ startDelim = idx;
+ }
+ }
+
+ // If we do not have a start or stop delimiter, then just
+ // return the existing value.
+ if ((startDelim < 0) && (stopDelim < 0)) {
+ return val;
+ }
+ // At this point, we found a stop delimiter without a start,
+ // so throw an exception.
+ else if (((startDelim < 0) || (startDelim > stopDelim))
+ && (stopDelim >= 0)) {
+ throw new IllegalArgumentException(
+ "stop delimiter with no start delimiter: "
+ + val);
+ }
+
+ // At this point, we have found a variable placeholder so
+ // we must perform a variable substitution on it.
+ // Using the start and stop delimiter indices, extract
+ // the first, deepest nested variable placeholder.
+ String variable =
+ val.substring(startDelim + DELIM_START.length(), stopDelim);
+
+ // Verify that this is not a recursive variable reference.
+ if (cycleMap.get(variable) != null) {
+ throw new IllegalArgumentException(
+ "recursive variable reference: " + variable);
+ }
+
+ // Get the value of the deepest nested variable placeholder.
+ // Try to configuration properties first.
+ String substValue = (configProps != null)
+ ? configProps.getProperty(variable, null)
+ : null;
+ if (substValue == null) {
+ // Ignore unknown property values.
+ substValue = System.getProperty(variable, "");
+ }
+
+ // Remove the found variable from the cycle map, since
+ // it may appear more than once in the value and we don't
+ // want such situations to appear as a recursive reference.
+ cycleMap.remove(variable);
+
+ // Append the leading characters, the substituted value of
+ // the variable, and the trailing characters to get the new
+ // value.
+ val = val.substring(0, startDelim)
+ + substValue
+ + val.substring(stopDelim + DELIM_STOP.length(), val.length());
+
+ // Now perform substitution again, since there could still
+ // be substitutions to make.
+ val = substVars(val, currentKey, cycleMap, configProps);
+
+ // Return the value.
+ return val;
+ }
+
}
diff --git a/karaf/main/src/main/java/org/apache/felix/karaf/main/Main.java b/karaf/main/src/main/java/org/apache/felix/karaf/main/Main.java
index 453c8d0..663a399 100644
--- a/karaf/main/src/main/java/org/apache/felix/karaf/main/Main.java
+++ b/karaf/main/src/main/java/org/apache/felix/karaf/main/Main.java
@@ -1203,6 +1203,7 @@
// Match against our command string
boolean match = command.toString().equals(shutdown);
if (match) {
+ LOG.log(Level.INFO, "Karaf shutdown socket: received shutdown command. Stopping framework...");
framework.stop();
break;
} else {