Shutdown: Add support for Ctrl-Close event on Windows platforms to gracefully shutdown node
This commit adds the support for the Ctrl-Close event on Windows using native system calls. This way, it is possible to catch the Ctrl-Close event sent by a 'taskill /pid' command (or when the user closes the console window where elasticsearch.bat was started) and gracefully close the node. Before this commit, the node was simply killed on taskkill/window closing.
This commit is contained in:
parent
90f2f1da84
commit
a4133ec4a3
|
@ -19,14 +19,12 @@
|
||||||
|
|
||||||
package org.elasticsearch.bootstrap;
|
package org.elasticsearch.bootstrap;
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
|
||||||
import org.elasticsearch.ExceptionsHelper;
|
import org.elasticsearch.ExceptionsHelper;
|
||||||
import org.elasticsearch.Version;
|
import org.elasticsearch.Version;
|
||||||
import org.elasticsearch.common.PidFile;
|
import org.elasticsearch.common.PidFile;
|
||||||
import org.elasticsearch.common.collect.Tuple;
|
import org.elasticsearch.common.collect.Tuple;
|
||||||
import org.elasticsearch.common.inject.CreationException;
|
import org.elasticsearch.common.inject.CreationException;
|
||||||
import org.elasticsearch.common.inject.spi.Message;
|
import org.elasticsearch.common.inject.spi.Message;
|
||||||
import org.elasticsearch.common.io.FileSystemUtils;
|
|
||||||
import org.elasticsearch.common.jna.Natives;
|
import org.elasticsearch.common.jna.Natives;
|
||||||
import org.elasticsearch.common.logging.ESLogger;
|
import org.elasticsearch.common.logging.ESLogger;
|
||||||
import org.elasticsearch.common.logging.Loggers;
|
import org.elasticsearch.common.logging.Loggers;
|
||||||
|
@ -39,15 +37,13 @@ import org.elasticsearch.node.Node;
|
||||||
import org.elasticsearch.node.NodeBuilder;
|
import org.elasticsearch.node.NodeBuilder;
|
||||||
import org.elasticsearch.node.internal.InternalSettingsPreparer;
|
import org.elasticsearch.node.internal.InternalSettingsPreparer;
|
||||||
|
|
||||||
import java.io.File;
|
import java.nio.file.Paths;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.file.*;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
import static com.google.common.collect.Sets.newHashSet;
|
import static com.google.common.collect.Sets.newHashSet;
|
||||||
|
import static org.elasticsearch.common.jna.Kernel32Library.ConsoleCtrlHandler;
|
||||||
import static org.elasticsearch.common.settings.ImmutableSettings.Builder.EMPTY_SETTINGS;
|
import static org.elasticsearch.common.settings.ImmutableSettings.Builder.EMPTY_SETTINGS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,11 +58,9 @@ public class Bootstrap {
|
||||||
private static Bootstrap bootstrap;
|
private static Bootstrap bootstrap;
|
||||||
|
|
||||||
private void setup(boolean addShutdownHook, Tuple<Settings, Environment> tuple) throws Exception {
|
private void setup(boolean addShutdownHook, Tuple<Settings, Environment> tuple) throws Exception {
|
||||||
// Loggers.getLogger(Bootstrap.class, tuple.v1().get("name")).info("heap_size {}/{}", JvmStats.jvmStats().mem().heapCommitted(), JvmInfo.jvmInfo().mem().heapMax());
|
|
||||||
if (tuple.v1().getAsBoolean("bootstrap.mlockall", false)) {
|
if (tuple.v1().getAsBoolean("bootstrap.mlockall", false)) {
|
||||||
Natives.tryMlockall();
|
Natives.tryMlockall();
|
||||||
}
|
}
|
||||||
tuple = setupJmx(tuple);
|
|
||||||
|
|
||||||
NodeBuilder nodeBuilder = NodeBuilder.nodeBuilder().settings(tuple.v1()).loadConfigSettings(false);
|
NodeBuilder nodeBuilder = NodeBuilder.nodeBuilder().settings(tuple.v1()).loadConfigSettings(false);
|
||||||
node = nodeBuilder.build();
|
node = nodeBuilder.build();
|
||||||
|
@ -78,16 +72,22 @@ public class Bootstrap {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static Tuple<Settings, Environment> setupJmx(Tuple<Settings, Environment> tuple) {
|
if (tuple.v1().getAsBoolean("bootstrap.ctrlhandler", true)) {
|
||||||
// We disable JMX on by default, since we don't really want the overhead of RMI (and RMI GC...)
|
Natives.addConsoleCtrlHandler(new ConsoleCtrlHandler() {
|
||||||
// if (tuple.v1().get(JmxService.SettingsConstants.CREATE_CONNECTOR) == null) {
|
@Override
|
||||||
// // automatically create the connector if we are bootstrapping
|
public boolean handle(int code) {
|
||||||
// Settings updated = settingsBuilder().put(tuple.v1()).put(JmxService.SettingsConstants.CREATE_CONNECTOR, true).build();
|
if (CTRL_CLOSE_EVENT == code) {
|
||||||
// tuple = new Tuple<Settings, Environment>(updated, tuple.v2());
|
ESLogger logger = Loggers.getLogger(Bootstrap.class);
|
||||||
// }
|
logger.info("running graceful exit on windows");
|
||||||
return tuple;
|
|
||||||
|
System.exit(0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setupLogging(Tuple<Settings, Environment> tuple) {
|
private static void setupLogging(Tuple<Settings, Environment> tuple) {
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch licenses this file to you under
|
||||||
|
* the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.common.jna;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.sun.jna.Library;
|
||||||
|
import com.sun.jna.Native;
|
||||||
|
import com.sun.jna.win32.StdCallLibrary;
|
||||||
|
import org.elasticsearch.common.logging.ESLogger;
|
||||||
|
import org.elasticsearch.common.logging.Loggers;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Library for Windows/Kernel32
|
||||||
|
*/
|
||||||
|
public class Kernel32Library {
|
||||||
|
|
||||||
|
private static ESLogger logger = Loggers.getLogger(Kernel32Library.class);
|
||||||
|
|
||||||
|
private Kernel32 internal;
|
||||||
|
|
||||||
|
private List<NativeHandlerCallback> callbacks = new ArrayList<>();
|
||||||
|
|
||||||
|
private final static class Holder {
|
||||||
|
private final static Kernel32Library instance = new Kernel32Library();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Kernel32Library() {
|
||||||
|
try {
|
||||||
|
internal = (Kernel32)Native.synchronizedLibrary((Kernel32)Native.loadLibrary("kernel32", Kernel32.class));
|
||||||
|
logger.debug("windows/Kernel32 library loaded");
|
||||||
|
} catch (NoClassDefFoundError e) {
|
||||||
|
logger.warn("JNA not found. native methods and handlers will be disabled.");
|
||||||
|
} catch (UnsatisfiedLinkError e) {
|
||||||
|
logger.warn("unable to link Windows/Kernel32 library. native methods and handlers will be disabled.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Kernel32Library getInstance() {
|
||||||
|
return Holder.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addConsoleCtrlHandler(ConsoleCtrlHandler handler) {
|
||||||
|
if (internal == null) {
|
||||||
|
throw new UnsupportedOperationException("windows/Kernel32 library not loaded, console ctrl handler cannot be set");
|
||||||
|
}
|
||||||
|
boolean result = false;
|
||||||
|
if (handler != null) {
|
||||||
|
NativeHandlerCallback callback = new NativeHandlerCallback(handler);
|
||||||
|
result = internal.SetConsoleCtrlHandler(callback, true);
|
||||||
|
if (result) {
|
||||||
|
callbacks.add(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableList<Object> getCallbacks() {
|
||||||
|
return ImmutableList.builder().addAll(callbacks).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Kernel32 extends Library {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a Console Ctrl Handler.
|
||||||
|
*
|
||||||
|
* @param handler
|
||||||
|
* @param add
|
||||||
|
* @return true if the handler is correctly set
|
||||||
|
*/
|
||||||
|
public boolean SetConsoleCtrlHandler(StdCallLibrary.StdCallCallback handler, boolean add);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles consoles event with WIN API
|
||||||
|
* <p/>
|
||||||
|
* See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683242%28v=vs.85%29.aspx
|
||||||
|
*/
|
||||||
|
class NativeHandlerCallback implements StdCallLibrary.StdCallCallback {
|
||||||
|
|
||||||
|
private final ConsoleCtrlHandler handler;
|
||||||
|
|
||||||
|
public NativeHandlerCallback(ConsoleCtrlHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean callback(long dwCtrlType) {
|
||||||
|
int event = (int) dwCtrlType;
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("console control handler receives event [{}@{}]", event, dwCtrlType);
|
||||||
|
|
||||||
|
}
|
||||||
|
return handler.handle(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ConsoleCtrlHandler {
|
||||||
|
|
||||||
|
public static final int CTRL_CLOSE_EVENT = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the Ctrl event.
|
||||||
|
*
|
||||||
|
* @param code the code corresponding to the Ctrl sent.
|
||||||
|
* @return true if the handler processed the event, false otherwise. If false, the next handler will be called.
|
||||||
|
*/
|
||||||
|
boolean handle(int code);
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,8 @@
|
||||||
package org.elasticsearch.common.jna;
|
package org.elasticsearch.common.jna;
|
||||||
|
|
||||||
import com.sun.jna.Native;
|
import com.sun.jna.Native;
|
||||||
|
import org.apache.lucene.util.Constants;
|
||||||
|
import org.elasticsearch.common.jna.Kernel32Library.ConsoleCtrlHandler;
|
||||||
import org.elasticsearch.common.logging.ESLogger;
|
import org.elasticsearch.common.logging.ESLogger;
|
||||||
import org.elasticsearch.common.logging.Loggers;
|
import org.elasticsearch.common.logging.Loggers;
|
||||||
|
|
||||||
|
@ -59,4 +61,21 @@ public class Natives {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void addConsoleCtrlHandler(ConsoleCtrlHandler handler) {
|
||||||
|
// The console Ctrl handler is necessary on Windows platforms only.
|
||||||
|
if (Constants.WINDOWS) {
|
||||||
|
try {
|
||||||
|
boolean result = Kernel32Library.getInstance().addConsoleCtrlHandler(handler);
|
||||||
|
if (result) {
|
||||||
|
logger.debug("console ctrl handler correctly set");
|
||||||
|
} else {
|
||||||
|
logger.warn("unknown error " + Native.getLastError() + " when adding console ctrl handler:");
|
||||||
|
}
|
||||||
|
} catch (UnsatisfiedLinkError e) {
|
||||||
|
// this will have already been logged by Kernel32Library, no need to repeat it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch licenses this file to you under
|
||||||
|
* the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.common.jna;
|
||||||
|
|
||||||
|
import org.apache.lucene.util.Constants;
|
||||||
|
import org.elasticsearch.common.jna.Kernel32Library.ConsoleCtrlHandler;
|
||||||
|
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
|
||||||
|
public class Kernel32LibraryTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Those properties are set by the JNA Api and if not ignored,
|
||||||
|
* lead to tests failure (see AbstractRandomizedTest#IGNORED_INVARIANT_PROPERTIES)
|
||||||
|
*/
|
||||||
|
private static final String[] JNA_INVARIANT_PROPERTIES = {
|
||||||
|
"jna.platform.library.path",
|
||||||
|
"jnidispatch.path"
|
||||||
|
};
|
||||||
|
|
||||||
|
private Map<String, String> properties = new HashMap<>();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void saveProperties() {
|
||||||
|
for (String p : JNA_INVARIANT_PROPERTIES) {
|
||||||
|
properties.put(p, System.getProperty(p));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void restoreProperties() {
|
||||||
|
for (String p : JNA_INVARIANT_PROPERTIES) {
|
||||||
|
if (properties.get(p) != null) {
|
||||||
|
System.setProperty(p, properties.get(p));
|
||||||
|
} else {
|
||||||
|
System.clearProperty(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testKernel32Library() {
|
||||||
|
ConsoleCtrlHandler handler = new ConsoleCtrlHandler() {
|
||||||
|
@Override
|
||||||
|
public boolean handle(int code) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assertNotNull(Kernel32Library.getInstance());
|
||||||
|
assertThat(Kernel32Library.getInstance().getCallbacks().size(), equalTo(0));
|
||||||
|
|
||||||
|
if (Constants.WINDOWS) {
|
||||||
|
assertTrue(Kernel32Library.getInstance().addConsoleCtrlHandler(handler));
|
||||||
|
assertThat(Kernel32Library.getInstance().getCallbacks().size(), equalTo(1));
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Kernel32Library.getInstance().addConsoleCtrlHandler(handler);
|
||||||
|
fail("should have thrown an unsupported operation exception");
|
||||||
|
} catch (UnsupportedOperationException e) {
|
||||||
|
assertThat(e.getMessage(), e.getMessage().contains("console ctrl handler cannot be set"), equalTo(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue