I have a server client application and I want to stop the server on Linux properly with systemd.
The server and client are implemented in Java, running on JRE 1.8. I figured out the most elegant way is to request the server stop over DBus. I implemented the DBus communication over a modified DBus binding for Java (because freedesktop.org's implementation seems to be unmainted and broken).
The server needs around 1-2 seconds on average to properly shutdown.
I successfully tested that the server shutdown works properly over console by invoking
$> /usr/bin/dbus-send --system --print-reply --dest=com.xxx.server.Server /com/xxx/server/Server com.xxx.server.Server.DBusInterfaceImpl.stopServer
Calling systemctl stop product_srv.service
seems to send a SIGKILL
to the server process after dbus-send
succeeded and doesn't wait until the server is properly shutdown. I tried to set the TimeoutStopSec
to 15 seconds, but it doesn't work.
How do I stop the systemd service properly over DBus?
Do I have to implement a DBus client to stop the server or does dbus-send
work with some changes at the service file?
Example server:
implementation 'com.github.hypfvieh:dbus-java:2.7.1'
service file /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
policy config /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();
}
}
}
}