1

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();
         }
      }
   }
}
fireandfuel
  • 732
  • 1
  • 9
  • 22
  • Why not just handle the `SIGTERM` signal in your server process, and not use D-Bus? `systemctl stop` should send `SIGTERM` to your process to tell it to gracefully shut down; it will then wait for the `TimeoutStopSec` period before sending `SIGKILL`. – Philip Withnall Jun 18 '19 at 10:14
  • You mean like here: https://stackoverflow.com/questions/47365753/how-to-process-sigterm-signal-gracefully-in-java ? – fireandfuel Jun 18 '19 at 15:19
  • Probably, yes — I’m not a Java expert. – Philip Withnall Jun 19 '19 at 10:50

0 Answers0