blob: c442d84341ed315ec0bbeca6813492773b336780 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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.android.tradefed.sandbox;
import com.android.annotations.VisibleForTesting;
import com.android.ddmlib.Log.LogLevel;
import com.android.tradefed.command.CommandRunner.ExitCode;
import com.android.tradefed.command.ICommandScheduler;
import com.android.tradefed.command.ICommandScheduler.IScheduledInvocationListener;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.device.FreeDeviceState;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.NoDeviceException;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.InvocationContext;
import com.android.tradefed.invoker.proto.InvocationContext.Context;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.SerializationUtil;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import sun.misc.Signal;
import sun.misc.SignalHandler;
/** Runner associated with a {@link TradefedSandbox} that will allow executing the sandbox. */
public class TradefedSandboxRunner {
public static final String EXCEPTION_KEY = "serialized_exception";
public static final String DEBUG_THREAD_KEY = "debug_thread";
private ICommandScheduler mScheduler;
private ExitCode mErrorCode = ExitCode.NO_ERROR;
public TradefedSandboxRunner() {}
public ExitCode getErrorCode() {
return mErrorCode;
}
/** Initialize the required global configuration. */
@VisibleForTesting
void initGlobalConfig(String[] args) throws ConfigurationException {
GlobalConfiguration.createGlobalConfiguration(args);
}
/** Get the {@link ICommandScheduler} instance from the global configuration. */
@VisibleForTesting
ICommandScheduler getCommandScheduler() {
return GlobalConfiguration.getInstance().getCommandScheduler();
}
/** Prints the exception stack to stderr. */
@VisibleForTesting
void printStackTrace(Throwable e) {
e.printStackTrace();
File serializedException = null;
try {
serializedException = SerializationUtil.serialize(e);
JSONObject json = new JSONObject();
json.put(EXCEPTION_KEY, serializedException.getAbsolutePath());
System.err.println(json.toString());
System.err.flush();
} catch (IOException | JSONException io) {
io.printStackTrace();
FileUtil.deleteFile(serializedException);
}
}
/**
* The main method to run the command.
*
* @param args the config name to run and its options
*/
public void run(String[] args) {
if (System.getenv(DEBUG_THREAD_KEY) != null) {
new DebugThread().start();
}
List<String> argList = new ArrayList<>(Arrays.asList(args));
IInvocationContext context = null;
if (argList.size() < 2) {
mErrorCode = ExitCode.THROWABLE_EXCEPTION;
printStackTrace(
new RuntimeException("TradefedContainerRunner expect at least 2 args."));
return;
}
File contextFile = new File(argList.remove(0));
try {
Context c = Context.parseDelimitedFrom(new FileInputStream(contextFile));
context = InvocationContext.fromProto(c);
} catch (IOException e) {
// Fallback to compatible old way
// TODO: Delete when parent has been deployed.
try {
context = (IInvocationContext) SerializationUtil.deserialize(contextFile, false);
} catch (IOException e2) {
printStackTrace(e);
printStackTrace(e2);
mErrorCode = ExitCode.THROWABLE_EXCEPTION;
return;
}
}
try {
initGlobalConfig(new String[] {});
mScheduler = getCommandScheduler();
mScheduler.start();
SignalHandler handler =
new SignalHandler() {
@Override
public void handle(Signal sig) {
CLog.logAndDisplay(
LogLevel.INFO,
String.format(
"Received signal %s. Shutting down.", sig.getName()));
mScheduler.shutdownHard(false);
}
};
Signal.handle(new Signal("TERM"), handler);
// Wait 2 secs to let device discovery finish
RunUtil.getDefault().sleep(2000);
mScheduler.execCommand(
context, new StubScheduledInvocationListener(), argList.toArray(new String[0]));
} catch (NoDeviceException e) {
printStackTrace(e);
mErrorCode = ExitCode.NO_DEVICE_ALLOCATED;
} catch (ConfigurationException e) {
printStackTrace(e);
mErrorCode = ExitCode.CONFIG_EXCEPTION;
} finally {
mScheduler.shutdownOnEmpty();
}
try {
mScheduler.join();
// If no error code has been raised yet, we checked the invocation error code.
if (ExitCode.NO_ERROR.equals(mErrorCode)) {
mErrorCode = mScheduler.getLastInvocationExitCode();
}
} catch (InterruptedException e) {
e.printStackTrace();
mErrorCode = ExitCode.THROWABLE_EXCEPTION;
}
if (!ExitCode.NO_ERROR.equals(mErrorCode)
&& mScheduler.getLastInvocationThrowable() != null) {
// Print error to the stderr so that it can be recovered.
printStackTrace(mScheduler.getLastInvocationThrowable());
}
}
public static void main(final String[] mainArgs) {
TradefedSandboxRunner console = new TradefedSandboxRunner();
console.run(mainArgs);
System.exit(console.getErrorCode().getCodeValue());
}
/** A stub {@link IScheduledInvocationListener} that does nothing. */
public static class StubScheduledInvocationListener implements IScheduledInvocationListener {
@Override
public void invocationComplete(
IInvocationContext metadata, Map<ITestDevice, FreeDeviceState> devicesStates) {
// do nothing
}
}
private class DebugThread extends Thread {
DebugThread() {
setDaemon(true);
setName("TradefedSandboxRunner-DebugThread");
}
@Override
public void run() {
System.out.println("Starting the DebugThread");
try {
File dumpStack = FileUtil.createTempFile("debugger-thread-stacks", ".txt");
while (true) {
RunUtil.getDefault().sleep(300000L);
try (PrintStream ps = new PrintStream(dumpStack)) {
dumpStacks(ps);
}
printMemoryMessage();
}
} catch (IOException e) {
System.err.println(e);
}
}
}
private void dumpStacks(PrintStream ps) {
Map<Thread, StackTraceElement[]> threadMap = Thread.getAllStackTraces();
for (Map.Entry<Thread, StackTraceElement[]> threadEntry : threadMap.entrySet()) {
dumpThreadStack(threadEntry.getKey(), threadEntry.getValue(), ps);
}
}
private void dumpThreadStack(Thread thread, StackTraceElement[] trace, PrintStream ps) {
printLine(String.format("%s", thread), ps);
for (int i = 0; i < trace.length; i++) {
printLine(String.format("\t%s", trace[i]), ps);
}
printLine("", ps);
}
private void printLine(String output, PrintStream pw) {
pw.print(output);
pw.println();
}
private void printMemoryMessage() {
long diff = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println(
String.format(
"=====\nTotal: %s, Free: %s, Diff: %s\n=====",
Runtime.getRuntime().totalMemory(),
Runtime.getRuntime().freeMemory(),
diff));
}
}