/******************************************************************************* * Copyright (c) 2001, 2010 Mathew A. Nelson and Robocode contributors * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://robocode.sourceforge.net/license/epl-v10.html * * Contributors: * Mathew A. Nelson * - Initial API and implementation * Flemming N. Larsen * - Code cleanup * - Updated to use methods from the Logger, which replaces logger methods * that have been (re)moved from the robocode.util.Utils class * - Moved the stopThread() method from the RobocodeDeprecated class into * this class * - Bugfix: The waitForStop() was using 'runThreadGroup.activeCount > 0' * instead of runThread.isAlive() causing some robots to be forced to stop. * In the same time this method was simplified up updated for faster CPU's * Pavel Savara * - moved to RobotProxy side * - forceStop is faster and smarter * - start of thread is creating safe ATW queue *******************************************************************************/ package net.sf.robocode.host.security; import net.sf.robocode.host.IHostedThread; import net.sf.robocode.host.IThreadManager; import net.sf.robocode.io.Logger; import static net.sf.robocode.io.Logger.logError; import static net.sf.robocode.io.Logger.logMessage; import static net.sf.robocode.io.Logger.logWarning; import robocode.exception.RobotException; import java.lang.reflect.InvocationTargetException; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.HashMap; import java.util.Map; /** * @author Mathew A. Nelson (original) * @author Flemming N. Larsen (contributor) */ public class RobotThreadManager { private final IHostedThread robotProxy; private Thread runThread; private ThreadGroup runThreadGroup; private Object awtForThreadGroup; private final Map<Thread, Disposal> disposeAppContextThreadMap = new HashMap<Thread, Disposal>(); public RobotThreadManager(IHostedThread robotProxy) { this.robotProxy = robotProxy; createThreadGroup(); } public void cleanup() { try { if (runThread == null || !runThread.isAlive()) { if (!discardAWT()) { runThreadGroup.destroy(); } } else { Logger.logWarning("Could not destroy " + runThread.getName()); } } catch (Exception e) { Logger.logError("Could not destroy " + runThreadGroup.getName(), e); } } public void initAWT() { if (awtForThreadGroup == null) { awtForThreadGroup = AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { return createNewAppContext(); } }); } } public boolean discardAWT() { boolean res = false; if (awtForThreadGroup != null && !(awtForThreadGroup instanceof Integer)) { res = disposeAppContext(awtForThreadGroup); awtForThreadGroup = null; } return res; } public void checkRunThread() { if (Thread.currentThread() != runThread) { throw new RobotException("You cannot take action in this thread!"); } } public void start(IThreadManager threadManager) { try { threadManager.addThreadGroup(runThreadGroup, robotProxy); runThread = new Thread(runThreadGroup, robotProxy, robotProxy.getStatics().getName()); runThread.setDaemon(true); runThread.setPriority(Thread.NORM_PRIORITY - 1); runThread.setContextClassLoader(this.robotProxy.getRobotClassloader()); runThread.start(); } catch (Exception e) { logError("Exception starting thread", e); } } /** * @return true as peaceful stop */ public boolean waitForStop() { boolean isAlive = false; if (runThread != null && runThread.isAlive()) { runThread.interrupt(); waitForStop(runThread); isAlive = runThread.isAlive(); } Thread[] threads = new Thread[100]; runThreadGroup.enumerate(threads); for (Thread thread : threads) { if (thread != null && thread != runThread && thread.isAlive()) { thread.interrupt(); waitForStop(thread); isAlive |= thread.isAlive(); } } if (isAlive) { if (!System.getProperty("NOSECURITY", "false").equals("true")) { logError("Robot " + robotProxy.getStatics().getName() + " is not stopping. Forcing a stop."); // Force the robot to stop return forceStop(); } else { logError( "Robot " + robotProxy.getStatics().getName() + " is still running. Not stopping it because security is off."); } } return true; } /** * @return true as peaceful stop */ public boolean forceStop() { int res = stopSteps(runThread); Thread[] threads = new Thread[100]; runThreadGroup.enumerate(threads); for (Thread thread : threads) { if (thread != null && thread != runThread && thread.isAlive()) { res += stopSteps(thread); } } if (res > 0) { robotProxy.println("SYSTEM: This robot has been stopped. No score will be generated."); // recycle thread group createThreadGroup(); } runThread = null; return res == 0; } /** * @param t thread to stop * @return 0 as peaceful stop */ private int stopSteps(Thread t) { if (t != null && t.isAlive()) { interrupt(t); if (t.isAlive()) { stop(t); } if (t.isAlive()) { // noinspection deprecation // t.suspend(); logWarning("Unable to stop thread: " + runThread.getName()); } else { logMessage(robotProxy.getStatics().getName() + " has been stopped."); } return 1; } return 0; } @SuppressWarnings("deprecation") private void stop(Thread t) { if (t != null) { // noinspection deprecation t.stop(); try { t.join(1500); } catch (InterruptedException e) { // Immediately reasserts the exception by interrupting the caller thread itself Thread.currentThread().interrupt(); } } } private void interrupt(Thread t) { if (t != null) { try { t.setPriority(Thread.MIN_PRIORITY); } catch (NullPointerException e) { logError("Sometimes this occurs in the Java core?!", e); } t.interrupt(); try { t.join(500); } catch (InterruptedException e) { // Immediately reasserts the exception by interrupting the caller thread itself Thread.currentThread().interrupt(); } } } private void waitForStop(Thread thread) { for (int j = 0; j < 100 && thread.isAlive(); j++) { if (j == 50) { logMessage( "Waiting for robot " + robotProxy.getStatics().getName() + " to stop thread " + thread.getName()); } try { Thread.sleep(10); } catch (InterruptedException e) { // Immediately reasserts the exception by interrupting the caller thread itself Thread.currentThread().interrupt(); break; // We are in a loop } } } private void createThreadGroup() { runThreadGroup = new ThreadGroup(robotProxy.getStatics().getName()); // bit lower than battle have runThreadGroup.setMaxPriority(Thread.NORM_PRIORITY - 1); } public Object createNewAppContext() { // Add the current thread to our disposeAppContextThreadMap if it does not exit already if (!disposeAppContextThreadMap.containsKey(Thread.currentThread())) { disposeAppContextThreadMap.put(Thread.currentThread(), new Disposal()); } // same as SunToolkit.createNewAppContext(); // we can't assume that we are always on Suns JVM, so we can't reference it directly // why we call that ? Because SunToolkit is caching AWTQueue instance form main thread group and use it on robots threads // and he is not asking us for checkAwtEventQueueAccess above try { final Class<?> sunToolkit = ClassLoader.getSystemClassLoader().loadClass("sun.awt.SunToolkit"); // We need to wait for the sun.awt.AppContext.dispose() to complete for the current thread before creating // a new AppContext for it. Otherwise the AWT EventQueue fires events like WINDOW_CLOSE to our main window // which closes the entire application due to a System.exit() Disposal disposal = disposeAppContextThreadMap.get(Thread.currentThread()); synchronized (disposal) { while (disposal.isDisposing) { try { disposal.wait(); } catch (InterruptedException e) { return -1; } } } // Call sun.awt.SunToolkit.createNewAppContext() to create a new AppContext for the current thread return sunToolkit.getDeclaredMethod("createNewAppContext").invoke(null); } catch (ClassNotFoundException e) { // we are not on sun JVM return -1; } catch (NoSuchMethodException e) { throw new Error("Looks like SunVM but unable to assure secured AWTQueue, sorry", e); } catch (InvocationTargetException e) { throw new Error("Looks like SunVM but unable to assure secured AWTQueue, sorry", e); } catch (IllegalAccessException e) { throw new Error("Looks like SunVM but unable to assure secured AWTQueue, sorry", e); } // end: same as SunToolkit.createNewAppContext(); } public boolean disposeAppContext(final Object appContext) { // This method should must not be used when the AWT is running in headless mode! // Bugfix [2833271] IllegalThreadStateException with the AWT-Shutdown thread. // Read more about headless mode here: // http://java.sun.com/developer/technicalArticles/J2SE/Desktop/headless/ // This check makes sure we exit, if the AWT is running in headless mode if (System.getProperty("java.awt.headless", "false").equals("true")) { return false; } // same as AppContext.dispose(); try { final Class<?> sunToolkit = ClassLoader.getSystemClassLoader().loadClass("sun.awt.AppContext"); // We run this in a thread, as invoking the AppContext.dispose() method sometimes takes several // seconds, and thus causes the cleanup of a battle to hang, which is annoying when trying to restart // a battle. new Thread(new Runnable() { public void run() { Disposal disposal = disposeAppContextThreadMap.get(Thread.currentThread()); try { // Signal that the AppContext for the current thread is being disposed (start) if (disposal != null) { synchronized (disposal) { disposal.isDisposing = true; disposal.notifyAll(); } } // Call sun.awt.AppContext.dispose(appContext) to dispose the AppContext for the current thread sunToolkit.getDeclaredMethod("dispose").invoke(appContext); } catch (Exception e) { logError(e); } finally { // Signal that the AppContext for the current thread has been disposed (finish) if (disposal != null) { synchronized (disposal) { disposal.isDisposing = false; disposal.notifyAll(); } } } } }, "DisposeAppContext").start(); return true; } catch (ClassNotFoundException e) { logError(e); } return false; // end: same as AppContext.dispose(); } private static class Disposal { boolean isDisposing; } }