/******************************************************************************* * Copyright (c) 2007 IBM Corporation. * 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://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package com.ibm.wala.util; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; import java.lang.management.MemoryUsage; import java.util.Collections; import java.util.List; import java.util.Map; import javax.management.ListenerNotFoundException; import javax.management.Notification; import javax.management.NotificationEmitter; import javax.management.NotificationFilter; import javax.management.NotificationListener; import javax.management.openmbean.CompositeData; import com.ibm.wala.util.MonitorUtil.IProgressMonitor; import com.ibm.wala.util.collections.HashMapFactory; import com.sun.management.GarbageCollectionNotificationInfo; /** * A class to control execution through the {@link IProgressMonitor} interface. * * This class bounds each work item with a time in milliseconds. If there is no * apparent progress within the specified bound, this class cancels itself. */ public class ProgressMaster implements IProgressMonitor { private final IProgressMonitor delegate; private volatile boolean timedOut = false; private volatile boolean tooMuchMemory = false; /** * If -1, work items can run forever. */ private final int msPerWorkItem; private final boolean checkMemory; private Timeout currentNanny; private ProgressMaster(IProgressMonitor monitor, int msPerWorkItem, boolean checkMemory) { this.delegate = monitor; this.msPerWorkItem = msPerWorkItem; this.checkMemory = checkMemory; } public static ProgressMaster make(IProgressMonitor monitor, int msPerWorkItem, boolean checkMemory) { if (monitor == null) { throw new IllegalArgumentException("null monitor"); } return new ProgressMaster(monitor, msPerWorkItem, checkMemory); } @Override public synchronized void beginTask(String name, int totalWork) { delegate.beginTask(name, totalWork); startNanny(); } private synchronized void startNanny() { killNanny(); if (msPerWorkItem >= 1 || checkMemory) { currentNanny = new Timeout(); // don't let nanny thread prevent JVM exit currentNanny.setDaemon(true); currentNanny.start(); } } public synchronized void reset() { killNanny(); setCanceled(false); timedOut = false; tooMuchMemory = false; } /** * Was the last cancel state due to a timeout? */ public boolean lastItemTimedOut() { return timedOut; } public boolean lastItemTooMuchMemory() { return tooMuchMemory; } @Override public synchronized void done() { killNanny(); delegate.done(); } private synchronized void killNanny() { if (currentNanny != null) { currentNanny.interrupt(); try { currentNanny.join(); } catch (InterruptedException e) { } currentNanny = null; } } @Override public boolean isCanceled() { return delegate.isCanceled() || timedOut || tooMuchMemory; } public void setCanceled(boolean value) { killNanny(); } /** BEGIN Custom change: subtasks and canceling */ @Override public void subTask(String subTask) { delegate.subTask(subTask); } @Override public void cancel() { setCanceled(true); } /** END Custom change: subtasks and canceling */ @Override public synchronized void worked(int work) { killNanny(); delegate.worked(work); startNanny(); } public int getMillisPerWorkItem() { return msPerWorkItem; } public static class TooMuchMemoryUsed extends Exception { } private class Timeout extends Thread { @Override public void run() { try { Map<NotificationEmitter,NotificationListener> gcListeners = Collections.emptyMap();; if (checkMemory) { gcListeners = HashMapFactory.make(); final Thread nannyThread = this; List<GarbageCollectorMXBean> gcbeans = ManagementFactory.getGarbageCollectorMXBeans(); for (GarbageCollectorMXBean gcbean : gcbeans) { final NotificationEmitter emitter = (NotificationEmitter) gcbean; NotificationFilter filter = new NotificationFilter() { @Override public boolean isNotificationEnabled(Notification notification) { return notification.getType().equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION); } }; NotificationListener listener = new NotificationListener() { @Override public void handleNotification(Notification notification, Object arg1) { GarbageCollectionNotificationInfo info = GarbageCollectionNotificationInfo.from((CompositeData) notification.getUserData()); long used = 0; for(MemoryUsage usage : info.getGcInfo().getMemoryUsageAfterGc().values()) { used += usage.getUsed(); } long max = Runtime.getRuntime().maxMemory(); if (((double)used/(double)max) > MAX_USED_MEM_BEFORE_BACKING_OUT) { tooMuchMemory = true; nannyThread.interrupt(); } } }; emitter.addNotificationListener(listener, filter, null); gcListeners.put(emitter,listener); } } Thread.sleep(msPerWorkItem); if (checkMemory) { try { for(Map.Entry<NotificationEmitter,NotificationListener> gc : gcListeners.entrySet()) { gc.getKey().removeNotificationListener(gc.getValue()); } } catch (ListenerNotFoundException e) { assert false : "cannot remove listener that was added"; } } if (isInterrupted()) { return; } timedOut = true; } catch (InterruptedException e) { return; } } private static final double MAX_USED_MEM_BEFORE_BACKING_OUT = .8; } @Override public String getCancelMessage() { return tooMuchMemory? "too much memory": timedOut? "timed out" : "unknown"; } }