Skip to content

Commit 905ae1c

Browse files
committed
[server] Register signal handlers to log termination due to SIGTERM, SIGHUP and SIGINT
1 parent ae84521 commit 905ae1c

File tree

4 files changed

+172
-1
lines changed

4 files changed

+172
-1
lines changed

fluss-server/src/main/java/org/apache/fluss/server/ServerBase.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import org.apache.fluss.server.tablet.TabletServer;
3232
import org.apache.fluss.server.utils.ConfigurationParserUtils;
3333
import org.apache.fluss.server.utils.FatalErrorHandler;
34+
import org.apache.fluss.server.utils.LoggingSignalHandler;
35+
import org.apache.fluss.server.utils.OperatingSystem;
3436
import org.apache.fluss.server.utils.ShutdownHookUtil;
3537
import org.apache.fluss.utils.AutoCloseableAsync;
3638
import org.apache.fluss.utils.ExceptionUtils;
@@ -113,6 +115,17 @@ protected static void startServer(ServerBase server) {
113115
}
114116

115117
public void start() throws Exception {
118+
try {
119+
if (!OperatingSystem.IS_WINDOWS) {
120+
new LoggingSignalHandler().register();
121+
}
122+
} catch (ReflectiveOperationException e) {
123+
LOG.warn(
124+
"Failed to register optional signal handler that logs a message when the process is terminated "
125+
+ "by a signal. Reason for registration failure is: ",
126+
e);
127+
}
128+
116129
try {
117130
addShutDownHook();
118131

fluss-server/src/main/java/org/apache/fluss/server/tablet/TabletServer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ protected void startServices() throws Exception {
284284
@Override
285285
protected CompletableFuture<Result> closeAsync(Result result) {
286286
if (isShutDown.compareAndSet(false, true)) {
287-
287+
LOG.info("Shutting down Tablet server ({}).", result);
288288
controlledShutDown();
289289

290290
CompletableFuture<Void> serviceShutdownFuture = stopServices();
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.fluss.server.utils;
19+
20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
22+
23+
import java.lang.reflect.Constructor;
24+
import java.lang.reflect.InvocationHandler;
25+
import java.lang.reflect.InvocationTargetException;
26+
import java.lang.reflect.Method;
27+
import java.lang.reflect.Proxy;
28+
import java.util.Arrays;
29+
import java.util.List;
30+
import java.util.Map;
31+
import java.util.concurrent.ConcurrentHashMap;
32+
33+
/**
34+
* A utility class to register signal handlers to log termination due to SIGTERM, SIGHUP and SIGINT
35+
* (control-c).
36+
*/
37+
public class LoggingSignalHandler {
38+
39+
private static final Logger LOG = LoggerFactory.getLogger(LoggingSignalHandler.class);
40+
41+
private static final List<String> SIGNALS = Arrays.asList("TERM", "INT", "HUP");
42+
43+
private final Constructor<?> signalConstructor;
44+
private final Class<?> signalHandlerClass;
45+
private final Method signalHandleMethod;
46+
private final Method signalGetNameMethod;
47+
private final Method signalHandlerHandleMethod;
48+
49+
/**
50+
* Create an instance of this class.
51+
*
52+
* @throws ReflectiveOperationException if the underlying API has changed in an incompatible
53+
* manner.
54+
*/
55+
public LoggingSignalHandler() throws ReflectiveOperationException {
56+
Class<?> signalClass = Class.forName("sun.misc.Signal");
57+
signalConstructor = signalClass.getConstructor(String.class);
58+
signalHandlerClass = Class.forName("sun.misc.SignalHandler");
59+
signalHandlerHandleMethod = signalHandlerClass.getMethod("handle", signalClass);
60+
signalHandleMethod = signalClass.getMethod("handle", signalClass, signalHandlerClass);
61+
signalGetNameMethod = signalClass.getMethod("getName");
62+
}
63+
64+
/**
65+
* Register signal handler to log termination due to SIGTERM, SIGHUP and SIGINT (control-c).
66+
* This method does not currently work on Windows.
67+
*
68+
* @implNote sun.misc.Signal and sun.misc.SignalHandler are described as "not encapsulated" in
69+
* <a href="http://openjdk.java.net/jeps/260">...</a>. However, they are not available in to
70+
* compile classpath if the `--release` flag is used. As a workaround, we rely on
71+
* reflection.
72+
*/
73+
public void register() throws ReflectiveOperationException {
74+
Map<String, Object> jvmSignalHandlers = new ConcurrentHashMap<>();
75+
76+
for (String signal : SIGNALS) {
77+
register(signal, jvmSignalHandlers);
78+
}
79+
LOG.info("Registered signal handlers for {}", String.join(", ", SIGNALS));
80+
}
81+
82+
private Object createSignalHandler(final Map<String, Object> jvmSignalHandlers) {
83+
InvocationHandler invocationHandler =
84+
new InvocationHandler() {
85+
86+
private String getName(Object signal) throws Throwable {
87+
try {
88+
return (String) signalGetNameMethod.invoke(signal);
89+
} catch (InvocationTargetException e) {
90+
throw e.getCause();
91+
}
92+
}
93+
94+
private void handle(Object signalHandler, Object signal)
95+
throws ReflectiveOperationException {
96+
signalHandlerHandleMethod.invoke(signalHandler, signal);
97+
}
98+
99+
@Override
100+
public Object invoke(Object proxy, Method method, Object[] args)
101+
throws Throwable {
102+
Object signal = args[0];
103+
LOG.info("Terminating process due to signal {}", signal);
104+
Object handler = jvmSignalHandlers.get(getName(signal));
105+
if (handler != null) handle(handler, signal);
106+
return null;
107+
}
108+
};
109+
110+
return Proxy.newProxyInstance(
111+
LoggingSignalHandler.class.getClassLoader(),
112+
new Class<?>[] {signalHandlerClass},
113+
invocationHandler);
114+
}
115+
116+
private void register(String signalName, final Map<String, Object> jvmSignalHandlers)
117+
throws ReflectiveOperationException {
118+
Object signal = signalConstructor.newInstance(signalName);
119+
Object signalHandler = createSignalHandler(jvmSignalHandlers);
120+
Object oldHandler = signalHandleMethod.invoke(null, signal, signalHandler);
121+
if (oldHandler != null) jvmSignalHandlers.put(signalName, oldHandler);
122+
}
123+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.fluss.server.utils;
19+
20+
import java.util.Locale;
21+
22+
/** Utility class for operating system related helper functions. */
23+
public final class OperatingSystem {
24+
25+
private OperatingSystem() {}
26+
27+
public static final String NAME;
28+
29+
public static final boolean IS_WINDOWS;
30+
31+
static {
32+
NAME = System.getProperty("os.name").toLowerCase(Locale.ROOT);
33+
IS_WINDOWS = NAME.startsWith("windows");
34+
}
35+
}

0 commit comments

Comments
 (0)