Tuesday, May 31, 2011

How to exercise your Daemons - Callable, Future, and ExecutorService

Mac OS X logoImage via Wikipedia
In a previous post, I mentioned how to add a shutdown hook to your application. This is great for the case where you need to do some cleanup prior to exiting. The example below shows how to convert your class to work like a daemon on UNIX, or a service on Windows. I will not go into details on how to create a UNIX, or Windows service. I will provide a StartupItem example for Mac OS X, or a modern inetd plist for launchd.

In the example classes below, I create a value holder object, a Callable<ValueHolder>, a Future<ValueHolder>, an ExecutorService which uses a SingleThreadExecutor. The concurrency libraries are from java.util.concurrent which is based on the original work done by Doug Lea, et. al. These classes make doing Thread based programming much simpler.

Here are the relevant files for the application:
JavaDaemonExample.zip (NetBeans 7.0 project)
JavaDaemonStartupItem.zip (This is for OS X 10.4 or less. It will work on 10.5+, but is not preferred)
com.bluelotussoftware.examples.Daemon (This is the inetd plist for launchd 10.5+ preferred)

Before we get to the code, the Mac OS X daemon controls must be modified to match your environment.

The com.bluelotussoftware.examples.Daemon plist file needs to modified to point to the jar file to execute. This is for Leopard, and Snow Leopard.
  1. To install it, place it in the ~/Library/LaunchAgents directory.
  2. From a command prompt, type:
    launchctl load -w /Users/jyeary/Library/LaunchAgents/JavaDaemonStartupItem/com.bluelotussoftware.examples.Daemon
    This loads it.
  3. To execute from launchctl:
    root:$ launctl
    launchd% start com.bluelotussoftware.examples.Daemon
    launchd% list
    launchd% quit
    root:$

Alternatively, you can use the JavaDaemonStartupItem.
  1. Modify the JavaDaemonStartupItem script to match your environment.
  2. Place the files in the /Library/StartupItems directory.
  3. Change permissions:
    sudo chown -R root:wheel JavaDaemonStartupItem
  4. Execute the application using:
    sudo /sbin/SystemStarter start "Java Daemon Example"

This is the main code Example:

Daemon.java


/*
 * Copyright 2011 John Yeary <jyeary@bluelotussoftware.com>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: Daemon.java 355 2011-05-31 21:05:01Z jyeary $
 */
package com.bluelotussoftware.examples;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Main executable daemon class.
 * @author John Yeary <jyeary@bluelotussoftware.com>
 * @version 1.0
 */
public class Daemon {

    private static final Logger logger = Logger.getLogger(Daemon.class.getName());


    /**
     * Default no-argument constructor.
     */
    public Daemon() {

    }

    /**
     * Main execution entry point.
     * @param args the command line arguments
     */
    public static void main(String[] args) {

        try {
            /* Create a file handler which is used to log output. This
             * becomes vitally important since we close the System.out,
             * System.err, and System.in streams next.
             */
            Handler handler = new FileHandler(Daemon.class.getName() + ".log");
            logger.addHandler(handler);
        } catch (IOException ex) {
            ex.printStackTrace(System.err);
        } catch (SecurityException ex) {
            ex.printStackTrace(System.err);
        }

        logger.log(Level.INFO, "Closing streams");
        closeDefaultStreams();
        logger.log(Level.INFO, "Fetching ExecutorService");
        final ExecutorService executorService = Executors.newSingleThreadExecutor();
        logger.log(Level.INFO, "Adding Shutdown Hook");
        Runtime.getRuntime().addShutdownHook(new Thread() {

            @Override
            public void run() {
                shutdown(executorService);
            }
        });

        logger.log(Level.INFO, "Submitting CallableDaemon");
        Future<ValueHolder> future = executorService.submit(new CallableDaemon());
        logger.log(Level.INFO, "Waiting for results...");

        try {
            StringBuilder sb = new StringBuilder();
            sb.append("Results: ");
            sb.append(Arrays.toString(future.get().getDates().toArray()));
            logger.log(Level.INFO, sb.toString());
        } catch (InterruptedException ex) {
            logger.log(Level.SEVERE, null, ex);
        } catch (ExecutionException ex) {
            logger.log(Level.SEVERE, null, ex);
        }
        logger.log(Level.INFO, "Shutting down ExecutorService normally.");
        /* Call to shutdown executor service, otherwise the application blocks
         * and waits for another Runnable to be submitted. You can verify this in the
         * debugger, or by submitting another Runnable.
         */
        executorService.shutdown();
    }

