Interrupt command processing on client disconnect.

Summary:
Updates the version of Nailgun used by Buck to a fork which adds support for
detecting client disconnection and adds a callback to Main which interrupts
command processing on client disconnection, so that Buck behaves consistently
when killed wether using buckd or running standalone.

Test Plan: buck test --all
diff --git a/.classpath b/.classpath
index 924e3ee..e39204a 100644
--- a/.classpath
+++ b/.classpath
@@ -17,6 +17,7 @@
 	<classpathentry kind="lib" path="lib/jackson-annotations-2.0.5.jar"/>
 	<classpathentry kind="lib" path="lib/jsr305.jar"/>
 	<classpathentry kind="lib" path="lib/junit-4.11.jar" sourcepath="lib/junit-4.11-sources.jar"/>
+	<classpathentry kind="lib" path="lib/nailgun-server-0.9.2-SNAPSHOT.jar" sourcepath="lib/nailgun-server-0.9.2-SNAPSHOT-sources.jar"/>
 	<classpathentry kind="lib" path="lib/objenesis-1.2.jar"/>
 	<classpathentry kind="lib" path="lib/sdklib.jar"/>
 	<classpathentry kind="lib" path="third-party/java/asm/asm-debug-all-4.1.jar" sourcepath="third-party/java/asm/asm-4.1-src.zip"/>
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index b22c943..9accd4d 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -2,6 +2,11 @@
 <project version="4">
   <component name="CompilerConfiguration">
     <option name="DEFAULT_COMPILER" value="Javac" />
+    <excludeFromCompile>
+      <directory url="file://$PROJECT_DIR$/buck-out/android" includeSubdirectories="true" />
+      <directory url="file://$PROJECT_DIR$/buck-out/bin" includeSubdirectories="true" />
+      <directory url="file://$PROJECT_DIR$/buck-out/gen" includeSubdirectories="true" />
+    </excludeFromCompile>
     <resourceExtensions />
     <wildcardResourcePatterns>
       <entry name="?*.properties" />
diff --git a/.idea/libraries/buck_lib.xml b/.idea/libraries/buck_lib.xml
index e147b05..957de19 100644
--- a/.idea/libraries/buck_lib.xml
+++ b/.idea/libraries/buck_lib.xml
@@ -11,11 +11,13 @@
       <root url="jar://$PROJECT_DIR$/lib/sdklib.jar!/" />
       <root url="jar://$PROJECT_DIR$/third-party/java/asm/asm-debug-all-4.1.jar!/" />
       <root url="jar://$PROJECT_DIR$/lib/guava-15.0.jar!/" />
+      <root url="jar://$PROJECT_DIR$/lib/nailgun-server-0.9.2-SNAPSHOT.jar!/" />
     </CLASSES>
     <JAVADOC />
     <SOURCES>
       <root url="jar://$PROJECT_DIR$/third-party/java/asm/asm-4.1-src.zip!/" />
       <root url="jar://$PROJECT_DIR$/lib/guava-15.0-sources.jar!/" />
+      <root url="jar://$PROJECT_DIR$/lib/nailgun-server-0.9.2-SNAPSHOT-sources.jar!/" />
     </SOURCES>
   </library>
 </component>
\ No newline at end of file
diff --git a/bin/buck b/bin/buck
index c2e6ec1..f9fe0f8 100755
--- a/bin/buck
+++ b/bin/buck
@@ -46,7 +46,6 @@
 # Daemon is not running, or is busy so start buck process.
 java \
 $BUCK_JAVA_ARGS \
--Dbuck.daemon=false \
 -classpath \
 $BUCK_JAVA_CLASSPATH \
 com.facebook.buck.cli.Main "$@"
