sf.net.experimaestro.connectors.XPMProcess.java Source code

Java tutorial

Introduction

Here is the source code for sf.net.experimaestro.connectors.XPMProcess.java

Source

/*
 * This file is part of experimaestro.
 * Copyright (c) 2012 B. Piwowarski <benjamin@bpiwowar.net>
 *
 * experimaestro is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * experimaestro is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with experimaestro.  If not, see <http://www.gnu.org/licenses/>.
 */

package sf.net.experimaestro.connectors;

import com.sleepycat.je.DatabaseException;
import com.sleepycat.persist.model.Persistent;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;
import sf.net.experimaestro.locks.Lock;
import sf.net.experimaestro.scheduler.EndOfJobMessage;
import sf.net.experimaestro.scheduler.Job;
import sf.net.experimaestro.scheduler.Resource;
import sf.net.experimaestro.scheduler.Scheduler;
import sf.net.experimaestro.utils.log.Logger;

import java.io.*;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * A process monitor.
 * <p/>
 * <p>
 * A job monitor task is to... monitor the execution of a job, wherever
 * the job is running (remote, local, etc.).
 * It is returned when a job starts, contains information (ID, state, etc.) and control methods (stop)
 * </p>
 *
 * @author B. Piwowarski <benjamin@bpiwowar.net>
 * @date June 2012
 */
@Persistent
public abstract class XPMProcess {
    static private Logger LOGGER = Logger.getLogger();
    /**
     * The checker
     */
    protected transient ScheduledFuture<?> checker = null;
    /**
     * The job to notify when finished with this
     */
    protected transient Job job;
    /**
     * Our process ID
     */
    String pid;
    /**
     * The host where this process is running (or give an access to the process, e.g. for OAR processes)
     */
    transient SingleHostConnector connector;
    String connectorId;
    /**
     * The associated locks to release when the process has ended
     */
    private List<Lock> locks = null;

    /**
     * Creates a new job monitor from a process
     *
     * @param job The attached job (If any)
     * @param pid The process ID
     */
    protected XPMProcess(SingleHostConnector connector, String pid, final Job job) {
        this.connector = connector;
        this.pid = pid;
        this.job = job;
        this.connectorId = connector.getIdentifier();
    }

    /**
     * Constructs a XPMProcess without an underlying process
     */
    protected XPMProcess(SingleHostConnector connector, final Job job, String pid) {
        this(connector, pid, job);
    }

    /**
     * Used for serialization
     *
     * @see {@linkplain #init(sf.net.experimaestro.scheduler.Job)}
     */
    protected XPMProcess() {
    }

    /**
     * Set up a notifiction using {@linkplain java.lang.Process#waitFor()}.
     */

    protected void startWaitProcess() {
        LOGGER.debug("XPM Process %s constructed", connectorId);

        // Set up the notification thread if needed
        if (job != null) {
            new Thread(String.format("job monitor [%s]", job.getId())) {
                @Override
                public void run() {
                    int code;
                    while (true) {
                        try {
                            LOGGER.info("Waiting for job [%s] process to finish", job);
                            code = waitFor();
                            LOGGER.info("Job [%s] process has finished running [code %d]", job, code);
                            break;
                        } catch (InterruptedException e) {
                            LOGGER.error("Interrupted: %s", e);
                        }
                    }

                    try {
                        job.notify(new EndOfJobMessage(code, System.currentTimeMillis()));
                    } catch (RuntimeException e) {
                        LOGGER.warn(e, "Failed to notify end-of-job for %s", job);
                    }
                }
            }.start();
        }
    }

    /**
     * Initialization of the job monitor (when restoring from database)
     * <p/>
     * The method also sets up a status checker at regular intervals.
     */
    public void init(Job job) throws DatabaseException {
        this.job = job;
        final Scheduler scheduler = job.getScheduler();

        // TODO: use connector & job dependent times for checking
        checker = scheduler.schedule(this, 15, TimeUnit.SECONDS);

        connector = (SingleHostConnector) scheduler.getConnector(connectorId);

        // Init locks if needed
        for (Lock lock : locks)
            lock.init(scheduler);
    }

