Monday, February 23, 2015

Spring boot 1.4 run as windows service Procrun


The challenge was starting spring boot application as windows service. I spent a long time searching on the Internet the solution but I didn't find it.

My solution was the following.

First I created an application using start.spring.io



Then, I created a project to which I added spring-boot-loader dependency

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-loader</artifactId>
            <scope>provided</scope>
        </dependency>

because of the need to extend the JarLauncher class.
Spring boot provides a special launcher that changes the java behaviour class loader. The class org.springframework.boot.loader.JarLauncher creates a special class loader and boostraps the application.

Since I wanted to launch the application as a window service, I chose Procrun as service manager. 
Procrun needs two start and stop methods or one method with string array parameter (see procrun project for more details). Therefore I created a Bootsrap class that extended JarLauncher and implemented the methods that Procrun needs.

public class Bootstrap extends JarLauncher {

    private static ClassLoader classLoader = null;
    private static Bootstrap bootstrap = null;

    protected void launch(String[] args, String mainClass, ClassLoader classLoader, boolean wait)
            throws Exception {
        // spring boot 1.2 Runnable runner = createMainMethodRunner(mainClass, args, classLoader);
        // spring boot 1.2 Thread runnerThread = new Thread(runner);
        Thread runnerThread = new Thread(() -> {
            try {
                createMainMethodRunner(mainClass, args, classLoader).run();
            }
            catch(Exception ex) {}
        });        
        runnerThread.setContextClassLoader(classLoader);
        runnerThread.setName(Thread.currentThread().getName());
        runnerThread.start();
        if (wait == true) {
            runnerThread.join();
        }
    }

    public static void start (String []args) {
        bootstrap = new Bootstrap ();
        try {
            JarFile.registerUrlProtocolHandler();
            classLoader = bootstrap.createClassLoader(bootstrap.getClassPathArchives());
            bootstrap.launch(args, bootstrap.getMainClass(), classLoader, true);
        }
        catch (Exception ex) {
            ex.printStackTrace();
            System.exit(1);
        }
    }

    public static void stop (String []args) {
        try {
            if (bootstrap != null) {
                bootstrap.launch(args, bootstrap.getMainClass(), classLoader, true);
                bootstrap = null;
                classLoader = null;
            }
        }
        catch (Exception ex) {
            ex.printStackTrace();
            System.exit(1);
        }
    }

    public static void main(String[] args) {
        String mode = args != null && args.length > 0 ? args[0] : null;
        if ("start".equals(mode)) {
            Bootstrap.start(args);
        }
        else if ("stop".equals(mode)) {
            Bootstrap.stop(args);
        }
    }
}

In the spring boot application class I changed the main method with:

    private static ApplicationContext applicationContext = null;

    public static void main(String[] args) {
        String mode = args != null && args.length > 0 ? args[0] : null;

        if (logger.isDebugEnabled()) {
            logger.debug("PID:" + ManagementFactory.getRuntimeMXBean().getName() + 
                         " Application mode:" + mode + " context:" + applicationContext);
        }
        if (applicationContext != null && mode != null && "stop".equals(mode)) {
            System.exit(SpringApplication.exit(applicationContext, new ExitCodeGenerator() {
                @Override
                public int getExitCode() {
                    return 0;
                }
            }));
        }
        else {
            SpringApplication app = new SpringApplication(TestProcrunApplication.class);
            applicationContext = app.run(args);
            if (logger.isDebugEnabled()) {
                logger.debug("PID:" + ManagementFactory.getRuntimeMXBean().getName() + 
                             " Application started context:" + applicationContext);
            }
        }
    }

Then, I installed the service with prunsrv.exe

prunsrv.exe //IS//test-procrun --DisplayName="test-procrun" --Description="test-procrun" --Startup=auto --Install=%CD%\prunsrv.exe --Jvm=auto --Classpath=%CD%..\target\test-procrun-0.0.1-SNAPSHOT.jar --StartMode=jvm --StartClass=it.test.procrun.Bootstrap --StartMethod=start --StartParams=start --StopMode=jvm --StopClass=it.test.procrun.Bootstrap --StopMethod=stop --StopParams=stop --StdOutput=auto --StdError=auto --LogPath=%CD% --LogLevel=Debug