diff --git a/bin/buck_common b/bin/buck_common
index c74a3b9..026a5ca 100755
--- a/bin/buck_common
+++ b/bin/buck_common
@@ -152,14 +152,14 @@
 BUCK_JAVA_CLASSPATH="${BUCK_DIRECTORY}/src:\
 ${BUCK_DIRECTORY}/build/classes:\
 ${BUCK_DIRECTORY}/lib/args4j.jar:\
+${BUCK_DIRECTORY}/lib/ddmlib-r21.jar:\
 ${BUCK_DIRECTORY}/lib/guava-15.0.jar:\
 ${BUCK_DIRECTORY}/lib/ini4j-0.5.2.jar:\
 ${BUCK_DIRECTORY}/lib/jackson-annotations-2.0.5.jar:\
 ${BUCK_DIRECTORY}/lib/jackson-core-2.0.5.jar:\
 ${BUCK_DIRECTORY}/lib/jackson-databind-2.0.5.jar:\
 ${BUCK_DIRECTORY}/lib/jsr305.jar:\
-${BUCK_DIRECTORY}/lib/sdklib.jar:\
-${BUCK_DIRECTORY}/lib/ddmlib-r21.jar:\
+${BUCK_DIRECTORY}/lib/nailgun-server-0.9.2-SNAPSHOT.jar:\
 ${BUCK_DIRECTORY}/lib/sdklib.jar:\
 ${BUCK_DIRECTORY}/third-party/java/asm/asm-debug-all-4.1.jar:\
 ${BUCK_DIRECTORY}/third-party/java/astyanax/astyanax-cassandra-1.56.38.jar:\
diff --git a/bin/buckd b/bin/buckd
index 1401bdc..13bd1ec 100755
--- a/bin/buckd
+++ b/bin/buckd
@@ -38,10 +38,8 @@
 # Run buckd.
 java \
 $BUCK_JAVA_ARGS \
--Dbuck.daemon=true \
 -classpath \
-${BUCK_JAVA_CLASSPATH}:\
-${BUCK_DIRECTORY}/lib/nailgun-server-0.9.2-SNAPSHOT.jar \
+${BUCK_JAVA_CLASSPATH} \
 com.martiansoftware.nailgun.NGServer localhost:${BUCKD_PORT} \
 &> $BUCKD_LOG_FILE &
 echo $! > "$BUCKD_PID_FILE"
diff --git a/build.xml b/build.xml
index 8ac1da4..7e075b7 100644
--- a/build.xml
+++ b/build.xml
@@ -30,6 +30,7 @@
     <include name="jackson-databind-2.0.5.jar" />
     <include name="jsr305.jar" />
     <include name="sdklib.jar" />
+    <include name="nailgun-server-0.9.2-SNAPSHOT.jar" />
   </fileset>
 
   <fileset dir="${third-party.dir}" id="third-party.jars">
diff --git a/lib/BUCK b/lib/BUCK
index d960f9b..49d6f94 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -159,3 +159,14 @@
     '//src/com/facebook/buck/android:steps',
   ]
 )
+
+prebuilt_jar(
+  name = 'nailgun',
+  binary_jar = 'nailgun-server-0.9.2-SNAPSHOT.jar',
+  source_jar = 'nailgun-server-0.9.2-SNAPSHOT-sources.jar',
+  visibility = [
+    '//src/com/facebook/buck/cli:cli',
+    '//test/com/facebook/buck/cli:cli',
+    '//test/com/facebook/buck/testutil/integration:integration',
+  ]
+)
diff --git a/lib/README.txt b/lib/README.txt
index 88e850a..53963da 100644
--- a/lib/README.txt
+++ b/lib/README.txt
@@ -6,3 +6,18 @@
 critical feature that we need for our purposes. For download go to:
 
 http://downloads.sourceforge.net/project/emma/emma-release/2.0.5312/emma-2.0.5312-lib.zip
+
+
+nailgun-server-0.9.2-SNAPSHOT.jar and nailgun-server-0.9.2-SNAPSHOT-sources.jar were
+built from a fork of Nailgun which adds support for detecting client disconnection at
+https://github.com/jimpurbrick/nailgun.git
+
+To regenerate these jars:
+
+ 0) install maven (brew install maven)
+ 1) git clone https://github.com/jimpurbrick/nailgun.git
+ 2) cd nailgun
+ 3) git checkout d005c16f13d42489ac1ab428b15c3d9cdb1ada31
+ 4) mvn clean install
+ 5) copy nailgun-server/target/nailgun-server-0.9.2-SNAPSHOT.jar and
+    nailgun-server/target/nailgun-server-0.9.2-SNAPSHOT-sources.jar to your buck/lib directory
diff --git a/lib/nailgun-server-0.9.2-SNAPSHOT-sources.jar b/lib/nailgun-server-0.9.2-SNAPSHOT-sources.jar
new file mode 100644
index 0000000..a595667
--- /dev/null
+++ b/lib/nailgun-server-0.9.2-SNAPSHOT-sources.jar
Binary files differ
diff --git a/lib/nailgun-server-0.9.2-SNAPSHOT.jar b/lib/nailgun-server-0.9.2-SNAPSHOT.jar
index 5b5658e..8d45dda 100644
--- a/lib/nailgun-server-0.9.2-SNAPSHOT.jar
+++ b/lib/nailgun-server-0.9.2-SNAPSHOT.jar
Binary files differ
diff --git a/src/com/facebook/buck/cli/BUCK b/src/com/facebook/buck/cli/BUCK
index 6b8f806..c74794e 100644
--- a/src/com/facebook/buck/cli/BUCK
+++ b/src/com/facebook/buck/cli/BUCK
@@ -29,6 +29,7 @@
     '//lib:jackson-core',
     '//lib:jackson-databind',
     '//lib:jsr305',
