• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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