    @Override
    public String toString() {
        return String.format("[Process of %s]", job);
    }

    /**
     * Get the underlying job
     *
     * @return The job
     */
    public Job getJob() {
        return job;
    }

    public void setJob(Job job) {
        this.job = job;
    }

    /**
     * Adopt the locks
     */
    public void adopt(List<Lock> locks) {
        // Changing the ownership of the different logs
        this.locks = locks;
        for (Lock lock : locks) {
            try {
                lock.changeOwnership(pid);
            } catch (Throwable e) {
                LOGGER.error("Could not adopt lock %s", lock);
            }
        }
    }

    /**
     * Dispose of this job monitor
     */
    public void dispose() {
        close();
        if (locks != null) {
            LOGGER.info("Disposing of %d locks for %s", locks.size(), this);
            while (!locks.isEmpty()) {
                locks.get(locks.size() - 1).close();
                locks.remove(locks.size() - 1);
            }

            locks = null;
        }

    }

    /**
     * Close this job monitor
     */
    void close() {
        if (checker != null) {
            LOGGER.info("Cancelling the checker for %s", this);
            if (!checker.cancel(true)) {
                checker = null;
            } else
                LOGGER.warn("Could not cancel the checker");

        }
    }

    /**
     * Check if the job is running
     *
     * @return True if the job is running
     * @throws Exception
     */
    public boolean isRunning() throws Exception {
        // We have no process, check
        return job.getMainConnector().resolveFile(job.getLocator().getPath() + Job.LOCK_EXTENSION).exists();

    }

    /**
     * Returns the error code
     */
    public int exitValue() {
        // Check for done file
        try {
            if (job.getLocator().resolve(connector, Resource.DONE_EXTENSION).exists())
                return 0;

            // If the job is not done, check the ".code" file to get the error code
            // and otherwise return -1
            final FileObject codeFile = job.getMainConnector()
                    .resolveFile(job.getLocator().getPath() + Job.CODE_EXTENSION);
            if (codeFile.exists()) {
                final InputStream stream = codeFile.getContent().getInputStream();
                final BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
                String s = reader.readLine();
                int code = s != null ? Integer.parseInt(s) : -1;
                reader.close();
                return code;
            }
        } catch (IOException e) {
            throw new IllegalThreadStateException();
        }

        // The process failed, but we do not know how
        return -1;

    }

    /**
     * Add a lock to release after this job has completed
     *
     * @param lock
     */
    public void addLock(Lock lock) {
        locks.add(lock);
    }

    /**
     * Asynchronous check the state of the job monitor
     */
    public void check() throws Exception {
        if (!isRunning()) {
            // We are not running: send a message
            LOGGER.debug("End of job [%s]", job);
            final FileObject file = job.getLocator().resolve(connector, Resource.CODE_EXTENSION);
            final long time = file.exists() ? file.getContent().getLastModifiedTime() : -1;
            job.notify(new EndOfJobMessage(exitValue(), time));
            dispose();
        }
    }

    /**
     * @see {@linkplain Process#getOutputStream()}
     */
    abstract public OutputStream getOutputStream();

    /**
     * @see {@linkplain Process#getErrorStream()}
     */
    abstract public InputStream getErrorStream();

    /**
     * @see {@linkplain Process#getInputStream()}
     */
    abstract public InputStream getInputStream();

    /**
     * Periodic checks
     *
     * @see {@linkplain Process#waitFor()}
     */
    public int waitFor() throws InterruptedException {
        synchronized (this) {
            // Wait 1 second
            while (true) {
                try {
                    try {
                        if (!isRunning()) {
                            return exitValue();
                        }
                    } catch (Exception e) {
                        // TODO: what to do here?
                        // Delay?
                        LOGGER.warn("Error while checking if running");
                    }
                    wait(1000);

                } catch (InterruptedException e) {

                }
            }
        }
    }

    /**
     * @see {@linkplain Process#destroy()}
     */
    abstract public void destroy() throws FileSystemException;

    public String getPID() {
        return pid;
    }
}