1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tradefed.sandbox; 17 18 import com.android.annotations.VisibleForTesting; 19 import com.android.ddmlib.Log.LogLevel; 20 import com.android.tradefed.command.CommandRunner.ExitCode; 21 import com.android.tradefed.command.ICommandScheduler; 22 import com.android.tradefed.command.ICommandScheduler.IScheduledInvocationListener; 23 import com.android.tradefed.config.ConfigurationException; 24 import com.android.tradefed.config.GlobalConfiguration; 25 import com.android.tradefed.device.FreeDeviceState; 26 import com.android.tradefed.device.ITestDevice; 27 import com.android.tradefed.device.NoDeviceException; 28 import com.android.tradefed.invoker.IInvocationContext; 29 import com.android.tradefed.invoker.InvocationContext; 30 import com.android.tradefed.invoker.proto.InvocationContext.Context; 31 import com.android.tradefed.log.LogUtil.CLog; 32 import com.android.tradefed.util.FileUtil; 33 import com.android.tradefed.util.RunUtil; 34 import com.android.tradefed.util.SerializationUtil; 35 36 import org.json.JSONException; 37 import org.json.JSONObject; 38 39 import java.io.File; 40 import java.io.FileInputStream; 41 import java.io.IOException; 42 import java.io.PrintStream; 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.List; 46 import java.util.Map; 47 48 import sun.misc.Signal; 49 import sun.misc.SignalHandler; 50 51 /** Runner associated with a {@link TradefedSandbox} that will allow executing the sandbox. */ 52 public class TradefedSandboxRunner { 53 public static final String EXCEPTION_KEY = "serialized_exception"; 54 public static final String DEBUG_THREAD_KEY = "debug_thread"; 55 56 private ICommandScheduler mScheduler; 57 private ExitCode mErrorCode = ExitCode.NO_ERROR; 58 TradefedSandboxRunner()59 public TradefedSandboxRunner() {} 60 getErrorCode()61 public ExitCode getErrorCode() { 62 return mErrorCode; 63 } 64 65 /** Initialize the required global configuration. */ 66 @VisibleForTesting initGlobalConfig(String[] args)67 void initGlobalConfig(String[] args) throws ConfigurationException { 68 GlobalConfiguration.createGlobalConfiguration(args); 69 } 70 71 /** Get the {@link ICommandScheduler} instance from the global configuration. */ 72 @VisibleForTesting getCommandScheduler()73 ICommandScheduler getCommandScheduler() { 74 return GlobalConfiguration.getInstance().getCommandScheduler(); 75 } 76 77 /** Prints the exception stack to stderr. */ 78 @VisibleForTesting printStackTrace(Throwable e)79 void printStackTrace(Throwable e) { 80 e.printStackTrace(); 81 File serializedException = null; 82 try { 83 serializedException = SerializationUtil.serialize(e); 84 JSONObject json = new JSONObject(); 85 json.put(EXCEPTION_KEY, serializedException.getAbsolutePath()); 86 System.err.println(json.toString()); 87 System.err.flush(); 88 } catch (IOException | JSONException io) { 89 io.printStackTrace(); 90 FileUtil.deleteFile(serializedException); 91 } 92 } 93 94 /** 95 * The main method to run the command. 96 * 97 * @param args the config name to run and its options 98 */ run(String[] args)99 public void run(String[] args) { 100 if (System.getenv(DEBUG_THREAD_KEY) != null) { 101 new DebugThread().start(); 102 } 103 List<String> argList = new ArrayList<>(Arrays.asList(args)); 104 IInvocationContext context = null; 105 106 if (argList.size() < 2) { 107 mErrorCode = ExitCode.THROWABLE_EXCEPTION; 108 printStackTrace( 109 new RuntimeException("TradefedContainerRunner expect at least 2 args.")); 110 return; 111 } 112 113 File contextFile = new File(argList.remove(0)); 114 try { 115 Context c = Context.parseDelimitedFrom(new FileInputStream(contextFile)); 116 context = InvocationContext.fromProto(c); 117 } catch (IOException e) { 118 // Fallback to compatible old way 119 // TODO: Delete when parent has been deployed. 120 try { 121 context = (IInvocationContext) SerializationUtil.deserialize(contextFile, false); 122 } catch (IOException e2) { 123 printStackTrace(e); 124 printStackTrace(e2); 125 mErrorCode = ExitCode.THROWABLE_EXCEPTION; 126 return; 127 } 128 } 129 130 try { 131 initGlobalConfig(new String[] {}); 132 mScheduler = getCommandScheduler(); 133 mScheduler.start(); 134 SignalHandler handler = 135 new SignalHandler() { 136 @Override 137 public void handle(Signal sig) { 138 CLog.logAndDisplay( 139 LogLevel.INFO, 140 String.format( 141 "Received signal %s. Shutting down.", sig.getName())); 142 mScheduler.shutdownHard(false); 143 } 144 }; 145 Signal.handle(new Signal("TERM"), handler); 146 mScheduler.execCommand( 147 context, new StubScheduledInvocationListener(), argList.toArray(new String[0])); 148 } catch (NoDeviceException e) { 149 printStackTrace(e); 150 mErrorCode = ExitCode.NO_DEVICE_ALLOCATED; 151 } catch (ConfigurationException e) { 152 printStackTrace(e); 153 mErrorCode = ExitCode.CONFIG_EXCEPTION; 154 } finally { 155 mScheduler.shutdownOnEmpty(); 156 } 157 try { 158 mScheduler.join(); 159 // If no error code has been raised yet, we checked the invocation error code. 160 if (ExitCode.NO_ERROR.equals(mErrorCode)) { 161 mErrorCode = mScheduler.getLastInvocationExitCode(); 162 } 163 } catch (InterruptedException e) { 164 e.printStackTrace(); 165 mErrorCode = ExitCode.THROWABLE_EXCEPTION; 166 } 167 if (!ExitCode.NO_ERROR.equals(mErrorCode) 168 && mScheduler.getLastInvocationThrowable() != null) { 169 // Print error to the stderr so that it can be recovered. 170 printStackTrace(mScheduler.getLastInvocationThrowable()); 171 } 172 } 173 main(final String[] mainArgs)174 public static void main(final String[] mainArgs) { 175 TradefedSandboxRunner console = new TradefedSandboxRunner(); 176 console.run(mainArgs); 177 System.exit(console.getErrorCode().getCodeValue()); 178 } 179 180 /** A stub {@link IScheduledInvocationListener} that does nothing. */ 181 public static class StubScheduledInvocationListener implements IScheduledInvocationListener { 182 @Override invocationComplete( IInvocationContext metadata, Map<ITestDevice, FreeDeviceState> devicesStates)183 public void invocationComplete( 184 IInvocationContext metadata, Map<ITestDevice, FreeDeviceState> devicesStates) { 185 // do nothing 186 } 187 } 188 189 private class DebugThread extends Thread { 190 DebugThread()191 DebugThread() { 192 setDaemon(true); 193 setName("TradefedSandboxRunner-DebugThread"); 194 } 195 196 @Override run()197 public void run() { 198 System.out.println("Starting the DebugThread"); 199 try { 200 File dumpStack = FileUtil.createTempFile("debugger-thread-stacks", ".txt"); 201 while (true) { 202 RunUtil.getDefault().sleep(300000L); 203 try (PrintStream ps = new PrintStream(dumpStack)) { 204 dumpStacks(ps); 205 } 206 207 printMemoryMessage(); 208 } 209 } catch (IOException e) { 210 System.err.println(e); 211 } 212 } 213 } 214 dumpStacks(PrintStream ps)215 private void dumpStacks(PrintStream ps) { 216 Map<Thread, StackTraceElement[]> threadMap = Thread.getAllStackTraces(); 217 for (Map.Entry<Thread, StackTraceElement[]> threadEntry : threadMap.entrySet()) { 218 dumpThreadStack(threadEntry.getKey(), threadEntry.getValue(), ps); 219 } 220 } 221 dumpThreadStack(Thread thread, StackTraceElement[] trace, PrintStream ps)222 private void dumpThreadStack(Thread thread, StackTraceElement[] trace, PrintStream ps) { 223 printLine(String.format("%s", thread), ps); 224 for (int i = 0; i < trace.length; i++) { 225 printLine(String.format("\t%s", trace[i]), ps); 226 } 227 printLine("", ps); 228 } 229 printLine(String output, PrintStream pw)230 private void printLine(String output, PrintStream pw) { 231 pw.print(output); 232 pw.println(); 233 } 234 printMemoryMessage()235 private void printMemoryMessage() { 236 long diff = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); 237 System.out.println( 238 String.format( 239 "=====\nTotal: %s, Free: %s, Diff: %s\n=====", 240 Runtime.getRuntime().totalMemory(), 241 Runtime.getRuntime().freeMemory(), 242 diff)); 243 } 244 } 245