source code spring boot 1.2,1.3
source code spring boot 1.4

27 comments:

  1. I have followed this example with my own app, but it never returns to service manager saying it is start, usually get a timeout error. I have increased the ServicesPipeTimeout. Any other suggestions?

    ReplyDelete
    Replies
    1. But if you run your application with java -jar your_app.jar it works?
      Can you post commons-daemon.log?

      Delete
    2. yes, it will run that way and takes about 17-20 seconds to initialize.

      [2015-03-23 19:20:08] [info] ( prunsrv.c:1683) [ 2592] Commons Daemon procrun (1.0.15.0 32-bit) started
      [2015-03-23 19:20:08] [info] ( prunsrv.c:725 ) [ 2592] Starting service 'AutoAPI-procrun' ...
      [2015-03-23 19:21:08] [debug]

      Delete
    3. only this log...
      In my example I had
      [2015-03-24 18:19:04] [debug] ( prunsrv.c:1679) [ 3116] Commons Daemon procrun log initialized
      [2015-03-24 18:19:04] [info] ( prunsrv.c:1683) [ 3116] Commons Daemon procrun (1.0.15.0 32-bit) started
      [2015-03-24 18:19:04] [info] ( prunsrv.c:1596) [ 3116] Running 'test-procrun' Service...
      [2015-03-24 18:19:04] [debug] ( prunsrv.c:1374) [ 3940] Inside ServiceMain...
      [2015-03-24 18:19:04] [debug] ( prunsrv.c:844 ) [ 3940] reportServiceStatusE: 2, 0, 3000, 0
      [2015-03-24 18:19:04] [info] ( prunsrv.c:1127) [ 3940] Starting service...
      [2015-03-24 18:19:04] [debug] ( javajni.c:233 ) [ 3940] loading jvm 'C:\Programmi\Java\jre7\bin\client\jvm.dll'
      [2015-03-24 18:19:04] [debug] ( javajni.c:704 ) [ 3112] Jvm Option[0] -Djava.class.path=C:\prj\test-procrun\procrun\..\target\test-procrun-0.0.1-SNAPSHOT.jar
      [2015-03-24 18:19:05] [debug] ( javajni.c:704 ) [ 3112] Jvm Option[1] exit
      [2015-03-24 18:19:05] [debug] ( javajni.c:888 ) [ 3112] argv[0] = start
      [2015-03-24 18:19:05] [debug] ( javajni.c:941 ) [ 3112] Java Worker thread started it/test/procrun/Bootstrap:start
      [2015-03-24 18:19:06] [debug] ( prunsrv.c:1186) [ 3940] Java started it/test/procrun/Bootstrap
      [2015-03-24 18:19:06] [info] ( prunsrv.c:1284) [ 3940] Service started in 1131 ms.
      [2015-03-24 18:19:06] [debug] ( prunsrv.c:844 ) [ 3940] reportServiceStatusE: 4, 0, 0, 0
      [2015-03-24 18:19:06] [debug] ( prunsrv.c:1528) [ 3940] Waiting for worker to finish...
      [2015-03-24 18:20:10] [debug] ( javajni.c:964 ) [ 3112] Java Worker thread finished it/test/procrun/Bootstrap:start with status=0
      [2015-03-24 18:20:10] [debug] ( prunsrv.c:1533) [ 3940] Worker finished.
      [2015-03-24 18:20:10] [debug] ( prunsrv.c:1559) [ 3940] Waiting for all threads to exit

      I added in the example a sleep for initialize it took 60sec and it works.
      I believe that you have a environment problem. In my example into install.bat I add --StartMode=jvm --Jvm=auto this means that the procrun find the JVM from the Windows registry. (see http://commons.apache.org/proper/commons-daemon/procrun.html for more detail). Can you change --StartMode=jvm in --StartMode=java and add JAVA_HOME variable in your environment?

      Delete
    4. in the example I used procrun 32bit you need to jvm 32bit.

      Delete
  2. I get an error : Unable to find a single main class from the following candidates [org.test.Bootstrap, org.test.MyApplication]

    ReplyDelete
    Replies
    1. how you start the application as service? if yes Can you post commons-daemon.log?

      Delete
  3. Hello. Today classes has moved ot BOOT-INF directory in spring uber-jar, and procrun cannot find bootstrap ...

    ReplyDelete
  4. Well, spring-boot dont accepts two main classes, how do avoid this with your two, one in Bootstrap class and one in spring boot application class??

    ReplyDelete
  5. Your solution worked great for me up to Spring Boot version 1.3.
    Unfortunately it something has changed in Spring Boot 1.4.0 and there is a compilation error now. Have you tried updating this for SB 1.4.0?

    It is piece of very good code that saved my ass! Thanks!

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. I updated the git project (https://github.com/francesc79/test-procrun)

      Delete
  6. You are really saved my day, Buddy! Great article!

    ReplyDelete
  7. Hi, thank you for the article and my service starts nicely.
    I wonder how the stop works.
    It does does go into Application (@SpringBootApplication) but where does the applicationContext comes from? It's initialised as null so it will try to start another instance of my SpringApplication and it fails since port is in use. How did you resolve that?

    ReplyDelete
  8. [2017-01-20 11:12:10] [error] ( javajni.c:863 ) [ 8280] FindClass it/test/procrun/Bootstrap failed
    [2017-01-20 11:12:10] [debug] ( javajni.c:964 ) [ 8280] Java Worker thread finished it/test/procrun/Bootstrap:start with status=3
    [2017-01-20 11:12:10] [error] ( prunsrv.c:1183) [11564] Failed to start Java
    [2017-01-20 11:12:10] [error] ( prunsrv.c:1536) [11564] ServiceStart returned 4

    You can help me?

    ReplyDelete
    Replies
    1. Check if inside your jar exist it/test/procrun/Bootstrap.class if not you have build problem please post maven log.

      Delete
    2. Same error for me and it exists in the jar and no build error

      Delete
  9. Hello!
    I have try the example but I get some errors. On install all right but on run failed.

    > Install.bat log:

    xxxxxxxxxxxxxxx

    [2017-10-16 14:45:11] [debug] ( prunsrv.c:1679) [ 5736] Commons Daemon procrun log initialized
    [2017-10-16 14:45:11] [info] ( prunsrv.c:1683) [ 5736] Commons Daemon procrun (1.0.15.0 64-bit) started
    [2017-10-16 14:45:11] [debug] ( prunsrv.c:561 ) [ 5736] Installing service...
    [2017-10-16 14:45:11] [info] ( prunsrv.c:600 ) [ 5736] Service test-procrun name test-procrun
    [2017-10-16 14:45:11] [debug] ( prunsrv.c:616 ) [ 5736] Setting service description test-procrun
    [2017-10-16 14:45:11] [info] ( prunsrv.c:634 ) [ 5736] Service 'test-procrun' installed
    [2017-10-16 14:45:11] [info] ( prunsrv.c:1764) [ 5736] Commons Daemon procrun finished

    xxxxxxxxxxxxxxx

    > run.bat log:

    [2017-10-16 14:48:12] [debug] ( prunsrv.c:1679) [ 6468] Commons Daemon procrun log initialized
    [2017-10-16 14:48:12] [info] ( prunsrv.c:1683) [ 6468] Commons Daemon procrun (1.0.15.0 64-bit) started
    [2017-10-16 14:48:12] [info] ( prunsrv.c:725 ) [ 6468] Starting service 'test-procrun' ...
    [2017-10-16 14:48:13] [error] ( prunsrv.c:746 ) [ 6468] Failed to start 'test-procrun' service
    [2017-10-16 14:48:13] [error] ( prunsrv.c:746 ) [ 6468] Access denied.
    [2017-10-16 14:48:13] [info] ( prunsrv.c:754 ) [ 6468] Start service finished.
    [2017-10-16 14:48:13] [error] ( prunsrv.c:1755) [ 6468] Commons Daemon procrun failed with exit value: 5 (Failed to start service)
    [2017-10-16 14:48:13] [error] ( prunsrv.c:1755) [ 6468] Access denied.

    ReplyDelete
    Replies
    1. I try the //TS// command "Run the service as a console application" and the application run, see log:

      [2017-10-16 14:59:39] [debug] ( prunsrv.c:1679) [ 3980] Commons Daemon procrun log initialized
      [2017-10-16 14:59:39] [info] ( prunsrv.c:1683) [ 3980] Commons Daemon procrun (1.0.15.0 64-bit) started
      [2017-10-16 14:59:39] [info] ( prunsrv.c:1580) [ 3980] Debugging 'test-procrun' service...
      [2017-10-16 14:59:39] [debug] ( prunsrv.c:1374) [ 3980] Inside ServiceMain...
      [2017-10-16 14:59:39] [debug] ( prunsrv.c:844 ) [ 3980] reportServiceStatusE: 2, 0, 3000, 0
      [2017-10-16 14:59:39] [info] ( prunsrv.c:1127) [ 3980] Starting service...
      [2017-10-16 14:59:39] [debug] ( javajni.c:222 ) [ 3980] Invalid RuntimeLib 'C:\Java\jre1.8.0_74\bin\client\jvm.dll'
      [2017-10-16 14:59:39] [debug] ( javajni.c:224 ) [ 3980] Using Jre JavaHome 'C:\Java\jre1.8.0_74'
      [2017-10-16 14:59:39] [debug] ( javajni.c:233 ) [ 3980] loading jvm 'C:\Java\jre1.8.0_74\bin\server\jvm.dll'
      [2017-10-16 14:59:39] [debug] ( javajni.c:704 ) [ 5632] Jvm Option[0] -Djava.class.path=C:\tmp\master\test-procrun-0.0.2-SNAPSHOT.jar
      [2017-10-16 14:59:39] [debug] ( javajni.c:704 ) [ 5632] Jvm Option[1] exit
      [2017-10-16 14:59:39] [debug] ( javajni.c:888 ) [ 5632] argv[0] = start
      [2017-10-16 14:59:39] [debug] ( javajni.c:941 ) [ 5632] Java Worker thread started it/test/procrun/Bootstrap:start
      [2017-10-16 14:59:40] [debug] ( prunsrv.c:1186) [ 3980] Java started it/test/procrun/Bootstrap
      [2017-10-16 14:59:40] [info] ( prunsrv.c:1284) [ 3980] Service started in 1112 ms.
      [2017-10-16 14:59:40] [debug] ( prunsrv.c:844 ) [ 3980] reportServiceStatusE: 4, 0, 0, 0
      [2017-10-16 14:59:40] [debug] ( prunsrv.c:1528) [ 3980] Waiting for worker to finish...
      [2017-10-16 14:59:44] [debug] ( javajni.c:964 ) [ 5632] Java Worker thread finished it/test/procrun/Bootstrap:start with status=0
      [2017-10-16 14:59:44] [debug] ( prunsrv.c:1533) [ 3980] Worker finished.
      [2017-10-16 14:59:44] [debug] ( prunsrv.c:1559) [ 3980] Waiting for all threads to exit

      Delete
    2. [2017-10-16 14:48:13] [error] ( prunsrv.c:746 ) [ 6468] Access denied.

      I think you have privileges problems. Check windows events log if you have more information for find the real problem.

      Delete
    3. Hi, I fixed setting the procrun path to folder local:

      set PR_INSTALL%CD%

      thank you

      Delete
  10. I tried but getting error during service start .
    Windows could not start the MyService service on Local Computer.



    Error 1067: The process terminated unexpectedly.

    log:
    [2017-10-27 15:59:07] [debug] ( prunsrv.c:1374) [ 9680] Inside ServiceMain...
    [2017-10-27 15:59:07] [debug] ( prunsrv.c:844 ) [ 9680] reportServiceStatusE: 2, 0, 3000, 0
    [2017-10-27 15:59:07] [info] ( prunsrv.c:1127) [ 9680] Starting service...

    Please help

    ReplyDelete
  11. Can you try to uninstall reboot the pc and install the service again?

    ReplyDelete
  12. in the install.bat tray to change --StartMode and --StopMode from jvm to Java. Define in your system JAVA_HOME environment variable for more details see https://commons.apache.org/proper/commons-daemon/procrun.html

    ReplyDelete