You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by in...@apache.org on 2017/05/02 22:05:35 UTC

[18/50] [abbrv] hadoop git commit: YARN-679. Add an entry point that can start any Yarn service. Contributed by Steve Loughran.

YARN-679. Add an entry point that can start any Yarn service. Contributed by Steve Loughran.


Project: http://git-wip-us.apache.org/repos/asf/hadoop/repo
Commit: http://git-wip-us.apache.org/repos/asf/hadoop/commit/373bb493
Tree: http://git-wip-us.apache.org/repos/asf/hadoop/tree/373bb493
Diff: http://git-wip-us.apache.org/repos/asf/hadoop/diff/373bb493

Branch: refs/heads/HDFS-10467
Commit: 373bb4931fb392e3ca6bfd78992887e5a405e186
Parents: cb672a4
Author: Junping Du <ju...@apache.org>
Authored: Fri Apr 28 10:45:02 2017 -0700
Committer: Junping Du <ju...@apache.org>
Committed: Fri Apr 28 10:45:02 2017 -0700

----------------------------------------------------------------------
 .../hadoop/service/ServiceStateException.java   |   66 +-
 .../launcher/AbstractLaunchableService.java     |   78 ++
 .../HadoopUncaughtExceptionHandler.java         |  129 +++
 .../service/launcher/InterruptEscalator.java    |  216 ++++
 .../hadoop/service/launcher/IrqHandler.java     |  178 +++
 .../service/launcher/LaunchableService.java     |   95 ++
 .../service/launcher/LauncherArguments.java     |   59 +
 .../service/launcher/LauncherExitCodes.java     |  183 +++
 .../launcher/ServiceLaunchException.java        |   81 ++
 .../service/launcher/ServiceLauncher.java       | 1044 ++++++++++++++++++
 .../service/launcher/ServiceShutdownHook.java   |  112 ++
 .../hadoop/service/launcher/package-info.java   |  462 ++++++++
 .../apache/hadoop/util/ExitCodeProvider.java    |   35 +
 .../java/org/apache/hadoop/util/ExitUtil.java   |  253 ++++-
 .../hadoop/util/GenericOptionsParser.java       |  125 ++-
 .../org/apache/hadoop/util/StringUtils.java     |   43 +-
 .../apache/hadoop/service/BreakableService.java |   23 +-
 .../AbstractServiceLauncherTestBase.java        |  317 ++++++
 .../launcher/ExitTrackingServiceLauncher.java   |   59 +
 .../service/launcher/TestServiceConf.java       |  146 +++
 .../launcher/TestServiceInterruptHandling.java  |  118 ++
 .../service/launcher/TestServiceLauncher.java   |  213 ++++
 .../TestServiceLauncherCreationFailures.java    |   83 ++
 .../TestServiceLauncherInnerMethods.java        |   95 ++
 .../ExceptionInExecuteLaunchableService.java    |   96 ++
 .../testservices/FailInConstructorService.java  |   33 +
 .../testservices/FailInInitService.java         |   38 +
 .../testservices/FailInStartService.java        |   37 +
 .../testservices/FailingStopInStartService.java |   47 +
 .../testservices/FailureTestService.java        |   55 +
 .../InitInConstructorLaunchableService.java     |   63 ++
 .../testservices/LaunchableRunningService.java  |  111 ++
 .../testservices/NoArgsAllowedService.java      |   64 ++
 .../testservices/NullBindLaunchableService.java |   46 +
 .../launcher/testservices/RunningService.java   |   84 ++
 .../StoppingInStartLaunchableService.java       |   49 +
 .../StringConstructorOnlyService.java           |   39 +
 .../apache/hadoop/test/GenericTestUtils.java    |   34 +
 38 files changed, 4861 insertions(+), 148 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/ServiceStateException.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/ServiceStateException.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/ServiceStateException.java
index 0ac1821..ba4e0d2 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/ServiceStateException.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/ServiceStateException.java
@@ -20,37 +20,83 @@ package org.apache.hadoop.service;
 
 import org.apache.hadoop.classification.InterfaceAudience.Public;
 import org.apache.hadoop.classification.InterfaceStability.Evolving;
+import org.apache.hadoop.service.launcher.LauncherExitCodes;
+import org.apache.hadoop.util.ExitCodeProvider;
 
 /**
- * Exception that is raised on state change operations.
+ * Exception that can be raised on state change operations, whose
+ * exit code can be explicitly set, determined from that of any nested
+ * cause, or a default value of
+ * {@link  LauncherExitCodes#EXIT_SERVICE_LIFECYCLE_EXCEPTION}.
  */
 @Public
 @Evolving
