Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ package org.apache.slider.server.services.workflow; import org.apache.hadoop.service.ServiceStateException; import org.apache.slider.core.main.ServiceLaunchException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; /** * Service wrapper for an external program that is launched and can/will terminate. * This service is notified when the subprocess terminates, and stops itself * and converts a non-zero exit code into a failure exception. * * <p> * Key Features: * <ol> * <li>The property {@link #executionTimeout} can be set to set a limit * on the duration of a process</li> * <li>Output is streamed to the output logger provided</li>. * <li>The most recent lines of output are saved to a linked list</li>. * <li>A synchronous callback, {@link LongLivedProcessLifecycleEvent}, is raised on the start * and finish of a process.</li> * </ol> * * Usage: * <p></p> * The service can be built in the constructor, {@link #ForkedProcessService(String, Map, List)}, * or have its simple constructor used to instantiate the service, then the * {@link #build(Map, List)} command used to define the environment variables * and list of commands to execute. One of these two options MUST be exercised * before calling the services's {@link #start()} method. * <p></p> * The forked process is executed in the service's {@link #serviceStart()} method; * if still running when the service is stopped, {@link #serviceStop()} will * attempt to stop it. * <p></p> * * The service delegates process execution to {@link LongLivedProcess}, * receiving callbacks via the {@link LongLivedProcessLifecycleEvent}. * When the service receives a callback notifying that the process has completed, * it calls its {@link #stop()} method. If the error code was non-zero, * the service is logged as having failed. */ public class ForkedProcessService extends WorkflowExecutorService<ExecutorService> implements LongLivedProcessLifecycleEvent, Runnable { /** * Log for the forked master process */ private static final Logger LOG = LoggerFactory.getLogger(ForkedProcessService.class); private final AtomicBoolean processTerminated = new AtomicBoolean(false); private boolean processStarted = false; private LongLivedProcess process; private int executionTimeout = -1; private int timeoutCode = 1; /** log to log to; defaults to this service log */ private Logger processLog = LOG; /** * Exit code set when the spawned process exits */ private AtomicInteger exitCode = new AtomicInteger(0); /** * Create an instance of the service * @param name a name */ public ForkedProcessService(String name) { super(name); } /** * Create an instance of the service, set up the process * @param name a name * @param commandList list of commands is inserted on the front * @param env environment variables above those generated by * @throws IOException IO problems */ public ForkedProcessService(String name, Map<String, String> env, List<String> commandList) throws IOException { super(name); build(env, commandList); } @Override //AbstractService protected void serviceStart() throws Exception { if (process == null) { throw new ServiceStateException("Process not yet configured"); } //now spawn the process -expect updates via callbacks process.start(); } @Override //AbstractService protected void serviceStop() throws Exception { completed(0); stopForkedProcess(); } private void stopForkedProcess() { if (process != null) { process.stop(); } } /** * Set the process log. This may be null for "do not log" * @param processLog process log */ public void setProcessLog(Logger processLog) { this.processLog = processLog; process.setProcessLog(processLog); } /** * Set the timeout by which time a process must have finished -or -1 for forever * @param timeout timeout in milliseconds */ public void setTimeout(int timeout, int code) { this.executionTimeout = timeout; this.timeoutCode = code; } /** * Build the process to execute when the service is started * @param commandList list of commands is inserted on the front * @param env environment variables above those generated by * @throws IOException IO problems */ public void build(Map<String, String> env, List<String> commandList) throws IOException { assert process == null; process = new LongLivedProcess(getName(), processLog, commandList); process.setLifecycleCallback(this); //set the env variable mapping process.putEnvMap(env); } @Override // notification from executed process public synchronized void onProcessStarted(LongLivedProcess process) { LOG.debug("Process has started"); processStarted = true; if (executionTimeout > 0) { setExecutor(ServiceThreadFactory.singleThreadExecutor(getName(), true)); execute(this); } } @Override // notification from executed process public void onProcessExited(LongLivedProcess process, int uncorrected, int code) { try { synchronized (this) { completed(code); //note whether or not the service had already stopped LOG.debug("Process has exited with exit code {}", code); if (code != 0) { reportFailure(code, getName() + " failed with code " + code); } } } finally { stop(); } } private void reportFailure(int code, String text) { //error ServiceLaunchException execEx = new ServiceLaunchException(code, text); LOG.debug("Noting failure", execEx); noteFailure(execEx); } /** * handle timeout response by escalating it to a failure */ @Override public void run() { try { synchronized (processTerminated) { if (!processTerminated.get()) { processTerminated.wait(executionTimeout); } } } catch (InterruptedException e) { //assume signalled; exit } //check the status; if the marker isn't true, bail if (!processTerminated.getAndSet(true)) { LOG.info("process timeout: reporting error code {}", timeoutCode); //timeout if (isInState(STATE.STARTED)) { //trigger a failure stopForkedProcess(); } reportFailure(timeoutCode, getName() + ": timeout after " + executionTimeout + " millis: exit code =" + timeoutCode); } } /** * Note the process as having completed. * The exit code is stored, the process marked as terminated * -and anything synchronized on <code>processTerminated</code> * is notified * @param code exit code */ protected void completed(int code) { processTerminated.set(true); synchronized (processTerminated) { processTerminated.notify(); } } public boolean isProcessTerminated() { return processTerminated.get(); } public synchronized boolean isProcessStarted() { return processStarted; } /** * Is a process running: between started and terminated * @return true if the process is up. */ public synchronized boolean isProcessRunning() { return processStarted && !isProcessTerminated(); } public Integer getExitCode() { return process.getExitCode(); } public int getExitCodeSignCorrected() { Integer exitCode = process.getExitCodeSignCorrected(); if (exitCode == null) return -1; return exitCode; } /** * Get the recent output from the process, or [] if not defined * @return a possibly empty list */ public List<String> getRecentOutput() { return process != null ? process.getRecentOutput() : new LinkedList<String>(); } /** * Get the recent output from the process, or [] if not defined * * @param finalOutput flag to indicate "wait for the final output of the process" * @param duration the duration, in ms, * ro wait for recent output to become non-empty * @return a possibly empty list */ public List<String> getRecentOutput(boolean finalOutput, int duration) { if (process == null) { return new LinkedList<String>(); } return process.getRecentOutput(finalOutput, duration); } }