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;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import org.elasticsearch.ExceptionsHelper;
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.common.PidFile;
|
||||
import org.elasticsearch.common.collect.Tuple;
|
||||
import org.elasticsearch.common.inject.CreationException;
|
||||
import org.elasticsearch.common.inject.spi.Message;
|
||||
import org.elasticsearch.common.io.FileSystemUtils;
|
||||
import org.elasticsearch.common.jna.Natives;
|
||||
import org.elasticsearch.common.logging.ESLogger;
|
||||
import org.elasticsearch.common.logging.Loggers;
|
||||
|
@ -39,15 +37,13 @@ import org.elasticsearch.node.Node;
|
|||
import org.elasticsearch.node.NodeBuilder;
|
||||
import org.elasticsearch.node.internal.InternalSettingsPreparer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
|
@ -62,11 +58,9 @@ public class Bootstrap {
|
|||
private static Bootstrap bootstrap;
|
||||
|
||||
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)) {
|
||||
Natives.tryMlockall();
|
||||
}
|
||||
tuple = setupJmx(tuple);
|
||||
|
||||
NodeBuilder nodeBuilder = NodeBuilder.nodeBuilder().settings(tuple.v1()).loadConfigSettings(false);
|
||||
node = nodeBuilder.build();
|
||||
|
@ -78,16 +72,22 @@ public class Bootstrap {
|
|||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static Tuple<Settings, Environment> setupJmx(Tuple<Settings, Environment> tuple) {
|
||||
// We disable JMX on by default, since we don't really want the overhead of RMI (and RMI GC...)
|
||||
// if (tuple.v1().get(JmxService.SettingsConstants.CREATE_CONNECTOR) == null) {
|
||||
// // automatically create the connector if we are bootstrapping
|
||||
// Settings updated = settingsBuilder().put(tuple.v1()).put(JmxService.SettingsConstants.CREATE_CONNECTOR, true).build();
|
||||
// tuple = new Tuple<Settings, Environment>(updated, tuple.v2());
|
||||
// }
|
||||
return tuple;
|
||||
if (tuple.v1().getAsBoolean("bootstrap.ctrlhandler", true)) {
|
||||
Natives.addConsoleCtrlHandler(new ConsoleCtrlHandler() {
|
||||
@Override
|
||||
public boolean handle(int code) {
|
||||
if (CTRL_CLOSE_EVENT == code) {
|
||||
ESLogger logger = Loggers.getLogger(Bootstrap.class);
|
||||
logger.info("running graceful exit on windows");
|
||||
|
||||
System.exit(0);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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.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