+    '//lib:nailgun',
     '//src/com/facebook/buck/android:exceptions',
     '//src/com/facebook/buck/event:event',
     '//src/com/facebook/buck/command:command',
diff --git a/src/com/facebook/buck/cli/Main.java b/src/com/facebook/buck/cli/Main.java
index 5b62afa..22f65f0 100644
--- a/src/com/facebook/buck/cli/Main.java
+++ b/src/com/facebook/buck/cli/Main.java
@@ -55,6 +55,8 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.eventbus.EventBus;
 import com.google.common.reflect.ClassPath;
+import com.martiansoftware.nailgun.NGClientListener;
+import com.martiansoftware.nailgun.NGContext;
 
 import java.io.Closeable;
 import java.io.File;
@@ -82,6 +84,11 @@
    */
   public static final int BUSY_EXIT_CODE = 2;
 
+  /**
+   * Client disconnected.
+   */
+  public static final int CLIENT_DISCONNECT_EXIT_CODE = 3;
+
   private static final String DEFAULT_BUCK_CONFIG_FILE_NAME = ".buckconfig";
   private static final String DEFAULT_BUCK_CONFIG_OVERRIDE_FILE_NAME = ".buckconfig.local";
 
@@ -163,8 +170,32 @@
       return parser;
     }
 
+    private void watchClient(NGContext context) {
+      context.addClientListener(new NGClientListener() {
+        @Override
+        public void clientDisconnected() {
+
+          // Synchronize on parser object so that the main command processing thread is not
+          // interrupted mid way through a Parser cache update by the Thread.interrupt() call
+          // triggered by System.exit(). The Parser cache will be reused by subsequent commands
+          // so needs to be left in a consistent state even if the current command is interrupted
+          // due to a client disconnection.
+          synchronized (parser) {
+            System.exit(CLIENT_DISCONNECT_EXIT_CODE);
+          }
+        }
+      });
+    }
+
     private void watchFileSystem() throws IOException {
-      filesystemWatcher.postEvents();
+
+      // Synchronize on parser object so that all outstanding watch events are processed
+      // as a single, atomic Parser cache update and are not interleaved with Parser cache
+      // invalidations triggered by requests to parse build files or interrupted by client
+      // disconnections.
+      synchronized (parser) {
+        filesystemWatcher.postEvents();
+      }
     }
 
     /** @return true if the web server was started successfully. */
@@ -203,10 +234,16 @@
 
   @Nullable private static Daemon daemon;
 