-public class ServiceStateException extends RuntimeException {
+public class ServiceStateException extends RuntimeException implements
+    ExitCodeProvider {
 
   private static final long serialVersionUID = 1110000352259232646L;
 
+  /**
+   * Exit code.
+   */
+  private int exitCode ;
+
+  /**
+   * Instantiate
+   * @param message error message
+   */
   public ServiceStateException(String message) {
-    super(message);
+    this(message, null);
   }
 
+  /**
+   * Instantiate with a message and cause; if the cause has an exit code
+   * then it is used, otherwise the generic
+   * {@link LauncherExitCodes#EXIT_SERVICE_LIFECYCLE_EXCEPTION} exit code
+   * is used.
+   * @param message exception message
+   * @param cause optional inner cause
+   */
   public ServiceStateException(String message, Throwable cause) {
     super(message, cause);
+    if(cause instanceof ExitCodeProvider) {
+      this.exitCode = ((ExitCodeProvider) cause).getExitCode();
+    } else {
+      this.exitCode = LauncherExitCodes.EXIT_SERVICE_LIFECYCLE_EXCEPTION;
+    }
+  }
+
+  /**
+   * Instantiate, using the specified exit code as the exit code
+   * of the exception, irrespetive of any exit code supplied by any inner
+   * cause.
+   *
+   * @param exitCode exit code to declare
+   * @param message exception message
+   * @param cause inner cause
+   */
+  public ServiceStateException(int exitCode,
+      String message,
+      Throwable cause) {
+    this(message, cause);
+    this.exitCode =  exitCode;
   }
 
   public ServiceStateException(Throwable cause) {
     super(cause);
   }
 
+  @Override
+  public int getExitCode() {
+    return exitCode;
+  }
+
   /**
    * Convert any exception into a {@link RuntimeException}.
-   * If the caught exception is already of that type, it is typecast to a
-   * {@link RuntimeException} and returned.
-   *
    * All other exception types are wrapped in a new instance of
-   * ServiceStateException
+   * {@code ServiceStateException}.
    * @param fault exception or throwable
-   * @return a ServiceStateException to rethrow
+   * @return a {@link RuntimeException} to rethrow
    */
   public static RuntimeException convert(Throwable fault) {
     if (fault instanceof RuntimeException) {
@@ -66,10 +112,10 @@ public class ServiceStateException extends RuntimeException {
    * {@link RuntimeException} and returned.
    *
    * All other exception types are wrapped in a new instance of
-   * ServiceStateException
+   * {@code ServiceStateException}.
    * @param text text to use if a new exception is created
    * @param fault exception or throwable
-   * @return a ServiceStateException to rethrow
+   * @return a {@link RuntimeException} to rethrow
    */
   public static RuntimeException convert(String text, Throwable fault) {
     if (fault instanceof RuntimeException) {

http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/AbstractLaunchableService.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/AbstractLaunchableService.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/AbstractLaunchableService.java
new file mode 100644
index 0000000..be28c5b
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/AbstractLaunchableService.java
@@ -0,0 +1,78 @@
+/*
+ * 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.hadoop.service.launcher;
+
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.service.AbstractService;
+
+/**
+ * Subclass of {@link AbstractService} that provides basic implementations
+ * of the {@link LaunchableService} methods.
+ */
+@InterfaceAudience.Public
+@InterfaceStability.Evolving
+public abstract class AbstractLaunchableService extends AbstractService
+    implements LaunchableService {
+
+  private static final Logger LOG =
+      LoggerFactory.getLogger(AbstractLaunchableService.class);
+
+  /**
+   * Construct an instance with the given name.
+   */
+  protected AbstractLaunchableService(String name) {
+    super(name);
+  }
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * The base implementation logs all arguments at the debug level,
+   * then returns the passed in config unchanged.
+   */
+  
+  @Override
+  public Configuration bindArgs(Configuration config, List<String> args) throws
+      Exception {
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Service {} passed in {} arguments:", getName(), args.size());
+      for (String arg : args) {
+        LOG.debug(arg);
+      }
+    }
+    return config;
+  }
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * The action is to signal success by returning the exit code 0.
+   */
+  @Override
+  public int execute() throws Exception {
+    return LauncherExitCodes.EXIT_SUCCESS;
+  }
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/HadoopUncaughtExceptionHandler.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/HadoopUncaughtExceptionHandler.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/HadoopUncaughtExceptionHandler.java
new file mode 100644
index 0000000..bf4a863
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/HadoopUncaughtExceptionHandler.java
@@ -0,0 +1,129 @@
+/*
+ * 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.hadoop.service.launcher;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.hadoop.classification.InterfaceAudience.Public;
+import org.apache.hadoop.classification.InterfaceStability.Evolving;
+import org.apache.hadoop.util.ExitUtil;
+import org.apache.hadoop.util.ShutdownHookManager;
+
+/**
+ * This class is intended to be installed by calling 
+ * {@link Thread#setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)}
+ * in the main entry point. 
+ *
+ * The base class will always attempt to shut down the process if an Error
+ * was raised; the behavior on a standard Exception, raised outside 
+ * process shutdown, is simply to log it. 
+ *
+ * (Based on the class {@code YarnUncaughtExceptionHandler})
+ */
+@SuppressWarnings("UseOfSystemOutOrSystemErr")
+@Public
+@Evolving
+public class HadoopUncaughtExceptionHandler
+    implements UncaughtExceptionHandler {
+
+  /**
+   * Logger.
+   */
+  private static final Logger LOG = LoggerFactory.getLogger(
+      HadoopUncaughtExceptionHandler.class);
+
+  /**
+   * Delegate for simple exceptions.
+   */
+  private final UncaughtExceptionHandler delegate;
+
+  /**
+   * Create an instance delegating to the supplied handler if
+   * the exception is considered "simple".
+   * @param delegate a delegate exception handler.
+   */
+  public HadoopUncaughtExceptionHandler(UncaughtExceptionHandler delegate) {
+    this.delegate = delegate;
+  }
+
+  /**
+   * Basic exception handler -logs simple exceptions, then continues.
+   */
+  public HadoopUncaughtExceptionHandler() {
+    this(null);
+  }
+
+  /**
+   * Uncaught exception handler.
+   * If an error is raised: shutdown
+   * The state of the system is unknown at this point -attempting
+   * a clean shutdown is dangerous. Instead: exit
+   * @param thread thread that failed
+   * @param exception the raised exception
+   */
+  @Override
+  public void uncaughtException(Thread thread, Throwable exception) {
+    if (ShutdownHookManager.get().isShutdownInProgress()) {
+      LOG.error("Thread {} threw an error during shutdown: {}.",
+          thread.toString(),
+          exception,
+          exception);
+    } else if (exception instanceof Error) {
+      try {
+        LOG.error("Thread {} threw an error: {}. Shutting down",
+            thread.toString(),
+            exception,
+            exception);
+      } catch (Throwable err) {
+        // We don't want to not exit because of an issue with logging
+      }
+      if (exception instanceof OutOfMemoryError) {
+        // After catching an OOM java says it is undefined behavior, so don't
+        // even try to clean up or we can get stuck on shutdown.
+        try {
+          System.err.println("Halting due to Out Of Memory Error...");
+        } catch (Throwable err) {
+          // Again we don't want to exit because of logging issues.
+        }
+        ExitUtil.haltOnOutOfMemory((OutOfMemoryError) exception);
+      } else {
+        // error other than OutOfMemory
+        ExitUtil.ExitException ee =
+            ServiceLauncher.convertToExitException(exception);
+        ExitUtil.terminate(ee.status, ee);
+      }
+    } else {
+      // simple exception in a thread. There's a policy decision here:
+      // terminate the process vs. keep going after a thread has failed
+      // base implementation: do nothing but log
+      LOG.error("Thread {} threw an exception: {}",
+          thread.toString(),
+          exception,
+          exception);
+      if (delegate != null) {
+        delegate.uncaughtException(thread, exception);
+      }
+    }
+
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/InterruptEscalator.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/InterruptEscalator.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/InterruptEscalator.java
new file mode 100644
index 0000000..a7e1edd
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/InterruptEscalator.java
@@ -0,0 +1,216 @@
+/*
+ * 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.hadoop.service.launcher;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.google.common.base.Preconditions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.service.Service;
+import org.apache.hadoop.util.ExitUtil;
+
+import static org.apache.hadoop.service.launcher.LauncherExitCodes.EXIT_INTERRUPTED;
+
+/**
+ * Handles interrupts by shutting down a service, escalating if the service
+ * does not shut down in time, or when other interrupts are received.
+ * <ol>
+ *   <li>The service is given a time in milliseconds to stop:
+ *   if it exceeds this it the process exits anyway.</li>
+ *   <li>the exit operation used is {@link ServiceLauncher#exit(int, String)}
+ *   with the exit code {@link LauncherExitCodes#EXIT_INTERRUPTED}</li>
+ *   <li>If a second shutdown signal is received during the shutdown
+ *   process, {@link ExitUtil#halt(int)} is invoked. This handles the 
+ *   problem of blocking shutdown hooks.</li>
+ * </ol>
+ *
+ */
+
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class InterruptEscalator implements IrqHandler.Interrupted {
+  private static final Logger LOG = LoggerFactory.getLogger(
+      InterruptEscalator.class);
+
+  /**
+   * Flag to indicate when a shutdown signal has already been received.
+   * This allows the operation to be escalated.
+   */
+  private final AtomicBoolean signalAlreadyReceived = new AtomicBoolean(false);
+
+  private final WeakReference<ServiceLauncher> ownerRef;
+
+  private final int shutdownTimeMillis;
+
+  /**
+   * Previous interrupt handlers. These are not queried.
+   */
+  private final List<IrqHandler> interruptHandlers = new ArrayList<>(2);
+  private boolean forcedShutdownTimedOut;
+
+  public InterruptEscalator(ServiceLauncher owner, int shutdownTimeMillis) {
+    Preconditions.checkArgument(owner != null, "null owner");
+    this.ownerRef = new WeakReference<>(owner);
+    this.shutdownTimeMillis = shutdownTimeMillis;
+  }
+
+  private ServiceLauncher getOwner() {
+    return ownerRef.get();
+  }
+
+  private Service getService() {
+    ServiceLauncher owner = getOwner();
+    return owner != null ? owner.getService() : null;
+  }
+
+  @Override
+  public String toString() {
+    final StringBuilder sb = new StringBuilder("InterruptEscalator{");
+    sb.append(" signalAlreadyReceived=").append(signalAlreadyReceived.get());
+    ServiceLauncher owner = ownerRef.get();
+    if (owner != null) {
+      sb.append(", owner= ").append(owner.toString());
+    }
+    sb.append(", shutdownTimeMillis=").append(shutdownTimeMillis);
+    sb.append(", forcedShutdownTimedOut=").append(forcedShutdownTimedOut);
+    sb.append('}');
+    return sb.toString();
+  }
+
+  @Override
+  public void interrupted(IrqHandler.InterruptData interruptData) {
+    String message = "Service interrupted by " + interruptData.toString();
+    LOG.warn(message);
+    if (!signalAlreadyReceived.compareAndSet(false, true)) {
+      message = "Repeated interrupt: escalating to a JVM halt";
+      LOG.warn(message);
+      // signal already received. On a second request to a hard JVM
+      // halt and so bypass any blocking shutdown hooks.
+      ExitUtil.halt(LauncherExitCodes.EXIT_INTERRUPTED, message);
+    }
+    Service service = getService();
+    if (service != null) {
+      //start an async shutdown thread with a timeout
+      ServiceForcedShutdown shutdown =
+          new ServiceForcedShutdown(service, shutdownTimeMillis);
+      Thread thread = new Thread(shutdown);
+      thread.setDaemon(true);
+      thread.setName("Service Forced Shutdown");
+      thread.start();
+      //wait for that thread to finish
+      try {
+        thread.join(shutdownTimeMillis);
+      } catch (InterruptedException ignored) {
+        //ignored
+      }
+      forcedShutdownTimedOut = !shutdown.getServiceWasShutdown();
+      if (forcedShutdownTimedOut) {
+        LOG.warn("Service did not shut down in time");
+      }
+    }
+    ExitUtil.terminate(EXIT_INTERRUPTED, message);
+  }
+
+  /**
+   * Register an interrupt handler.
+   * @param signalName signal name
+   * @throws IllegalArgumentException if the registration failed
+   */
+  public synchronized void register(String signalName) {
+    IrqHandler handler = new IrqHandler(signalName, this);
+    handler.bind();
+    interruptHandlers.add(handler);
+  }
+
+  /**
+   * Look up the handler for a signal.
+   * @param signalName signal name
+   * @return a handler if found
+   */
+  public synchronized IrqHandler lookup(String signalName) {
+    for (IrqHandler irqHandler : interruptHandlers) {
+      if (irqHandler.getName().equals(signalName)) {
+        return irqHandler;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Flag set if forced shut down timed out.
+   * @return true if a shutdown was attempted and it timed out
+   */
+  public boolean isForcedShutdownTimedOut() {
+    return forcedShutdownTimedOut;
+  }
+
+  /**
+   * Flag set if a signal has been received.
+   * @return true if there has been one interrupt already.
+   */
+  public boolean isSignalAlreadyReceived() {
+    return signalAlreadyReceived.get();
+  }
+
+  /**
+   * Forced shutdown runnable.
+   */
+  protected static class ServiceForcedShutdown implements Runnable {
+
+    private final int shutdownTimeMillis;
+    private final AtomicBoolean serviceWasShutdown =
+        new AtomicBoolean(false);
+    private Service service;
+
+    public ServiceForcedShutdown(Service service, int shutdownTimeMillis) {
+      this.shutdownTimeMillis = shutdownTimeMillis;
+      this.service = service;
+    }
+
+    /**
+     * Shutdown callback: stop the service and set an atomic boolean
+     * if it stopped within the shutdown time.
+     */
+    @Override
+    public void run() {
+      if (service != null) {
+        service.stop();
+        serviceWasShutdown.set(
+            service.waitForServiceToStop(shutdownTimeMillis));
+      } else {
+        serviceWasShutdown.set(true);
+      }
+    }
+
+    /**
+     * Probe for the service being shutdown.
+     * @return true if the service has been shutdown in the runnable
+     */
+    private boolean getServiceWasShutdown() {
+      return serviceWasShutdown.get();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/IrqHandler.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/IrqHandler.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/IrqHandler.java
new file mode 100644
index 0000000..30bb91c
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/IrqHandler.java
@@ -0,0 +1,178 @@
+/*
+ * 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.hadoop.service.launcher;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.google.common.base.Preconditions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sun.misc.Signal;
+import sun.misc.SignalHandler;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+/**
+ * Handler of interrupts that relays them to a registered
+ * implementation of {@link IrqHandler.Interrupted}.
+ *
+ * This class bundles up all the compiler warnings about abuse of sun.misc
+ * interrupt handling code into one place.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+@SuppressWarnings("UseOfSunClasses")
+public final class IrqHandler implements SignalHandler {
+  private static final Logger LOG = LoggerFactory.getLogger(IrqHandler.class);
+  
+  /**
+   * Definition of the Control-C handler name: {@value}.
+   */
+  public static final String CONTROL_C = "INT";
+
+  /**
+   * Definition of default <code>kill</code> signal: {@value}.
+   */
+  public static final String SIGTERM = "TERM";
+
+  /**
+   * Signal name.
+   */
+  private final String name;
+
+  /**
+   * Handler to relay to.
+   */
+  private final Interrupted handler;
+
+  /** Count of how many times a signal has been raised. */
+  private final AtomicInteger signalCount = new AtomicInteger(0);
+
+  /**
+   * Stored signal.
+   */
+  private Signal signal;
+
+  /**
+   * Create an IRQ handler bound to the specific interrupt.
+   * @param name signal name
+   * @param handler handler
+   */
+  public IrqHandler(String name, Interrupted handler) {
+    Preconditions.checkArgument(name != null, "Null \"name\"");
+    Preconditions.checkArgument(handler != null, "Null \"handler\"");
+    this.handler = handler;
+    this.name = name;
+  }
+
+  /**
+   * Bind to the interrupt handler.
+   * @throws IllegalArgumentException if the exception could not be set
+   */
+  void bind() {
+    Preconditions.checkState(signal == null, "Handler already bound");
+    try {
+      signal = new Signal(name);
+      Signal.handle(signal, this);
+    } catch (IllegalArgumentException e) {
+      throw new IllegalArgumentException(
+          "Could not set handler for signal \"" + name + "\"."
+          + "This can happen if the JVM has the -Xrs set.",
+          e);
+    }
+  }
+
+  /**
+   * @return the signal name.
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * Raise the signal.
+   */
+  public void raise() {
+    Signal.raise(signal);
+  }
+
+  @Override
+  public String toString() {
+    return "IrqHandler for signal " + name;
+  }
+
+  /**
+   * Handler for the JVM API for signal handling.
+   * @param s signal raised
+   */
+  @Override
+  public void handle(Signal s) {
+    signalCount.incrementAndGet();
+    InterruptData data = new InterruptData(s.getName(), s.getNumber());
+    LOG.info("Interrupted: {}", data);
+    handler.interrupted(data);
+  }
+
+  /**
+   * Get the count of how many times a signal has been raised.
+   * @return the count of signals
+   */
+  public int getSignalCount() {
+    return signalCount.get();
+  }
+  
+  /**
+   * Callback issues on an interrupt.
+   */
+  public interface Interrupted {
+
+    /**
+     * Handle an interrupt.
+     * @param interruptData data
+     */
+    void interrupted(InterruptData interruptData);
+  }
+
+  /**
+   * Interrupt data to pass on.
+   */
+  public static class InterruptData {
+    private final String name;
+    private final int number;
+
+    public InterruptData(String name, int number) {
+      this.name = name;
+      this.number = number;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    public int getNumber() {
+      return number;
+    }
+
+    @Override
+    public String toString() {
+      return "signal " + name + '(' + number + ')';
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LaunchableService.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LaunchableService.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LaunchableService.java
new file mode 100644
index 0000000..fb0a052
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LaunchableService.java
@@ -0,0 +1,95 @@
+/*
+ * 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.hadoop.service.launcher;
+
+import java.util.List;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.service.Service;
+
+/**
+ * An interface which services can implement to have their
+ * execution managed by the ServiceLauncher.
+ * <p>
+ * The command line options will be passed down before the 
+ * {@link Service#init(Configuration)} operation is invoked via an
+ * invocation of {@link LaunchableService#bindArgs(Configuration, List)}
+ * After the service has been successfully started via {@link Service#start()}
+ * the {@link LaunchableService#execute()} method is called to execute the 
+ * service. When this method returns, the service launcher will exit, using
+ * the return code from the method as its exit option.
+ */
+@InterfaceAudience.Public
+@InterfaceStability.Evolving
+public interface LaunchableService extends Service {
+
+  /**
+   * Propagate the command line arguments.
+   * <p>
+   * This method is called before {@link #init(Configuration)};
+   * Any non-null configuration that is returned from this operation
+   * becomes the one that is passed on to that {@link #init(Configuration)}
+   * operation.
+   * <p>
+   * This permits implementations to change the configuration before
+   * the init operation. As the ServiceLauncher only creates
+   * an instance of the base {@link Configuration} class, it is
+   * recommended to instantiate any subclass (such as YarnConfiguration)
+   * that injects new resources.
+   * <p>
+   * @param config the initial configuration build up by the
+   * service launcher.
+   * @param args list of arguments passed to the command line
+   * after any launcher-specific commands have been stripped.
+   * @return the configuration to init the service with.
+   * Recommended: pass down the config parameter with any changes
+   * @throws Exception any problem
+   */
+  Configuration bindArgs(Configuration config, List<String> args)
+        throws Exception;
+
+  /**
+   * Run a service. This method is called after {@link Service#start()}.
+   * <p>
+   * The return value becomes the exit code of the launched process.
+   * <p>
+   * If an exception is raised, the policy is:
+   * <ol>
+   *   <li>Any subset of {@link org.apache.hadoop.util.ExitUtil.ExitException}:
+   *   the exception is passed up unmodified.
+   *   </li>
+   *   <li>Any exception which implements
+   *   {@link org.apache.hadoop.util.ExitCodeProvider}:
+   *   A new {@link ServiceLaunchException} is created with the exit code
+   *   and message of the thrown exception; the thrown exception becomes the
+   *   cause.</li>
+   *   <li>Any other exception: a new {@link ServiceLaunchException} is created
+   *   with the exit code {@link LauncherExitCodes#EXIT_EXCEPTION_THROWN} and
+   *   the message of the original exception (which becomes the cause).</li>
+   * </ol>
+   * @return the exit code
+   * @throws org.apache.hadoop.util.ExitUtil.ExitException an exception passed
+   *  up as the exit code and error text.
+   * @throws Exception any exception to report. If it provides an exit code
+   * this is used in a wrapping exception.
+   */
+  int execute() throws Exception;
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LauncherArguments.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LauncherArguments.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LauncherArguments.java
new file mode 100644
index 0000000..08118f5
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LauncherArguments.java
@@ -0,0 +1,59 @@
+/*
+ * 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.hadoop.service.launcher;
+
+/**
+ * Standard launcher arguments. These are all from
+ * the {@code GenericOptionsParser}, simply extracted to constants.
+ */
+public interface LauncherArguments {
+  /**
+   * Name of the configuration argument on the CLI.
+   * Value: {@value}
+   */
+  String ARG_CONF = "conf";
+  String ARG_CONF_SHORT = "conf";
+
+  /**
+   * prefixed version of {@link #ARG_CONF}.
+   * Value: {@value}
+   */
+  String ARG_CONF_PREFIXED = "--" + ARG_CONF;
+
+  /**
+   * Name of a configuration class which is loaded before any
+   * attempt is made to load the class.
+   * <p>
+   * Value: {@value}
+   */
+  String ARG_CONFCLASS = "hadoopconf";
+  String ARG_CONFCLASS_SHORT = "hadoopconf";
+
+  /**
+   * Prefixed version of {@link #ARG_CONFCLASS}.
+   * Value: {@value}
+   */
+  String ARG_CONFCLASS_PREFIXED = "--" + ARG_CONFCLASS;
+
+  /**
+   * Error string on a parse failure.
+   * Value: {@value}
+   */
+  String E_PARSE_FAILED = "Failed to parse: ";
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LauncherExitCodes.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LauncherExitCodes.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LauncherExitCodes.java
new file mode 100644
index 0000000..f48e38e
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LauncherExitCodes.java
@@ -0,0 +1,183 @@
+/*
+ * 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.hadoop.service.launcher;
+
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+/**
+ * Common Exit codes.
+ * <p>
+ * Codes with a YARN prefix are YARN-related.
+ * <p>
+ * Many of the exit codes are designed to resemble HTTP error codes,
+ * squashed into a single byte. e.g 44 , "not found" is the equivalent
+ * of 404. The various 2XX HTTP error codes aren't followed;
+ * the Unix standard of "0" for success is used.
+ * <pre>
+ *    0-10: general command issues
+ *   30-39: equivalent to the 3XX responses, where those responses are
+ *          considered errors by the application.
+ *   40-49: client-side/CLI/config problems
+ *   50-59: service-side problems.
+ *   60+  : application specific error codes
+ * </pre>
+ */
+@InterfaceAudience.Public
+@InterfaceStability.Evolving
+public interface LauncherExitCodes {
+
+  /**
+   * Success: {@value}.
+   */
+  int EXIT_SUCCESS = 0;
+
+  /**
+   * Generic "false/fail" response: {@value}.
+   * The operation worked but the result was not "true" from the viewpoint
+   * of the executed code.
+   */
+  int EXIT_FAIL = -1;
+
+  /**
+   * Exit code when a client requested service termination: {@value}.
+   */
+  int EXIT_CLIENT_INITIATED_SHUTDOWN = 1;
+
+  /**
+   * Exit code when targets could not be launched: {@value}.
+   */
+  int EXIT_TASK_LAUNCH_FAILURE = 2;
+
+  /**
+   * Exit code when a control-C, kill -3, signal was picked up: {@value}.
+   */
+  int EXIT_INTERRUPTED = 3;
+
+  /**
+   * Exit code when something happened but we can't be specific: {@value}.
+   */
+  int EXIT_OTHER_FAILURE = 5;
+
+  /**
+   * Exit code when the command line doesn't parse: {@value}, or
+   * when it is otherwise invalid.
+   * <p>
+   * Approximate HTTP equivalent: {@code 400 Bad Request}
+   */
+  int EXIT_COMMAND_ARGUMENT_ERROR = 40;
+
+  /**
+   * The request requires user authentication: {@value}.
+   * <p>
+   * approximate HTTP equivalent: Approximate HTTP equivalent: {@code 401 Unauthorized}
+   */
+  int EXIT_UNAUTHORIZED = 41;
+
+  /**
+   * Exit code when a usage message was printed: {@value}.
+   */
+  int EXIT_USAGE = 42;
+
+  /**
+   * Forbidden action: {@value}.
+   * <p>
+   * Approximate HTTP equivalent: Approximate HTTP equivalent: {@code 403: Forbidden}
+   */
+  int EXIT_FORBIDDEN = 43;
+
+  /**
+   * Something was not found: {@value}.
+   * <p>
+   * Approximate HTTP equivalent: {@code 404: Not Found}
+   */
+  int EXIT_NOT_FOUND = 44;
+
+  /**
+   * The operation is not allowed: {@value}.
+   * <p>
+   * Approximate HTTP equivalent: {@code 405: Not allowed}
+   */
+  int EXIT_OPERATION_NOT_ALLOWED = 45;
+
+  /**
+   * The command is somehow not acceptable: {@value}.
+   * <p>
+   * Approximate HTTP equivalent: {@code 406: Not Acceptable}
+   */
+  int EXIT_NOT_ACCEPTABLE = 46;
+
+  /**
+   * Exit code on connectivity problems: {@value}.
+   * <p>
+   * Approximate HTTP equivalent: {@code 408: Request Timeout}
+   */
+  int EXIT_CONNECTIVITY_PROBLEM = 48;
+
+  /**
+   * Exit code when the configurations in valid/incomplete: {@value}.
+   * <p>
+   * Approximate HTTP equivalent: {@code 409: Conflict}
+   */
+  int EXIT_BAD_CONFIGURATION = 49;
+
+  /**
+   * Exit code when an exception was thrown from the service: {@value}.
+   * <p>
+   * Approximate HTTP equivalent: {@code 500 Internal Server Error}
+   */
+  int EXIT_EXCEPTION_THROWN = 50;
+
+  /**
+   * Unimplemented feature: {@value}.
+   * <p>
+   * Approximate HTTP equivalent: {@code 501: Not Implemented}
+   */
+  int EXIT_UNIMPLEMENTED = 51;
+
+  /**
+   * Service Unavailable; it may be available later: {@value}.
+   * <p>
+   * Approximate HTTP equivalent: {@code 503 Service Unavailable}
+   */
+  int EXIT_SERVICE_UNAVAILABLE = 53;
+
+  /**
+   * The application does not support, or refuses to support this
+   * version: {@value}.
+   * <p>
+   * If raised, this is expected to be raised server-side and likely due
+   * to client/server version incompatibilities.
+   * <p>
+   * Approximate HTTP equivalent: {@code 505: Version Not Supported}
+   */
+  int EXIT_UNSUPPORTED_VERSION = 55;
+
+  /**
+   * The service instance could not be created: {@value}.
+   */
+  int EXIT_SERVICE_CREATION_FAILURE = 56;
+ 
+  /**
+   * The service instance could not be created: {@value}.
+   */
+  int EXIT_SERVICE_LIFECYCLE_EXCEPTION = 57;
+  
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceLaunchException.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceLaunchException.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceLaunchException.java
new file mode 100644
index 0000000..1243a1f
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceLaunchException.java
@@ -0,0 +1,81 @@
+/*
+ * 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.hadoop.service.launcher;
+
+
+import java.util.Locale;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.util.ExitCodeProvider;
+import org.apache.hadoop.util.ExitUtil;
+
+/**
+ * A service launch exception that includes an exit code.
+ * <p>
+ * When caught by the ServiceLauncher, it will convert that
+ * into a process exit code.
+ * 
+ * The {@link #ServiceLaunchException(int, String, Object...)} constructor
+ * generates formatted exceptions.
+ */
+@InterfaceAudience.Public
+@InterfaceStability.Evolving
+
+public class ServiceLaunchException extends ExitUtil.ExitException
+    implements ExitCodeProvider, LauncherExitCodes {
+
+  /**
+   * Create an exception with the specific exit code.
+   * @param exitCode exit code
+   * @param cause cause of the exception
+   */
+  public ServiceLaunchException(int exitCode, Throwable cause) {
+    super(exitCode, cause);
+  }
+
+  /**
+   * Create an exception with the specific exit code and text.
+   * @param exitCode exit code
+   * @param message message to use in exception
+   */
+  public ServiceLaunchException(int exitCode, String message) {
+    super(exitCode, message);
+  }
+
+  /**
+   * Create a formatted exception.
+   * <p>
+   * This uses {@link String#format(String, Object...)}
+   * to build the formatted exception in the ENGLISH locale.
+   * <p>
+   * If the last argument is a throwable, it becomes the cause of the exception.
+   * It will also be used as a parameter for the format.
+   * @param exitCode exit code
+   * @param format format for message to use in exception
+   * @param args list of arguments
+   */
+  public ServiceLaunchException(int exitCode, String format, Object... args) {
+    super(exitCode, String.format(Locale.ENGLISH, format, args));
+    if (args.length > 0 && (args[args.length - 1] instanceof Throwable)) {
+      initCause((Throwable) args[args.length - 1]);
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceLauncher.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceLauncher.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceLauncher.java
new file mode 100644
index 0000000..6b0b4e8
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceLauncher.java
@@ -0,0 +1,1044 @@
+/*
+ * 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.hadoop.service.launcher;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.Options;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.net.NetUtils;
+import org.apache.hadoop.service.Service;
+import org.apache.hadoop.util.ExitCodeProvider;
+import org.apache.hadoop.util.ExitUtil;
+import org.apache.hadoop.util.GenericOptionsParser;
+import org.apache.hadoop.util.StringUtils;
+
+/**
+ * A class to launch any YARN service by name.
+ *
+ * It's designed to be subclassed for custom entry points.
+ *
+ * Workflow:
+ * <ol>
+ *   <li>An instance of the class is created. It must be of the type
+ *   {@link Service}</li>
+ *   <li>If it implements
+ *   {@link LaunchableService#bindArgs(Configuration, List)},
+ *    it is given the binding args off the CLI after all general configuration
+ *    arguments have been stripped.</li>
+ *   <li>Its {@link Service#init(Configuration)} and {@link Service#start()}
+ *   methods are called.</li>
+ *   <li>If it implements it, {@link LaunchableService#execute()}
+ *   is called and its return code used as the exit code.</li>
+ *   <li>Otherwise: it waits for the service to stop, assuming that the
+ *   {@link Service#start()} method spawns one or more thread
+ *   to perform work</li>
+ *   <li>If any exception is raised and provides an exit code,
+ *   that is, it implements {@link ExitCodeProvider},
+ *   the return value of {@link ExitCodeProvider#getExitCode()},
+ *   becomes the exit code of the command.</li>
+ * </ol>
+ * Error and warning messages are logged to {@code stderr}.
+ * 
+ * @param <S> service class to cast the generated service to.
+ */
+@SuppressWarnings("UseOfSystemOutOrSystemErr")
+public class ServiceLauncher<S extends Service>
+    implements LauncherExitCodes, LauncherArguments,
+    Thread.UncaughtExceptionHandler {
+
+  /**
+   * Logger.
+   */
+  private static final Logger LOG =
+      LoggerFactory.getLogger(ServiceLauncher.class);
+
+  /**
+   * Priority for the shutdown hook: {@value}.
+   */
+  protected static final int SHUTDOWN_PRIORITY = 30;
+
+  /**
+   * The name of this class.
+   */
+  public static final String NAME = "ServiceLauncher";
+
+  protected static final String USAGE_NAME = "Usage: " + NAME;
+  protected static final String USAGE_SERVICE_ARGUMENTS =
+      "service-classname <service arguments>";
+
+  /**
+   * Usage message.
+   *
+   * Text: {@value}
+   */
+  public static final String USAGE_MESSAGE =
+      USAGE_NAME
+          + " [" + ARG_CONF_PREFIXED + " <conf file>]"
+          + " [" + ARG_CONFCLASS_PREFIXED + " <configuration classname>]"
+          + " "  + USAGE_SERVICE_ARGUMENTS;
+
+  /**
+   * The shutdown time on an interrupt: {@value}.
+   */
+  private static final int SHUTDOWN_TIME_ON_INTERRUPT = 30 * 1000;
+
+  /**
+   * The launched service.
+   *
+   * Invalid until the service has been created. 
+   */
+  private volatile S service;
+  
+  /**
+   * Exit code of the service.
+   *
+   * Invalid until a service has
+   * executed or stopped, depending on the service type.
+   */
+  private int serviceExitCode;
+  
+  /**
+   * Any exception raised during execution.
+   */
+  private ExitUtil.ExitException serviceException;
+
+  /**
+   * The interrupt escalator for the service.
+   */
+  private InterruptEscalator interruptEscalator;
+
+  /**
+   * Configuration used for the service.
+   */
+  private Configuration configuration;
+
+  /**
+   * Text description of service for messages.
+   */
+  private String serviceName;
+
+  /**
+   * Classname for the service to create.; empty string otherwise.
+   */
+  private String serviceClassName = "";
+
+  /**
+   * List of the standard configurations to create (and so load in properties).
+   * The values are Hadoop, HDFS and YARN configurations.
+   */
+  protected static final String[] DEFAULT_CONFIGS = {
+      "org.apache.hadoop.conf.Configuration",
+      "org.apache.hadoop.hdfs.HdfsConfiguration",
+      "org.apache.hadoop.yarn.conf.YarnConfiguration"
+  };
+
+  /**
+   * List of classnames to load to configuration before creating a
+   * {@link Configuration} instance.
+   */
+  private List<String> confClassnames = new ArrayList<>(DEFAULT_CONFIGS.length);
+
+  /**
+   * URLs of configurations to load into the configuration instance created.
+   */
+  private List<URL> confResourceUrls = new ArrayList<>(1);
+
+  /** Command options. Preserved for usage statements. */
+  private Options commandOptions;
+
+  /**
+   * Create an instance of the launcher.
+   * @param serviceClassName classname of the service
+   */
+  public ServiceLauncher(String serviceClassName) {
+    this(serviceClassName, serviceClassName);
+  }
+
+  /**
+   * Create an instance of the launcher.
+   * @param serviceName name of service for text messages
+   * @param serviceClassName classname of the service
+   */
+  public ServiceLauncher(String serviceName, String serviceClassName) {
+    this.serviceClassName = serviceClassName;
+    this.serviceName = serviceName;
+    // set up initial list of configurations
+    confClassnames.addAll(Arrays.asList(DEFAULT_CONFIGS));
+  }
+
+  /**
+   * Get the service.
+   *
+   * Null until
+   * {@link #coreServiceLaunch(Configuration, List, boolean, boolean)}
+   * has completed.
+   * @return the service
+   */
+  public final S getService() {
+    return service;
+  }
+
+  /**
+   * Setter is to give subclasses the ability to manipulate the service.
+   * @param s the new service
+   */
+  protected void setService(S s) {
+    this.service = s;
+  }
+
+  /**
+   * Get the configuration constructed from the command line arguments.
+   * @return the configuration used to create the service
+   */
+  public final Configuration getConfiguration() {
+    return configuration;
+  }
+
+  /**
+   * The exit code from a successful service execution.
+   * @return the exit code. 
+   */
+  public final int getServiceExitCode() {
+    return serviceExitCode;
+  }
+
+  /**
+   * Get the exit exception used to end this service.
+   * @return an exception, which will be null until the service
+   * has exited (and {@code System.exit} has not been called)
+   */
+  public final ExitUtil.ExitException getServiceException() {
+    return serviceException;
+  }
+
+  /**
+   * Probe for service classname being defined.
+   * @return true if the classname is set
+   */
+  private boolean isClassnameDefined() {
+    return serviceClassName != null && !serviceClassName.isEmpty();
+  }
+
+  @Override
+  public String toString() {
+    final StringBuilder sb = new StringBuilder("\"ServiceLauncher for \"");
+    sb.append(serviceName);
+    if (isClassnameDefined()) {
+      sb.append(", serviceClassName='").append(serviceClassName).append('\'');
+    }
+    if (service != null) {
+      sb.append(", service=").append(service);
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Launch the service and exit.
+   *
+   * <ol>
+   * <li>Parse the command line.</li> 
+   * <li>Build the service configuration from it.</li>
+   * <li>Start the service.</li>.
+   * <li>If it is a {@link LaunchableService}: execute it</li>
+   * <li>Otherwise: wait for it to finish.</li>
+   * <li>Exit passing the status code to the {@link #exit(int, String)}
+   * method.</li>
+   * </ol>
+   * @param args arguments to the service. {@code arg[0]} is 
+   * assumed to be the service classname.
+   */
+  public void launchServiceAndExit(List<String> args) {
+    StringBuilder builder = new StringBuilder();
+    for (String arg : args) {
+      builder.append('"').append(arg).append("\" ");
+    }
+    String argumentString = builder.toString();
+    if (LOG.isDebugEnabled()) {
+      LOG.debug(startupShutdownMessage(serviceName, args));
+      LOG.debug(argumentString);
+    }
+    registerFailureHandling();
+    // set up the configs, using reflection to push in the -site.xml files
+    loadConfigurationClasses();
+    Configuration conf = createConfiguration();
+    for (URL resourceUrl : confResourceUrls) {
+      conf.addResource(resourceUrl);
+    }
+    bindCommandOptions();
+    ExitUtil.ExitException exitException;
+    try {
+      List<String> processedArgs = extractCommandOptions(conf, args);
+      exitException = launchService(conf, processedArgs, true, true);
+    } catch (ExitUtil.ExitException e) {
+      exitException = e;
+      noteException(exitException);
+    }
+    if (exitException.getExitCode() != 0) {
+      // something went wrong. Print the usage and commands
+      System.err.println(getUsageMessage());
+      System.err.println("Command: " + argumentString);
+    }
+    System.out.flush();
+    System.err.flush();
+    exit(exitException);
+  }
+
+  /**
+   * Set the {@link #commandOptions} field to the result of
+   * {@link #createOptions()}; protected for subclasses and test access.
+   */
+  protected void bindCommandOptions() {
+    commandOptions = createOptions();
+  }
+
+  /**
+   * Record that an Exit Exception has been raised.
+   * Save it to {@link #serviceException}, with its exit code in
+   * {@link #serviceExitCode}
+   * @param exitException exception
+   */
+  void noteException(ExitUtil.ExitException exitException) {
+    LOG.debug("Exception raised", exitException);
+    serviceExitCode = exitException.getExitCode();
+    serviceException = exitException;
+  }
+
+  /**
+   * Get the usage message, ideally dynamically.
+   * @return the usage message
+   */
+  protected String getUsageMessage() {
+    String message =   USAGE_MESSAGE;
+    if (commandOptions != null) {
+      message = USAGE_NAME
+          + " " + commandOptions.toString()
+          + " " + USAGE_SERVICE_ARGUMENTS;
+    }
+    return message;
+  }
+
+  /**
+   * Override point: create an options instance to combine with the 
+   * standard options set.
+   * <i>Important. Synchronize uses of {@link OptionBuilder}</i>
+   * with {@code OptionBuilder.class}
+   * @return the new options
+   */
+  @SuppressWarnings("static-access")
+  protected Options createOptions() {
+    synchronized (OptionBuilder.class) {
+      Options options = new Options();
+      Option oconf = OptionBuilder.withArgName("configuration file")
+          .hasArg()
+          .withDescription("specify an application configuration file")
+          .withLongOpt(ARG_CONF)
+          .create(ARG_CONF_SHORT);
+      Option confclass = OptionBuilder.withArgName("configuration classname")
+          .hasArg()
+          .withDescription(
+              "Classname of a Hadoop Configuration subclass to load")
+          .withLongOpt(ARG_CONFCLASS)
+          .create(ARG_CONFCLASS_SHORT);
+      Option property = OptionBuilder.withArgName("property=value")
+          .hasArg()
+          .withDescription("use value for given property")
+          .create('D');
+      options.addOption(oconf);
+      options.addOption(property);
+      options.addOption(confclass);
+      return options;
+    }
+  }
+
+  /**
+   * Override point: create the base configuration for the service.
+   *
+   * Subclasses can override to create HDFS/YARN configurations etc.
+   * @return the configuration to use as the service initializer.
+   */
+  protected Configuration createConfiguration() {
+    return new Configuration();
+  }
+
+  /**
+   * Override point: Get a list of configuration classes to create.
+   * @return the array of configs to attempt to create. If any are off the
+   * classpath, that is logged
+   */
+  @SuppressWarnings("ReturnOfCollectionOrArrayField")
+  protected List<String> getConfigurationsToCreate() {
+    return confClassnames;
+  }
+
+  /**
+   * This creates all the configurations defined by
+   * {@link #getConfigurationsToCreate()} , ensuring that
+   * the resources have been pushed in.
+   * If one cannot be loaded it is logged and the operation continues
+   * except in the case that the class does load but it isn't actually
+   * a subclass of {@link Configuration}.
+   * @throws ExitUtil.ExitException if a loaded class is of the wrong type
+   */
+  @VisibleForTesting
+  public int loadConfigurationClasses() {
+    List<String> toCreate = getConfigurationsToCreate();
+    int loaded = 0;
+    for (String classname : toCreate) {
+      try {
+        Class<?> loadClass = getClassLoader().loadClass(classname);
+        Object instance = loadClass.getConstructor().newInstance();
+        if (!(instance instanceof Configuration)) {
+          throw new ExitUtil.ExitException(EXIT_SERVICE_CREATION_FAILURE,
+              "Could not create " + classname
+              + " because it is not a Configuration class/subclass");
+        }
+        loaded++;
+      } catch (ClassNotFoundException e) {
+        // class could not be found -implies it is not on the current classpath
+        LOG.debug("Failed to load {} because it is not on the classpath",
+            classname);
+      } catch (ExitUtil.ExitException e) {
+        // rethrow
+        throw e;
+      } catch (Exception e) {
+        // any other exception
+        LOG.info("Failed to create {}", classname, e);
+      }
+    }
+    return loaded;
+  }
+
+  /**
+   * Launch a service catching all exceptions and downgrading them to exit codes
+   * after logging.
+   *
+   * Sets {@link #serviceException} to this value.
+   * @param conf configuration to use
+   * @param processedArgs command line after the launcher-specific arguments
+   * have been stripped out.
+   * @param addShutdownHook should a shutdown hook be added to terminate
+   * this service on shutdown. Tests should set this to false.
+   * @param execute execute/wait for the service to stop.
+   * @return an exit exception, which will have a status code of 0 if it worked
+   */
+  @VisibleForTesting
+  public ExitUtil.ExitException launchService(Configuration conf,
+      List<String> processedArgs,
+      boolean addShutdownHook,
+      boolean execute) {
+    
+    ExitUtil.ExitException exitException;
+    
+    try {
+      int exitCode = coreServiceLaunch(conf, processedArgs, addShutdownHook,
+          execute);
+      if (service != null) {
+        // check to see if the service failed
+        Throwable failure = service.getFailureCause();
+        if (failure != null) {
+          // the service exited with a failure.
+          // check what state it is in
+          Service.STATE failureState = service.getFailureState();
+          if (failureState == Service.STATE.STOPPED) {
+            // the failure occurred during shutdown, not important enough
+            // to bother the user as it may just scare them
+            LOG.debug("Failure during shutdown: {} ", failure, failure);
+          } else {
+            //throw it for the catch handlers to deal with
+            throw failure;
+          }
+        }
+      }
+      String name = getServiceName();
+
+      if (exitCode == 0) {
+        exitException = new ServiceLaunchException(exitCode,
+            "%s succeeded",
+            name);
+      } else {
+        exitException = new ServiceLaunchException(exitCode,
+            "%s failed ", name);
+      }
+      // either the service succeeded, or an error raised during shutdown, 
+      // which we don't worry that much about
+    } catch (ExitUtil.ExitException ee) {
+      // exit exceptions are passed through unchanged
+      exitException = ee;
+    } catch (Throwable thrown) {
+      exitException = convertToExitException(thrown);
+    }
+    noteException(exitException);
+    return exitException;
+  }
+
+  /**
+   * Launch the service.
+   *
+   * All exceptions that occur are propagated upwards.
+   *
+   * If the method returns a status code, it means that it got as far starting
+   * the service, and if it implements {@link LaunchableService}, that the 
+   * method {@link LaunchableService#execute()} has completed. 
+   *
+   * After this method returns, the service can be retrieved returned by
+   * {@link #getService()}.
+   *
+   * @param conf configuration
+   * @param processedArgs arguments after the configuration parameters
+   * have been stripped out.
+   * @param addShutdownHook should a shutdown hook be added to terminate
+   * this service on shutdown. Tests should set this to false.
+   * @param execute execute/wait for the service to stop
+   * @throws ClassNotFoundException classname not on the classpath
+   * @throws IllegalAccessException not allowed at the class
+   * @throws InstantiationException not allowed to instantiate it
+   * @throws InterruptedException thread interrupted
+   * @throws ExitUtil.ExitException any exception defining the status code.
+   * @throws Exception any other failure -if it implements
+   * {@link ExitCodeProvider} then it defines the exit code for any
+   * containing exception
+   */
+
+  protected int coreServiceLaunch(Configuration conf,
+      List<String> processedArgs,
+      boolean addShutdownHook,
+      boolean execute) throws Exception {
+
+    // create the service instance
+    instantiateService(conf);
+    ServiceShutdownHook shutdownHook = null;
+
+    // and the shutdown hook if requested
+    if (addShutdownHook) {
+      shutdownHook = new ServiceShutdownHook(service);
+      shutdownHook.register(SHUTDOWN_PRIORITY);
+    }
+    String name = getServiceName();
+    LOG.debug("Launched service {}", name);
+    LaunchableService launchableService = null;
+
+    if (service instanceof LaunchableService) {
+      // it's a LaunchableService, pass in the conf and arguments before init)
+      LOG.debug("Service {} implements LaunchableService", name);
+      launchableService = (LaunchableService) service;
+      if (launchableService.isInState(Service.STATE.INITED)) {
+        LOG.warn("LaunchableService {}" 
+            + " initialized in constructor before CLI arguments passed in",
+            name);
+      }
+      Configuration newconf = launchableService.bindArgs(configuration,
+          processedArgs);
+      if (newconf != null) {
+        configuration = newconf;
+      }
+    }
+
+    //some class constructors init; here this is picked up on.
+    if (!service.isInState(Service.STATE.INITED)) {
+      service.init(configuration);
+    }
+    int exitCode;
+
+    try {
+      // start the service
+      service.start();
+      exitCode = EXIT_SUCCESS;
+      if (execute && service.isInState(Service.STATE.STARTED)) {
+        if (launchableService != null) {
+          // assume that runnable services are meant to run from here
+          try {
+            exitCode = launchableService.execute();
+            LOG.debug("Service {} execution returned exit code {}",
+                name, exitCode);
+          } finally {
+            // then stop the service
+            service.stop();
+          }
+        } else {
+          //run the service until it stops or an interrupt happens
+          // on a different thread.
+          LOG.debug("waiting for service threads to terminate");
+          service.waitForServiceToStop(0);
+        }
+      }
+    } finally {
+      if (shutdownHook != null) {
+        shutdownHook.unregister();
+      }
+    }
+    return exitCode;
+  }
+
+  /**
+   * Instantiate the service defined in {@code serviceClassName}.
+   *
+   * Sets the {@code configuration} field
+   * to the the value of {@code conf},
+   * and the {@code service} field to the service created.
+   *
+   * @param conf configuration to use
+   */
+  @SuppressWarnings("unchecked")
+  public Service instantiateService(Configuration conf) {
+    Preconditions.checkArgument(conf != null, "null conf");
+    Preconditions.checkArgument(serviceClassName != null,
+        "null service classname");
+    Preconditions.checkArgument(!serviceClassName.isEmpty(),
+        "undefined service classname");
+    configuration = conf;
+
+    // Instantiate the class. this requires the service to have a public
+    // zero-argument or string-argument constructor
+    Object instance;
+    try {
+      Class<?> serviceClass = getClassLoader().loadClass(serviceClassName);
+      try {
+        instance = serviceClass.getConstructor().newInstance();
+      } catch (NoSuchMethodException noEmptyConstructor) {
+        // no simple constructor, fall back to a string
+        LOG.debug("No empty constructor {}", noEmptyConstructor,
+            noEmptyConstructor);
+        instance = serviceClass.getConstructor(String.class)
+                               .newInstance(serviceClassName);
+      }
+    } catch (Exception e) {
+      throw serviceCreationFailure(e);
+    }
+    if (!(instance instanceof Service)) {
+      //not a service
+      throw new ServiceLaunchException(
+          LauncherExitCodes.EXIT_SERVICE_CREATION_FAILURE,
+          "Not a service class: \"%s\"", serviceClassName);
+    }
+
+    // cast to the specific instance type of this ServiceLauncher
+    service = (S) instance;
+    return service;
+  }
+
+  /**
+   * Convert an exception to an {@code ExitException}.
+   *
+   * This process may just be a simple pass through, otherwise a new
+   * exception is created with an exit code, the text of the supplied
+   * exception, and the supplied exception as an inner cause.
+   * 
+   * <ol>
+   *   <li>If is already the right type, pass it through.</li>
+   *   <li>If it implements {@link ExitCodeProvider#getExitCode()},
+   *   the exit code is extracted and used in the new exception.</li>
+   *   <li>Otherwise, the exit code
+   *   {@link LauncherExitCodes#EXIT_EXCEPTION_THROWN} is used.</li>
+   * </ol>
+   *  
+   * @param thrown the exception thrown
+   * @return an {@code ExitException} with a status code
+   */
+  protected static ExitUtil.ExitException convertToExitException(
+      Throwable thrown) {
+    ExitUtil.ExitException exitException;
+    // get the exception message
+    String message = thrown.toString();
+    int exitCode;
+    if (thrown instanceof ExitCodeProvider) {
+      // the exception provides a status code -extract it
+      exitCode = ((ExitCodeProvider) thrown).getExitCode();
+      message = thrown.getMessage();
+      if (message == null) {
+        // some exceptions do not have a message; fall back
+        // to the string value.
+        message = thrown.toString();
+      }
+    } else {
+      // no exception code: use the default
+      exitCode = EXIT_EXCEPTION_THROWN;
+    }
+    // construct the new exception with the original message and
+    // an exit code
+    exitException = new ServiceLaunchException(exitCode, message);
+    exitException.initCause(thrown);
+    return exitException;
+  }
+
+  /**
+   * Generate an exception announcing a failure to create the service.
+   * @param exception inner exception.
+   * @return a new exception, with the exit code
+   * {@link LauncherExitCodes#EXIT_SERVICE_CREATION_FAILURE}
+   */
+  protected ServiceLaunchException serviceCreationFailure(Exception exception) {
+    return new ServiceLaunchException(EXIT_SERVICE_CREATION_FAILURE, exception);
+  }
+  
+  /**
+   * Override point: register this class as the handler for the control-C
+   * and SIGINT interrupts.
+   *
+   * Subclasses can extend this with extra operations, such as
+   * an exception handler:
+   * <pre>
+   *  Thread.setDefaultUncaughtExceptionHandler(
+   *     new YarnUncaughtExceptionHandler());
+   * </pre>
+   */
+  protected void registerFailureHandling() {
+    try {
+      interruptEscalator = new InterruptEscalator(this,
+          SHUTDOWN_TIME_ON_INTERRUPT);
+      interruptEscalator.register(IrqHandler.CONTROL_C);
+      interruptEscalator.register(IrqHandler.SIGTERM);
+    } catch (IllegalArgumentException e) {
+      // downgrade interrupt registration to warnings
+      LOG.warn("{}", e, e);
+    }
+    Thread.setDefaultUncaughtExceptionHandler(
+        new HadoopUncaughtExceptionHandler(this));
+  }
+
+  /**
+   * Handler for uncaught exceptions: terminate the service.
+   * @param thread thread
+   * @param exception exception
+   */
+  @Override
+  public void uncaughtException(Thread thread, Throwable exception) {
+    LOG.error("Uncaught exception in thread {} -exiting", thread, exception);
+    exit(convertToExitException(exception));
+  }
+
+  /**
+   * Get the service name via {@link Service#getName()}.
+   *
+   * If the service is not instantiated, the classname is returned instead.
+   * @return the service name
+   */
+  public String getServiceName() {
+    Service s = service;
+    String name = null;
+    if (s != null) {
+      try {
+        name = s.getName();
+      } catch (Exception ignored) {
+        // ignored
+      }
+    }
+    if (name != null) {
+      return "service " + name;
+    } else {
+      return "service " + serviceName;
+    }
+  }
+  
+  /**
+   * Print a warning message.
+   * <p>
+   * This tries to log to the log's warn() operation.
+   * If the log at that level is disabled it logs to system error
+   * @param text warning text
+   */
+  protected void warn(String text) {
+    if (LOG.isWarnEnabled()) {
+      LOG.warn(text);
+    } else {
+      System.err.println(text);
+    }
+  }
+
+  /**
+   * Report an error. 
+   * <p>
+   * This tries to log to {@code LOG.error()}.
+   * <p>
+   * If that log level is disabled disabled the message
+   * is logged to system error along with {@code thrown.toString()}
+   * @param message message for the user
+   * @param thrown the exception thrown
+   */
+  protected void error(String message, Throwable thrown) {
+    String text = "Exception: " + message;
+    if (LOG.isErrorEnabled()) {
+      LOG.error(text, thrown);
+    } else {
+      System.err.println(text);
+      if (thrown != null) {
+        System.err.println(thrown.toString());
+      }
+    }
+  }
+  
+  /**
+   * Exit the JVM.
+   *
+   * This is method can be overridden for testing, throwing an 
+   * exception instead. Any subclassed method MUST raise an 
+   * {@code ExitException} instance/subclass.
+   * The service launcher code assumes that after this method is invoked,
+   * no other code in the same method is called.
+   * @param exitCode code to exit
+   */
+  protected void exit(int exitCode, String message) {
+    ExitUtil.terminate(exitCode, message);
+  }
+
+  /**
+   * Exit the JVM using an exception for the exit code and message,
+   * invoking {@link ExitUtil#terminate(ExitUtil.ExitException)}.
+   *
+   * This is the standard way a launched service exits.
+   * An error code of 0 means success -nothing is printed.
+   *
+   * If {@link ExitUtil#disableSystemExit()} has been called, this
+   * method will throw the exception.
+   *
+   * The method <i>may</i> be subclassed for testing
+   * @param ee exit exception
+   * @throws ExitUtil.ExitException if ExitUtil exceptions are disabled
+   */
+  protected void exit(ExitUtil.ExitException ee) {
+    ExitUtil.terminate(ee);
+  }
+
+  /**
+   * Override point: get the classloader to use.
+   * @return the classloader for loading a service class.
+   */
+  protected ClassLoader getClassLoader() {
+    return this.getClass().getClassLoader();
+  }
+
+  /**
+   * Extract the command options and apply them to the configuration,
+   * building an array of processed arguments to hand down to the service.
+   *
+   * @param conf configuration to update.
+   * @param args main arguments. {@code args[0]}is assumed to be
+   * the service classname and is skipped.
+   * @return the remaining arguments
+   * @throws ExitUtil.ExitException if JVM exiting is disabled.
+   */
+  public List<String> extractCommandOptions(Configuration conf,
+      List<String> args) {
+    int size = args.size();
+    if (size <= 1) {
+      return new ArrayList<>(0);
+    }
+    List<String> coreArgs = args.subList(1, size);
+
+    return parseCommandArgs(conf, coreArgs);
+  }
+
+  /**
+   * Parse the command arguments, extracting the service class as the last
+   * element of the list (after extracting all the rest).
+   *
+   * The field {@link #commandOptions} field must already have been set.
+   * @param conf configuration to use
+   * @param args command line argument list
+   * @return the remaining arguments
+   * @throws ServiceLaunchException if processing of arguments failed
+   */
+  protected List<String> parseCommandArgs(Configuration conf,
+      List<String> args) {
+    Preconditions.checkNotNull(commandOptions,
+        "Command options have not been created");
+    StringBuilder argString = new StringBuilder(args.size() * 32);
+    for (String arg : args) {
+      argString.append("\"").append(arg).append("\" ");
+    }
+    LOG.debug("Command line: {}", argString);
+    try {
+      String[] argArray = args.toArray(new String[args.size()]);
+      // parse this the standard way. This will
+      // update the configuration in the parser, and potentially
+      // patch the user credentials
+      GenericOptionsParser parser = createGenericOptionsParser(conf, argArray);
+      if (!parser.isParseSuccessful()) {
+        throw new ServiceLaunchException(EXIT_COMMAND_ARGUMENT_ERROR,
+            E_PARSE_FAILED + " %s", argString);
+      }
+      CommandLine line = parser.getCommandLine();
+      List<String> remainingArgs = Arrays.asList(parser.getRemainingArgs());
+      LOG.debug("Remaining arguments {}", remainingArgs);
+
+      // Scan the list of configuration files
+      // and bail out if they don't exist
+      if (line.hasOption(ARG_CONF)) {
+        String[] filenames = line.getOptionValues(ARG_CONF);
+        verifyConfigurationFilesExist(filenames);
+        // Add URLs of files as list of URLs to load
+        for (String filename : filenames) {
+          File file = new File(filename);
+          LOG.debug("Configuration files {}", file);
+          confResourceUrls.add(file.toURI().toURL());
+        }
+      }
+      if (line.hasOption(ARG_CONFCLASS)) {
+        // new resources to instantiate as configurations
+        List<String> classnameList = Arrays.asList(
+            line.getOptionValues(ARG_CONFCLASS));
+        LOG.debug("Configuration classes {}", classnameList);
+        confClassnames.addAll(classnameList);
+      }
+      // return the remainder
+      return remainingArgs;
+    } catch (IOException e) {
+      // parsing problem: convert to a command argument error with
+      // the original text
+      throw new ServiceLaunchException(EXIT_COMMAND_ARGUMENT_ERROR, e);
+    } catch (RuntimeException e) {
+      // lower level issue such as XML parse failure
+      throw new ServiceLaunchException(EXIT_COMMAND_ARGUMENT_ERROR,
+          E_PARSE_FAILED + " %s : %s", argString, e);
+    }
+  }
+
+  /**
+   * Override point: create a generic options parser or subclass thereof.
+   * @param conf Hadoop configuration
+   * @param argArray array of arguments
+   * @return a generic options parser to parse the arguments
+   * @throws IOException on any failure
+   */
+  protected GenericOptionsParser createGenericOptionsParser(Configuration conf,
+      String[] argArray) throws IOException {
+    return new MinimalGenericOptionsParser(conf, commandOptions, argArray);
+  }
+
+  /**
+   * Verify that all the specified filenames exist.
+   * @param filenames a list of files
+   * @throws ServiceLaunchException if a file is not found
+   */
+  protected void verifyConfigurationFilesExist(String[] filenames) {
+    if (filenames == null) {
+      return;
+    }
+    for (String filename : filenames) {
+      File file = new File(filename);
+      LOG.debug("Conf file {}", file.getAbsolutePath());
+      if (!file.exists()) {
+        // no configuration file
+        throw new ServiceLaunchException(EXIT_NOT_FOUND,
+            ARG_CONF_PREFIXED + ": configuration file not found: %s",
+            file.getAbsolutePath());
+      }
+    }
+  }
+
+  /**
+   * Build a log message for starting up and shutting down. 
+   * @param classname the class of the server
+   * @param args arguments
+   */
+  protected static String startupShutdownMessage(String classname,
+      List<String> args) {
+    final String hostname = NetUtils.getHostname();
+
+    return StringUtils.createStartupShutdownMessage(classname, hostname,
+        args.toArray(new String[args.size()]));
+  }
+
+  /**
+   * Exit with a printed message. 
+   * @param status status code
+   * @param message message message to print before exiting
+   * @throws ExitUtil.ExitException if exceptions are disabled
+   */
+  protected static void exitWithMessage(int status, String message) {
+    ExitUtil.terminate(new ServiceLaunchException(status, message));
+  }
+
+  /**
+   * Exit with the usage exit code {@link #EXIT_USAGE}
+   * and message {@link #USAGE_MESSAGE}.
+   * @throws ExitUtil.ExitException if exceptions are disabled
+   */
+  protected static void exitWithUsageMessage() {
+    exitWithMessage(EXIT_USAGE, USAGE_MESSAGE);
+  }
+
+  /**
+   * This is the JVM entry point for the service launcher.
+   *
+   * Converts the arguments to a list, then invokes {@link #serviceMain(List)}
+   * @param args command line arguments.
+   */
+  public static void main(String[] args) {
+    serviceMain(Arrays.asList(args));
+  }
+
+  /**
+   * Varargs version of the entry point for testing and other in-JVM use.
+   * Hands off to {@link #serviceMain(List)}
+   * @param args command line arguments.
+   */
+  public static void serviceMain(String... args) {
+    serviceMain(Arrays.asList(args));
+  }
+
+  /* ====================================================================== */
+  /**
+   * The real main function, which takes the arguments as a list.
+   * Argument 0 MUST be the service classname
+   * @param argsList the list of arguments
+   */
+  /* ====================================================================== */
+
+  public static void serviceMain(List<String> argsList) {
+    if (argsList.isEmpty()) {
+      // no arguments: usage message
+      exitWithUsageMessage();
+    } else {
+      ServiceLauncher<Service> serviceLauncher =
+          new ServiceLauncher<>(argsList.get(0));
+      serviceLauncher.launchServiceAndExit(argsList);
+    }
+  }
+
+  /**
+   * A generic options parser which does not parse any of the traditional
+   * Hadoop options.
+   */
+  protected static class MinimalGenericOptionsParser
+      extends GenericOptionsParser {
+    public MinimalGenericOptionsParser(Configuration conf,
+        Options options, String[] args) throws IOException {
+      super(conf, options, args);
+    }
+
+    @Override
+    protected Options buildGeneralOptions(Options opts) {
+      return opts;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceShutdownHook.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceShutdownHook.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceShutdownHook.java
new file mode 100644
index 0000000..9115f4e
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceShutdownHook.java
@@ -0,0 +1,112 @@
+/*
+ * 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.hadoop.service.launcher;
+
+import java.lang.ref.WeakReference;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.service.Service;
+import org.apache.hadoop.util.ShutdownHookManager;
+
+/**
+ * JVM Shutdown hook for Service which will stop the
+ * Service gracefully in case of JVM shutdown.
+ * This hook uses a weak reference to the service,
+ * and when shut down, calls {@link Service#stop()} if the reference is valid.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class ServiceShutdownHook implements Runnable {
+  private static final Logger LOG = LoggerFactory.getLogger(
+      ServiceShutdownHook.class);
+
+  /**
+   * A weak reference to the service.
+   */
+  private final WeakReference<Service> serviceRef;
+
+  /**
+   * Create an instance.
+   * @param service the service
+   */
+  public ServiceShutdownHook(Service service) {
+    serviceRef = new WeakReference<>(service);
+  }
+
+  /**
+   * Register the service for shutdown with Hadoop's
+   * {@link ShutdownHookManager}.
+   * @param priority shutdown hook priority
+   */
+  public synchronized void register(int priority) {
+    unregister();
+    ShutdownHookManager.get().addShutdownHook(this, priority);
+  }
+
+  /**
+   * Unregister the hook.
+   */
+  public synchronized void unregister() {
+    try {
+      ShutdownHookManager.get().removeShutdownHook(this);
+    } catch (IllegalStateException e) {
+      LOG.info("Failed to unregister shutdown hook: {}", e, e);
+    }
+  }
+
+  /**
+   * Shutdown handler.
+   * Query the service hook reference -if it is still valid the 
+   * {@link Service#stop()} operation is invoked.
+   */
+  @Override
+  public void run() {
+    shutdown();
+  }
+
+  /**
+   * Shutdown operation.
+   * <p>
+   * Subclasses may extend it, but it is primarily
+   * made available for testing.
+   * @return true if the service was stopped and no exception was raised.
+   */
+  protected boolean shutdown() {
+    Service service;
+    boolean result = false;
+    synchronized (this) {
+      service = serviceRef.get();
+      serviceRef.clear();
+    }
+    if (service != null) {
+      try {
+        // Stop the  Service
+        service.stop();
+        result = true;
+      } catch (Throwable t) {
+        LOG.info("Error stopping {}", service.getName(), t);
+      }
+    }
+    return result;
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: common-commits-unsubscribe@hadoop.apache.org
For additional commands, e-mail: common-commits-help@hadoop.apache.org