我有一个服务器客户端应用程序,我想使用systemd在Linux上正确停止服务器。
服务器和客户端以Java实现,在JRE 1.8上运行。我发现最优雅的方法是通过DBus请求服务器停止。我在modified DBus binding for Java上实现了DBus通信(因为freedesktop.org的实现似乎不受限制并且被破坏了)。
服务器平均大约需要1-2秒才能正常关闭。
我通过调用成功测试了服务器关闭是否可以通过控制台正常工作
$> /usr/bin/dbus-send --system --print-reply --dest=com.xxx.server.Server /com/xxx/server/Server com.xxx.server.Server.DBusInterfaceImpl.stopServer
在systemctl stop product_srv.service
成功之后,调用SIGKILL
似乎向服务器进程发送了dbus-send
,并且没有等到服务器正确关闭后才进行。我试图将TimeoutStopSec
设置为15秒,但这不起作用。
如何通过DBus正确停止systemd服务?
我是否必须实现DBus客户端来停止服务器,或者dbus-send
是否可以对服务文件进行一些更改?
示例服务器:
implementation 'com.github.hypfvieh:dbus-java:2.7.1'
服务文件/etc/systemd/system/product_srv.service
[Unit]
Description=Product Server
After=network.target mysql.service
Requires=mysql.service
[Service]
Environment=PRODUCT_SRV_PID=/var/run/product_srv/product_srv.pid
Environment=JAVA_HOME=/opt/xxx/java-jre18-x64
Environment=PRODUCT_HOME=/opt/xxx/product_srv
Environment=LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/jni
# assembles the classpath of the server
ExecStart=/opt/xxx/product_srv/bin/server_systemd.sh start
TimeoutStopSec=15s
ExecStop=/usr/bin/dbus-send --system --print-reply --dest=com.xxx.server.Server /com/xxx/server/Server com.xxx.server.Server.DBusInterfaceImpl.stopServer
Type=dbus
BusName=com.xxx.server.Server
[Install]
WantedBy=multi-user.target
策略配置/etc/dbus-1/system.d/product_srv.conf
:
<!DOCTYPE busconfig PUBLIC
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy user="root">
<allow own="com.xxx.server.Server"/>
<allow send_destination="com.xxx.server.Server"/>
<allow receive_sender="com.xxx.server.Server"/>
</policy>
</busconfig>
Server.java
package com.xxx.server;
import org.freedesktop.dbus.DBusConnection;
import org.freedesktop.dbus.DBusInterface;
import org.freedesktop.dbus.exceptions.DBusException;
import com.xxx.server.dbus.DBusClient;
public class Server{
public class DBusInterfaceImpl implements DBusInterface{
public void stopServer(){
Server.this.stopServer();
}
@Override
public boolean isRemote(){
return false;
}
}
public volatile boolean running = true;
private DBusClient dBusClient;
public Server(){
System.out.println("Starting the server");
// start some fancy server sockets (run in different threads)
try{
this.dBusClient = new DBusClient("com.xxx.server.Server",
DBusConnection.SYSTEM);
this.dBusClient.exportObject("/com/xxx/server/Server",
new DBusInterfaceImpl());
}
catch(DBusException e){
e.printStackTrace();
}
System.out.println("Started the server");
// let the main thread wait until stopping
synchronized(this){
while(this.running){
try{
this.wait();
}
catch(InterruptedException e){
e.printStackTrace();
}
}
}
// stop fancy server sockets
System.out.println("Stopped the server");
if (this.dBusClient != null){
try{
this.dBusClient.unExportObject("/com/xxx/server/Server");
this.dBusClient.stop();
}
catch(DBusException e){
e.printStackTrace();
}
}
}
public static void main(String[] args){
new Server();
}
public void stopServer(){
System.out.println("Stopping the server ...");
// don't stop the server immediately to prevent dbus-send
// failing before it receives a reply that the invocation
// is successful
new Thread(() -> {
try{
Thread.sleep(20);
}
catch(InterruptedException e){
e.printStackTrace();
}
synchronized(this){
this.running = false;
this.notifyAll();
}
},
"StopServerThread").start();
}
}
DBusClient.java
package com.xxx.server.dbus;
import org.freedesktop.dbus.DBusConnection;
import org.freedesktop.dbus.DBusInterface;
import org.freedesktop.dbus.exceptions.DBusException;
public class DBusClient{
private DBusConnection bus;
private String interfaceName;
public DBusClient(String interfaceName,
int dBusSession) throws DBusException{
this.bus = DBusConnection.getConnection(dBusSession);
this.interfaceName = interfaceName;
this.requestInterfaceName();
}
private void requestInterfaceName() throws DBusException{
if (this.bus != null && this.interfaceName != null && !this.interfaceName.isEmpty()){
synchronized(this.bus){
this.bus.requestBusName(this.interfaceName);
}
}
}
public void exportObject(String busName,
DBusInterface dBusInterfaceImpl) throws DBusException{
if (this.bus != null && busName != null && !busName.isEmpty() && dBusInterfaceImpl != null){
synchronized(this.bus){
this.bus.exportObject(busName,
dBusInterfaceImpl);
}
}
}
public void unExportObject(String busName){
if (this.bus != null && busName != null && !busName.isEmpty()){
synchronized(this.bus){
this.bus.unExportObject(busName);
}
}
}
public void stop() throws DBusException{
if (this.bus != null){
synchronized(this.bus){
if (this.interfaceName != null && !this.interfaceName.isEmpty()){
this.bus.releaseBusName(this.interfaceName);
}
this.bus.disconnect();
}
}
}
}