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.hama.bsp; import static java.util.concurrent.TimeUnit.SECONDS; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.io.IOUtils; import org.apache.hadoop.util.RunJar; /** * Base class that runs a task in a separate process. */ public class TaskRunner extends Thread { public static final Log LOG = LogFactory.getLog(TaskRunner.class); private static final String SYSTEM_PATH_SEPARATOR = System.getProperty("path.separator"); private enum LogType { STDOUT, ERROR, CONSOLE } boolean bspKilled = false; private Process bspProcess; private Thread errorLog; private Thread infoLog; private final Task task; private final BSPJob bspJob; private final GroomServer groomServer; private File logDir; class BspChildRunner implements Callable<Object> { private final List<String> commands; private final File workDir; private final ScheduledExecutorService sched; private final AtomicReference<ScheduledFuture<Object>> future; BspChildRunner(List<String> commands, File workDir) { this.commands = commands; this.workDir = workDir; this.sched = Executors.newScheduledThreadPool(1); this.future = new AtomicReference<ScheduledFuture<Object>>(); } void start() { this.future.set(this.sched.schedule(this, 0, SECONDS)); LOG.debug("Start building BSPPeer process."); } void stop() { killBsp(); this.sched.schedule(this, 0, SECONDS); LOG.info("Stop BSPPeer process."); } void join() throws InterruptedException, ExecutionException { this.future.get().get(); } @Override public Object call() throws Exception { final boolean consoleRedirect = bspJob.getConfiguration().getBoolean("hama.child.redirect.log.console", false); ProcessBuilder builder = new ProcessBuilder(commands); builder.directory(workDir); try { bspProcess = builder.start(); errorLog = new Thread() { @Override public void run() { logStream(bspProcess.getErrorStream(), consoleRedirect ? LogType.CONSOLE : LogType.ERROR); } }; errorLog.start(); infoLog = new Thread() { @Override public void run() { logStream(bspProcess.getInputStream(), consoleRedirect ? LogType.CONSOLE : LogType.STDOUT); } }; infoLog.start(); int exit_code = bspProcess.waitFor(); if (!bspKilled && exit_code != 0) { throw new IOException("BSP task process exit with nonzero status of " + exit_code + ". command = " + commands); } } catch (InterruptedException e) { LOG.warn("Thread is interrupted when execeuting BSP process.", e); } catch (IOException ioe) { LOG.error("Error when executing BSPPeer process.", ioe); } finally { killBsp(); } return null; } } public TaskRunner(BSPTask bspTask, GroomServer groom, BSPJob conf) { this.task = bspTask; this.bspJob = conf; this.groomServer = groom; } public Task getTask() { return task; } /** * Called to assemble this task's input. This method is run in the parent * process before the child is spawned. It should not execute user code, only * system code. */ public boolean prepare() throws IOException { return true; } private File createWorkDirectory() { File workDir = new File(new File(task.getJobFile()).getParent(), "work"); boolean isCreated = workDir.mkdirs(); if (isCreated) { LOG.debug("TaskRunner.workDir : " + workDir); } return workDir; } private static String assembleClasspath(BSPJob jobConf, File workDir) { StringBuffer classPath = new StringBuffer(); // start with same classpath as parent process classPath.append(System.getProperty("java.class.path")); classPath.append(SYSTEM_PATH_SEPARATOR); String jar = jobConf.getJar(); if (jar != null) { // if jar exists, it into workDir try { RunJar.unJar(new File(jar), workDir); } catch (IOException ioe) { LOG.error("Unable to uncompressing file to " + workDir.toString(), ioe); } File[] libs = new File(workDir, "lib").listFiles(); if (libs != null) { for (File lib : libs) { // add libs from jar to classpath classPath.append(SYSTEM_PATH_SEPARATOR); classPath.append(lib); } } classPath.append(SYSTEM_PATH_SEPARATOR); classPath.append(new File(workDir, "classes")); classPath.append(SYSTEM_PATH_SEPARATOR); classPath.append(workDir); } return classPath.toString(); } private List<String> buildJvmArgs(BSPJob jobConf, String classPath, Class<?> child) { // Build exec child jmv args. List<String> vargs = new ArrayList<String>(); File jvm = // use same jvm as parent new File(new File(System.getProperty("java.home"), "bin"), "java"); vargs.add(jvm.toString()); // bsp.child.java.opts String javaOpts = jobConf.getConfiguration().get("bsp.child.java.opts", "-Xmx200m"); javaOpts = javaOpts.replace("@taskid@", task.getTaskID().toString()); String[] javaOptsSplit = javaOpts.split(" "); Collections.addAll(vargs, javaOptsSplit); // Add classpath. vargs.add("-classpath"); vargs.add(classPath); // Add main class and its arguments LOG.debug("Executing child Process " + child.getName()); vargs.add(child.getName()); // bsp class name if (GroomServer.BSPPeerChild.class.equals(child)) { InetSocketAddress addr = groomServer.getTaskTrackerReportAddress(); vargs.add(addr.getHostName()); vargs.add(Integer.toString(addr.getPort())); vargs.add(task.getTaskID().toString()); vargs.add(groomServer.groomHostName); vargs.add(Long.toString(groomServer.getStartSuperstep(task.getTaskID()))); TaskStatus status = groomServer.getTaskStatus(task.getTaskID()); if (status != null && TaskStatus.State.RECOVERING.equals(status.getRunState())) { vargs.add(TaskStatus.State.RECOVERING.name()); } else { vargs.add(TaskStatus.State.RUNNING.name()); } } return vargs; } /** * Build working environment and launch BSPPeer processes. */ @Override public void run() { File workDir = createWorkDirectory(); logDir = createLogDirectory(); String classPath = assembleClasspath(bspJob, workDir); LOG.debug("Spawned child's classpath " + classPath); List<String> bspArgs = buildJvmArgs(bspJob, classPath, GroomServer.BSPPeerChild.class); BspChildRunner bspPeer = new BspChildRunner(bspArgs, workDir); bspPeer.start(); try { bspPeer.join(); } catch (InterruptedException ie) { LOG.error("BSPPeer child process is interrupted.", ie); } catch (ExecutionException ee) { LOG.error("Failure occurs when retrieving tasks result.", ee); } finally { killBsp(); } LOG.debug("Finishes executing BSPPeer child process."); } /** * Creates the tasks log directory if needed. * * @return the top directory of the tasks logging area. */ private File createLogDirectory() { // our log dir looks following: log/tasklogs/job_id/ File f = new File(System.getProperty("hama.log.dir") + File.separator + "tasklogs" + File.separator + task.jobId.toString()); // TODO if we have attemps: + File.separator+ task.getTaskID()); if (!f.exists()) { f.mkdirs(); } return f; } /** * Kill bspPeer child process. */ public void killBsp() { bspKilled = true; if (errorLog != null || infoLog != null) { errorLog = null; infoLog = null; } if (bspProcess != null) { bspProcess.destroy(); } } /** * Log process's stream. * * @param input stream to be logged. * @param stdout type of the log */ private void logStream(InputStream input, LogType type) { if (type == LogType.CONSOLE) { try { IOUtils.copyBytes(input, System.out, bspJob.getConfiguration()); } catch (IOException e) { // gracefully ignore any occuring exceptions here } return; } // STDOUT file can be found under LOG_DIR/task_attempt_id.log // ERROR file can be found under LOG_DIR/task_attempt_id.err File taskLogFile = new File(logDir, task.getTaskAttemptId() + getFileEndingForType(type)); BufferedWriter writer = null; try { writer = new BufferedWriter(new FileWriter(taskLogFile)); BufferedReader in = new BufferedReader(new InputStreamReader(input)); String line; while ((line = in.readLine()) != null) { writer.write(line); writer.newLine(); } } catch (IOException e) { if (!bspKilled) { LOG.warn(task.getTaskID() + " Error reading child output", e); } } finally { try { input.close(); } catch (IOException e) { LOG.warn(task.getTaskID() + " Error closing child output", e); } try { writer.close(); } catch (IOException e) { LOG.warn(task.getTaskID() + " Error closing log file", e); } } } /** * Returns the ending of the logfile for each LogType. e.G. ".log". * * @param type * @return an ending, including a dot. */ private static String getFileEndingForType(LogType type) { if (type != LogType.ERROR) return ".err"; else return ".log"; } }