/*
* Copyright 2015-present Facebook, Inc.
*
* Licensed 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 com.facebook.buck.util;
import com.sun.jna.NativeLibrary;
import com.sun.jna.Platform;
import com.zaxxer.nuprocess.NuProcess;
import com.zaxxer.nuprocess.NuProcessBuilder;
import java.io.IOException;
/**
* Safely kill background processes on nailgun client exit. All process creation must synchronize on
* the class object's monitor lock to make sure children inherit the correct signal handler set.
*/
public class BgProcessKiller {
private static boolean initialized;
private static boolean armed;
public static void init() {
NativeLibrary libcLibrary = NativeLibrary.getInstance(Platform.C_LIBRARY_NAME);
// We kill subprocesses by sending SIGHUP to our process group; we want our subprocesses to die
// on SIGHUP while we ourselves stay around. We can't just set SIGHUP to SIG_IGN, because
// subprocesses would inherit the SIG_IGN signal disposition and be immune to the SIGHUP we
// direct toward the process group. We need _some_ signal handler that does nothing --- i.e.,
// acts like SIG_IGN --- but that doesn't kill its host process. When we spawn a subprocess,
// the kernel replaces any signal handler that is not SIG_IGN with SIG_DFL, automatically
// creating the signal handler configuration we want.
//
// getpid might seem like a strange choice of signal handler, but consider the following:
//
// 1) on all supported ABIs, calling a function that returns int as if it returned void is
// harmless --- the return value is stuffed into a register (for example, EAX on x86
// 32-bit) that the caller ignores (EAX is caller-saved),
//
// 2) on all supported ABIs, calling a function with extra arguments is safe --- the callee
// ignores the extra arguments, which are passed either in caller-saved registers or in
// stack locations that the caller [1] cleans up upon return,
//
// 3) getpid is void(*)(int); signal handlers are void(*)(int), and
//
// 4) getpid is async-signal-safe according to POSIX.
//
// Therefore, it is safe to set getpid _as_ a signal handler. It does nothing, exactly as we
// want, and gets reset to SIG_DFL in subprocesses. If we were a C program, we'd just define a
// no-op function of the correct signature to use as a handler, but we're using JNA, and while
// JNA does have the ability to C function pointer to a Java function, it cannot create an
// async-signal-safe C function, since calls into Java are inherently signal-unsafe.
//
// [1] Win32 32-bit x86 stdcall is an exception to this general rule --- because the callee
// cleans up its stack --- but we don't run this code on Windows.
//
Libc.INSTANCE.signal(Libc.Constants.SIGHUP, libcLibrary.getFunction("getpid"));
initialized = true;
}
public static synchronized void disarm() {
armed = false;
}
public static synchronized void killBgProcesses() {
if (initialized) {
armed = true;
Libc.INSTANCE.kill(0 /* my process group */, Libc.Constants.SIGHUP);
}
}
private BgProcessKiller() {}
private static void checkArmedStatus() {
if (armed) {
throw new BuckIsDyingException("process creation blocked due to pending nailgun exit");
}
}
/**
* Use this method instead of {@link ProcessBuilder#start} in order to properly synchronize with
* signal handling.
*/
public static synchronized Process startProcess(ProcessBuilder pb) throws IOException {
checkArmedStatus();
return pb.start();
}
/**
* Use this method instead of {@link NuProcessBuilder#start} in order to properly synchronize with
* signal handling.
*/
public static synchronized NuProcess startProcess(NuProcessBuilder pb) throws IOException {
checkArmedStatus();
return pb.start();
}
}