    //TODO Need to verify that this is necessary for Windows systems.
    /**
     * This method closes {@code System#out}, {@code System#err}, and {@code System#in}
     * streams. This prevents premature shutdown from closure of terminal.
     */
    private static void closeDefaultStreams() {
        System.out.close();
        System.err.close();
        try {
            System.in.close();
        } catch (IOException ex) {
            logger.log(Level.WARNING, "Closing System.in() caused an Exception", ex);
        }
    }

    /**
     * Shutdown hook which calls {@code java.util.concurrent.ExecutorService#shutdownNow()}
     * @param executorService the executor which we want to attach to, and shutdown.
     */
    private static void shutdown(ExecutorService executorService) {
        logger.log(Level.INFO, "Executing shutdown hook");
        List<Runnable> runnables = executorService.shutdownNow();
        logger.log(Level.INFO, "The following items were awaiting execution when the daemon shutdown",
                Arrays.toString(runnables.toArray(new Runnable[runnables.size()])));
    }
}

This is the Callable<ValueHolder>:

CallableDaemon.java


/*
 * Copyright 2011 John Yeary <jyeary@bluelotussoftware.com>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: CallableDaemon.java 355 2011-05-31 21:05:01Z jyeary $
 */
package com.bluelotussoftware.examples;

import java.util.Date;
import java.util.concurrent.Callable;

/**
 * An example <code>Callable</code> implementation which executes and
 * waits. Every execution adds a <code>Date</code> to a <code>ValueHolder</code>.
 * On <code>call</code> completion, it returns the <code>ValueHolder</code>.
 * @author John Yeary <jyeary@bluelotussoftware.com>
 * @version 1.0
 */
public class CallableDaemon implements Callable<ValueHolder> {

    private int counter;
    private int iterations = 12;

    /**
     * Default no-argument constructor.
     */
    public CallableDaemon() {
    }

    /**
     * Constructor which sets the number of iterations.
     * @param iterations integral value which sets the number
     * of iterations to be executed.
     */
    public CallableDaemon(int iterations) {
        this.iterations = iterations;
    }

    /**
     * {@inheritDoc java.util.concurrent.Callable#call()}
     */
    @Override
    public ValueHolder call() throws Exception {
        ValueHolder valueHolder = new ValueHolder();
        for (; counter < iterations; counter++) {
            valueHolder.getDates().add(new Date());
            synchronized (this) {
                this.wait(5000);
            }
        }
        return valueHolder;
    }
}

ValueHolder


/*
 * Copyright 2011 John Yeary <jyeary@bluelotussoftware.com>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: ValueHolder.java 355 2011-05-31 21:05:01Z jyeary $
 */
package com.bluelotussoftware.examples;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * A value holder object.
 * @author John Yeary <jyeary@bluelotussoftware.com>
 */
public class ValueHolder {

    private List<Date> dates;

    /**
     * Default no-argument constructor.
     */
    public ValueHolder() {
        dates = new ArrayList<Date>();
    }

    /**
     * Returns a {@code List<Date>} objects contained in the <code>ValueHolder</code>.
     * The method is synchronized because it can be used to add values to the internal
     * <code>List</code>.
     * @return {@code List<Date>} objects contained in the <code>ValueHolder</code>.
     */
    public synchronized List<Date> getDates() {
        return dates;
    }
}


Enhanced by Zemanta

0 comments :

Popular Posts