-  private boolean isDaemon() {
-    return Boolean.getBoolean("buck.daemon");
+  /**
+   * Get existing Daemon.
+   */
+  private Daemon getDaemon() {
+    return Preconditions.checkNotNull(daemon);
   }
 
+  /**
+   * Get or create Daemon.
+   */
   private Daemon getDaemon(ProjectFilesystem filesystem,
                            BuckConfig config,
                            Console console) throws IOException {
@@ -273,11 +310,12 @@
   }
 
   /**
+   * @param context an optional NGContext that is present if running inside a Nailgun server.
    * @param args command line arguments
    * @return an exit code or {@code null} if this is a process that should not exit
    */
   @SuppressWarnings("PMD.EmptyCatchBlock")
-  public int runMainWithExitCode(File projectRoot, String... args) throws IOException {
+  public int runMainWithExitCode(File projectRoot, Optional<NGContext> context, String... args) throws IOException {
     if (args.length == 0) {
       return usage();
     }
@@ -295,15 +333,13 @@
 
     // Create or get and invalidate cached command parameters.
     Parser parser;
-    Optional<Daemon> daemonOptional;
-    if (isDaemon()) {
+    if (context.isPresent()) {
       Daemon daemon = getDaemon(projectFilesystem, config, console);
+      daemon.watchClient(context.get());
       daemon.watchFileSystem();
       daemon.initWebServer();
-      daemonOptional = Optional.of(daemon);
       parser = daemon.getParser();
     } else {
-      daemonOptional = Optional.absent();
       parser = new Parser(projectFilesystem,
           knownBuildRuleTypes,
           console,
@@ -321,24 +357,20 @@
     // Find and execute command.
     Optional<Command> command = Command.getCommandForName(args[0], console);
     if (command.isPresent()) {
-      Optional<WebServer> webServerOptional = Optional.absent();
-      if (daemonOptional.isPresent()) {
-        webServerOptional = daemonOptional.get().getWebServer();
-      }
       ImmutableList<BuckEventListener> eventListeners =
           addEventListeners(buildEventBus,
               clock,
               projectFilesystem,
               console,
               config,
-              webServerOptional);
+              getWebServerIfDaemon(context));
       String[] remainingArgs = new String[args.length - 1];
       System.arraycopy(args, 1, remainingArgs, 0, remainingArgs.length);
 
       Command executingCommand = command.get();
       String commandName = executingCommand.name().toLowerCase();
 
-      buildEventBus.post(CommandEvent.started(commandName, isDaemon()));
+      buildEventBus.post(CommandEvent.started(commandName, context.isPresent()));
 
       // The ArtifactCache is constructed lazily so that we do not try to connect to Cassandra when
       // running commands such as `buck clean`.
@@ -366,7 +398,7 @@
           parser,
           platform));
 
-      buildEventBus.post(CommandEvent.finished(commandName, isDaemon(), exitCode));
+      buildEventBus.post(CommandEvent.finished(commandName, context.isPresent(), exitCode));
 
       ExecutorService buildEventBusExecutor = buildEventBus.getExecutorService();
       buildEventBusExecutor.shutdown();
@@ -390,6 +422,13 @@
     }
   }
 
+  private Optional<WebServer> getWebServerIfDaemon(Optional<NGContext> context) {
+    if (context.isPresent()) {
+      return getDaemon().getWebServer();
+    }
+    return Optional.absent();
+  }
+
   private void loadListenersFromBuckConfig(
       ImmutableList.Builder<BuckEventListener> eventListeners,
       ProjectFilesystem projectFilesystem,
@@ -522,13 +561,14 @@
   }
 
   @VisibleForTesting
-  int tryRunMainWithExitCode(File projectRoot, String... args) throws IOException {
+  int tryRunMainWithExitCode(File projectRoot, Optional<NGContext> context, String... args)
+      throws IOException {
     // TODO(user): enforce write command exclusion, but allow concurrent read only commands?
     if (!commandSemaphore.tryAcquire()) {
       return BUSY_EXIT_CODE;
     }
     try {
-      return runMainWithExitCode(projectRoot, args);
+      return runMainWithExitCode(projectRoot, context, args);
     } catch (HumanReadableException e) {
       Console console = new Console(Verbosity.STANDARD_INFORMATION,
           stdOut,
@@ -541,12 +581,11 @@
     }
   }
 
-  public static void main(String[] args) {
-    Main main = new Main(System.out, System.err);
+  private void runMainThenExit(String[] args, Optional<NGContext> context) {
     File projectRoot = new File(".");
     int exitCode = FAIL_EXIT_CODE;
     try {
-      exitCode = main.tryRunMainWithExitCode(projectRoot, args);
+      exitCode = tryRunMainWithExitCode(projectRoot, context, args);
     } catch (Throwable t) {
       t.printStackTrace();
     } finally {
@@ -555,4 +594,17 @@
       System.exit(exitCode);
     }
   }
+
+  public static void main(String[] args) {
+    new Main(System.out, System.err).runMainThenExit(args, Optional.<NGContext>absent());
+  }
+
+  /**
+   * When running as a daemon in the NailGun server, {@link #nailMain(NGContext)} is called instead
+   * of {@link #main(String[])} so that the given context can be used to listen for client
+   * disconnections and interrupt command processing when they occur.
+   */
+  public static void nailMain(final NGContext context) throws InterruptedException {
+    new Main(context.out, context.err).runMainThenExit(context.getArgs(), Optional.of(context));
+  }
 }
diff --git a/src/com/facebook/buck/parser/Parser.java b/src/com/facebook/buck/parser/Parser.java
index 4dc3d8a..4593454 100644
--- a/src/com/facebook/buck/parser/Parser.java
+++ b/src/com/facebook/buck/parser/Parser.java
@@ -64,7 +64,6 @@
 import java.util.regex.Pattern;
 
 import javax.annotation.Nullable;
-import javax.annotation.concurrent.NotThreadSafe;
 
 /**
  * High-level build file parsing machinery.  Primarily responsible for producing a
@@ -73,7 +72,6 @@
  * processes filesystem WatchEvents to invalidate the cache as files change. Expected to be used
  * from a single thread, so methods are not synchronized or thread safe.
  */
-@NotThreadSafe
 public class Parser {
 
   private final BuildTargetParser buildTargetParser;
@@ -250,7 +248,7 @@
    * @param includes the files to include before executing the build file.
    * @return true if the cache was invalidated, false if the cache is still valid.
    */
-  private boolean invalidateCacheOnIncludeChange(Iterable<String> includes) {
+  private synchronized boolean invalidateCacheOnIncludeChange(Iterable<String> includes) {
     List<String> includesList = Lists.newArrayList(includes);
     if (!includesList.equals(this.cacheDefaultIncludes)) {
       invalidateCache();
@@ -260,7 +258,7 @@
     return false;
   }
 
-  private void invalidateCache() {
+  private synchronized void invalidateCache() {
     if (console.getVerbosity() == Verbosity.ALL) {
       console.getStdErr().println("Parser invalidating entire cache");
     }
@@ -461,7 +459,7 @@
    * @param rules the raw rule objects to parse.
    */
   @VisibleForTesting
-  void parseRawRulesInternal(Iterable<Map<String, Object>> rules)
+  synchronized void parseRawRulesInternal(Iterable<Map<String, Object>> rules)
       throws BuildTargetException, IOException {
     for (Map<String, Object> map : rules) {
 
@@ -577,7 +575,7 @@
    *     in the List returned by this method. If filter is null, then this method returns null.
    * @return The build targets in the project filtered by the given filter.
    */
-  public List<BuildTarget> filterAllTargetsInProject(ProjectFilesystem filesystem,
+  public synchronized List<BuildTarget> filterAllTargetsInProject(ProjectFilesystem filesystem,
                                                      Iterable<String> includes,
                                                      @Nullable RawRulePredicate filter)
       throws BuildFileParseException, BuildTargetException, IOException {
@@ -676,7 +674,7 @@
    * targets and rules defined by files that transitively include {@code path} from the cache.
    * @param path The File that has changed.
    */
-  private void invalidateDependents(Path path) {
+  private synchronized void invalidateDependents(Path path) {
     // Normalize path to ensure it hashes equally with map keys.
     path = normalize(path);
 
diff --git a/test/com/facebook/buck/cli/BUCK b/test/com/facebook/buck/cli/BUCK
index 065fe05..be5721d 100644
--- a/test/com/facebook/buck/cli/BUCK
+++ b/test/com/facebook/buck/cli/BUCK
@@ -68,6 +68,7 @@
     '//lib:jackson-databind',
     '//lib:jsr305',
     '//lib:junit',
+    '//lib:nailgun',
     '//src/com/facebook/buck/android:rules',
     '//src/com/facebook/buck/cli:cli',
     '//src/com/facebook/buck/cli:events',
diff --git a/test/com/facebook/buck/cli/DaemonIntegrationTest.java b/test/com/facebook/buck/cli/DaemonIntegrationTest.java
index 5f42a38..32f9020 100644
--- a/test/com/facebook/buck/cli/DaemonIntegrationTest.java
+++ b/test/com/facebook/buck/cli/DaemonIntegrationTest.java
@@ -17,20 +17,35 @@
 package com.facebook.buck.cli;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
 
 import com.facebook.buck.testutil.integration.DebuggableTemporaryFolder;
 import com.facebook.buck.testutil.integration.ProjectWorkspace;
 import com.facebook.buck.testutil.integration.TestDataHelper;
 import com.facebook.buck.util.CapturingPrintStream;
+import com.google.common.base.Optional;
 import com.google.common.base.Throwables;
+import com.martiansoftware.nailgun.NGClientListener;
+import com.martiansoftware.nailgun.NGConstants;
+import com.martiansoftware.nailgun.NGContext;
+import com.martiansoftware.nailgun.NGExitException;
+import com.martiansoftware.nailgun.NGInputStream;
+import com.martiansoftware.nailgun.NGSecurityManager;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.Arrays;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
@@ -39,6 +54,7 @@
 
 public class DaemonIntegrationTest {
 
+  private static final int SUCCESS_EXIT_CODE = 0;
   private ScheduledExecutorService executorService;
 
   @Rule
@@ -55,9 +71,9 @@
   }
 
   /**
-   * This verifies that when the user tries to run the Buck Main method, while it is already running,
-   * the second call will fail to avoid multiple threads accessing and corrupting the static state
-   * used by the Buck daemon.
+   * This verifies that when the user tries to run the Buck Main method, while it is already
+   * running, the second call will fail. Serializing command execution in this way avoids
+   * multiple threads accessing and corrupting the static state used by the Buck daemon.
    */
   @Test
   public void testExclusiveExecution()
@@ -75,8 +91,11 @@
       public void run() {
         try {
           Main main = new Main(stdOut, firstThreadStdErr);
-          int exitCode = main.tryRunMainWithExitCode(tmp.getRoot(), "build", "//:sleep");
-          assertEquals("Should return 0 when no command running.", 0, exitCode);
+          int exitCode = main.tryRunMainWithExitCode(tmp.getRoot(),
+              Optional.<NGContext>absent(),
+              "build",
+              "//:sleep");
+          assertEquals("Should return 0 when no command running.", SUCCESS_EXIT_CODE, exitCode);
         } catch (IOException e) {
           fail("Should not throw IOException");
           throw Throwables.propagate(e);
@@ -88,8 +107,10 @@
       public void run() {
         try {
           Main main = new Main(stdOut, secondThreadStdErr);
-          int exitCode = main.tryRunMainWithExitCode(tmp.getRoot(), "targets");
-          assertEquals("Should return 1 when command running.", Main.BUSY_EXIT_CODE, exitCode);
+          int exitCode = main.tryRunMainWithExitCode(tmp.getRoot(),
+              Optional.<NGContext>absent(),
+              "targets");
+          assertEquals("Should return 2 when command running.", Main.BUSY_EXIT_CODE, exitCode);
         } catch (IOException e) {
           fail("Should not throw IOException.");
           throw Throwables.propagate(e);
@@ -99,4 +120,75 @@
     firstThread.get();
     secondThread.get();
   }
+
+  private InputStream createHeartbeatStream(int count) {
+    final int BYTES_PER_HEARTBEAT = 5;
+    byte[] bytes = new byte[BYTES_PER_HEARTBEAT * count];
+    Arrays.fill(bytes, NGConstants.CHUNKTYPE_HEARTBEAT);
+    return new ByteArrayInputStream(bytes);
+  }
+
+  /**
+   * This verifies that a client disconnection will be detected by a Nailgun
+   * NGInputStream which then calls a clientDisconnected handler which interrupts Buck command
+   * processing.
+   */
+  @Test
+  public void whenClientDisconnectsThenCommandIsInterrupted()
+      throws InterruptedException, IOException {
+
+    // NGInputStream test double which provides access to registered client listener.
+    class TestNGInputStream extends NGInputStream {
+
+      public NGClientListener listener = null;
+
+      public TestNGInputStream(InputStream in, DataOutputStream out, PrintStream serverLog) {
+        super(in, out, serverLog);
+      }
+
+      @Override
+      public synchronized void addClientListener(NGClientListener listener) {
+        this.listener = listener;
+      }
+    }
+
+    // Build an NGContext connected to an NGInputStream reading from a stream of heartbeats.
+    Thread.currentThread().setName("Test");
+    CapturingPrintStream serverLog = new CapturingPrintStream();
+    NGContext context = new NGContext();
+    TestNGInputStream inputStream = new TestNGInputStream(
+        new DataInputStream(createHeartbeatStream(100)),
+        new DataOutputStream(new ByteArrayOutputStream(0)),
+        serverLog);
+    context.setArgs(new String[] {"targets"});
+    context.in = inputStream;
+    context.out = new CapturingPrintStream();
+    context.err = new CapturingPrintStream();
+
+    // NGSecurityManager is used to convert System.exit() calls in to NGExitExceptions.
+    SecurityManager originalSecurityManager = System.getSecurityManager();
+
+    // Run command to register client listener.
+    try {
+      System.setSecurityManager(new NGSecurityManager(originalSecurityManager));
+      Main.nailMain(context);
+      fail("Should throw NGExitException.");
+    } catch (NGExitException e) {
+      assertEquals("Should exit with status 0.", SUCCESS_EXIT_CODE, e.getStatus());
+    } finally {
+      System.setSecurityManager(originalSecurityManager);
+    }
+
+    // Check listener was registered calls System.exit() with client disconnect exit code.
+    try {
+      System.setSecurityManager(new NGSecurityManager(originalSecurityManager));
+      assertNotNull("Should register client listener.", inputStream.listener);
+      inputStream.listener.clientDisconnected();
+      fail("Should throw NGExitException.");
+    } catch (NGExitException e) {
+      assertEquals("Should exit with status 3", Main.CLIENT_DISCONNECT_EXIT_CODE, e.getStatus());
+    } finally {
+      System.setSecurityManager(originalSecurityManager);
+    }
+  }
 }
diff --git a/test/com/facebook/buck/cli/MainTest.java b/test/com/facebook/buck/cli/MainTest.java
index 7ff533d..bbccee2 100644
--- a/test/com/facebook/buck/cli/MainTest.java
+++ b/test/com/facebook/buck/cli/MainTest.java
@@ -21,6 +21,8 @@
 import com.facebook.buck.util.CapturingPrintStream;
 import com.google.common.base.Charsets;
 import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.martiansoftware.nailgun.NGContext;
 
 import org.easymock.EasyMock;
 import org.junit.After;
@@ -62,7 +64,7 @@
     CapturingPrintStream stdErr = new CapturingPrintStream();
 
     Main main = new Main(stdOut, stdErr);
-    int exitCode = main.runMainWithExitCode(new File("."));
+    int exitCode = main.runMainWithExitCode(new File("."), Optional.<NGContext>absent());
     assertEquals(1, exitCode);
     assertEquals(
         "When the user does not specify any arguments, the usage information should be displayed",
@@ -75,7 +77,7 @@
     CapturingPrintStream stdErr = new CapturingPrintStream();
 
     Main main = new Main(stdOut, stdErr);
-    int exitCode = main.runMainWithExitCode(new File("."), "--help");
+    int exitCode = main.runMainWithExitCode(new File("."), Optional.<NGContext>absent(), "--help");
     assertEquals(1, exitCode);
     assertEquals("Users instinctively try running `buck --help`, so it should print usage info.",
         getUsageString(),
diff --git a/test/com/facebook/buck/testutil/integration/BUCK b/test/com/facebook/buck/testutil/integration/BUCK
index d5e8568..669980a 100644
--- a/test/com/facebook/buck/testutil/integration/BUCK
+++ b/test/com/facebook/buck/testutil/integration/BUCK
@@ -10,6 +10,7 @@
     '//lib:guava',
     '//lib:jsr305',
     '//lib:junit',
+    '//lib:nailgun',
   ],
   visibility = [
     'PUBLIC',
diff --git a/test/com/facebook/buck/testutil/integration/ProjectWorkspace.java b/test/com/facebook/buck/testutil/integration/ProjectWorkspace.java
index 3d682d4..822fc1f 100644
--- a/test/com/facebook/buck/testutil/integration/ProjectWorkspace.java
+++ b/test/com/facebook/buck/testutil/integration/ProjectWorkspace.java
@@ -27,8 +27,10 @@
 import com.facebook.buck.util.environment.Platform;
 import com.google.common.base.Charsets;
 import com.google.common.base.Function;
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.io.Files;
+import com.martiansoftware.nailgun.NGContext;
 
 import org.junit.rules.TemporaryFolder;
 
@@ -129,7 +131,6 @@
       };
       java.nio.file.Files.walkFileTree(destPath, copyDirVisitor);
     }
-
     isSetUp = true;
   }
 
@@ -145,7 +146,7 @@
     CapturingPrintStream stderr = new CapturingPrintStream();
 
     Main main = new Main(stdout, stderr);
-    int exitCode = main.runMainWithExitCode(destDir, args);
+    int exitCode = main.runMainWithExitCode(destDir, Optional.<NGContext>absent(), args);
 
     return new ProcessResult(exitCode,
         stdout.getContentsAsString(Charsets.UTF_8),
diff --git a/third-party/nailgun/README.md b/third-party/nailgun/README.md
index 4e6301f..213d4f4 100644
--- a/third-party/nailgun/README.md
+++ b/third-party/nailgun/README.md
@@ -15,3 +15,7 @@
 you will additionally need to "make ng.exe".
 
 For more information, see [the nailgun website](http://martiansoftware.com/nailgun/).
+
+Buck currently uses a fork of nailgun hosted at https://github.com/jimpurbrick/nailgun.
+This fork adds support for interrupting server processing when client disconnection is
+detected, changes which are currently being accepted in to the main nailgun repository.
\ No newline at end of file
diff --git a/third-party/nailgun/nailgun-client/ng.c b/third-party/nailgun/nailgun-client/ng.c
index 5a34aef..bd7a390 100644
--- a/third-party/nailgun/nailgun-client/ng.c
+++ b/third-party/nailgun/nailgun-client/ng.c
@@ -27,6 +27,7 @@
 	#include <netdb.h>
 	#include <netinet/in.h>
 	#include <sys/socket.h>
+        #include <sys/time.h>
 	#include <sys/types.h>
 #endif
 
@@ -92,7 +93,8 @@
 #define CHUNKTYPE_DIR 'D'
 #define CHUNKTYPE_CMD 'C'
 #define CHUNKTYPE_EXIT 'X'
-#define CHUNKTYPE_STARTINPUT 'S'
+#define CHUNKTYPE_SENDINPUT 'S'
+#define CHUNKTYPE_HEARTBEAT 'H'
 
 /*
    the following is required to compile for hp-ux
@@ -108,8 +110,8 @@
 /* buffer used for receiving and writing nail output chunks */
 char buf[BUFSIZE];
 
-/* track whether or not we've been told to send stdin to server */
-int startedInput = 0;
+/* track whether server is ready to receive */
+int readyToSend = 0;
 
 /**
  * Clean up the application.
@@ -334,6 +336,7 @@
  * @param len the number of bytes to send
  */
 void sendStdin(char *buf, unsigned int len) {
+  readyToSend = 0;
   sendHeader(len, CHUNKTYPE_STDIN);
   sendAll(nailgunsocket, buf, len);
 }
@@ -345,6 +348,12 @@
   sendHeader(0, CHUNKTYPE_STDIN_EOF);
 }
 
+/**
+ * Sends a heartbeat chunk to let the server know the client is still alive.
+ */
+void sendHeartbeat() {
+  sendHeader(0, CHUNKTYPE_HEARTBEAT);
+}
 
 #ifdef WIN32
 /**
@@ -357,7 +366,7 @@
   for (;;) {
     DWORD numberOfBytes = 0;
 
-    if (!ReadFile(NG_STDIN_FILENO, wbuf, BUFSIZE, &numberOfBytes, NULL)) {
+    if (readyToSend && !ReadFile(NG_STDIN_FILENO, wbuf, BUFSIZE, &numberOfBytes, NULL)) {
       if (numberOfBytes != 0) {
         handleError();
       }
@@ -464,13 +473,8 @@
             break;
       case CHUNKTYPE_EXIT:   processExit(buf, len);
             break;
-      case CHUNKTYPE_STARTINPUT:
-            if (!startedInput) {
-                #ifdef WIN32
-                winStartInput();
-                #endif
-      		startedInput = 1;
-      	    }
+      case CHUNKTYPE_SENDINPUT:
+	    readyToSend = 1;
             break;
       default:  fprintf(stderr, "Unexpected chunk type %d ('%c')\n", chunkType, chunkType);
           cleanUpAndExit(NAILGUN_UNEXPECTED_CHUNKTYPE);
@@ -551,6 +555,8 @@
   #ifndef WIN32
     fd_set readfds;
     int eof = 0;
+    struct timeval readtimeout;
+
   #endif
 
   #ifdef WIN32
@@ -696,24 +702,29 @@
       FD_ZERO(&readfds);
 
       /* don't select on stdin if we've already reached its end */
-      if (startedInput && !eof) {
+      if (readyToSend && !eof) {
 	FD_SET(NG_STDIN_FILENO, &readfds);
       }
 
       FD_SET(nailgunsocket, &readfds);
-      if (select (nailgunsocket + 1, &readfds, NULL, NULL, NULL) == -1) {
-	perror("select");
+
+      memset(&readtimeout, '\0', sizeof(readtimeout));
+      readtimeout.tv_usec = 100000;
+      if(select (nailgunsocket + 1, &readfds, NULL, NULL, &readtimeout) == -1) {
+	  perror("select");
       }
 	  
       if (FD_ISSET(nailgunsocket, &readfds)) {
     #endif
-	processnailgunstream();
+      processnailgunstream();
     #ifndef WIN32
       } else if (FD_ISSET(NG_STDIN_FILENO, &readfds)) {
-	if (!processStdin()) {
-	  FD_CLR(NG_STDIN_FILENO, &readfds);
-	  eof = 1;
-	}
+        if (!processStdin()) {
+          FD_CLR(NG_STDIN_FILENO, &readfds);
+          eof = 1;
+        }
+      } else {
+        sendHeartbeat();
       }
     #endif
   }