/* * Copyright 2000-2013 JetBrains s.r.o. * Copyright 2014-2015 AS3Boyan * Copyright 2014-2015 Elias Ku * * 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.intellij.plugins.haxe.ide.hierarchy; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.plugins.haxe.util.HaxeDebugTimeLog; import org.apache.log4j.Level; import org.jetbrains.annotations.Nullable; import java.util.Timer; import java.util.TimerTask; /** * Created by ebishton on 1/20/15. * * Stops runaway hierarchy search tasks. Uses a java.util.Timer to stop * the background task (via canceling the ProgressIndicator), if the task * takes too long to execute. * * (Too long is currently hard coded to 15 seconds.) * */ final public class HaxeHierarchyTimeoutHandler { private static final boolean DEBUG = false; private static final Logger LOG = Logger.getInstance("#com.intellij.ide.hierarchy.HaxeHierarchyTimeoutHandler"); static { if (DEBUG) { LOG.setLevel(Level.DEBUG); } } // XXX: Probably should make this configurable somewhere in settings -- but where? private static final long max_duration_seconds = 20; private static final long max_duration = max_duration_seconds * 1000; // Track the time this takes. If too long, we'll cancel it. final private long startTime; private long stopTime; private boolean canceled; private boolean stopped; private Timer myTimer; /** * Create and start a new timeout handler. */ public HaxeHierarchyTimeoutHandler() { startTime = System.currentTimeMillis(); stopTime = 0; canceled = false; stopped = false; final HaxeHierarchyTimeoutHandler myself = this; // We have to capture the progress indicator here because the timer fires on a different // thread, and won't be able to find it. So we capture it and hold it in the closure. final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); LOG.debug("Progress Indicator is" + progressIndicator.toString()); myTimer = new Timer(getClass().getSimpleName()); myTimer.schedule( new TimerTask() { @Override public void run() { LOG.debug("Timer fired."); myself.cancel(progressIndicator); } }, max_duration); } /** * Cancel the current search. This is the workhorse when the timer fires, but is * fine to be called by another task that wants to cancel the search (such as * another search). * * Once canceled, use isCanceled() to determine if this function was called. * Use isStopped to determine if the timer was stopped via stop(), and isRunning() * to see if the tasks is still running. */ public synchronized void cancel(@Nullable ProgressIndicator progressIndicator) { if (!canceled) { myTimer.cancel(); stopTime = System.currentTimeMillis(); if (null == progressIndicator) { progressIndicator = ProgressManager.getInstance().getProgressIndicator(); } if (null != progressIndicator) { LOG.debug("Canceling the Progress Indicator."); progressIndicator.cancel(); } else { LOG.debug("No Progress Indicator to cancel."); } HaxeDebugTimeLog.logStamp(LOG, "Canceling hierarchy request. Took too long."); LOG.debug("Timer canceled after " + (stopTime - startTime) + " milliseconds."); } canceled = true; } /** * Stop this handler from firing. Destroys the underlying Timer. */ public synchronized void stop() { myTimer.cancel(); stopped = true; stopTime = System.currentTimeMillis(); LOG.debug("Timer stopped after " + (stopTime - startTime) + " milliseconds."); } /** * Checks if the maximum time has been reached, and, if so, cancels the * search process. * * NOTE: This may not be necessary now that a Timer is being employed. * * @return true if the process has been cancelled; false, otherwise. */ public synchronized boolean checkAndCancelIfNecessary() { if (!canceled && (System.currentTimeMillis() - startTime) > max_duration) { cancel(null); } return canceled; } /** * Determine whether this timer was canceled. (Either automatically, or by * calling cancel().) * * @return true if this timer was canceled; false, if not, though it may * not still be running. Use isRunning() to determine that. */ public synchronized boolean isCanceled() { return canceled; } /** * Determine whether this timer was stopped manually. * * @return true if this timer was stopped via the stop() function; false, if not. */ public synchronized boolean isStopped() { return stopped; } /** * Determine whether this timer is still running. * * @return true if the timer is still running, waiting for the allotted time to expire. */ public synchronized boolean isRunning() { return !stopped && !canceled; } /** * Post a modal dialog on screen to alert the user that the search was cancelled and * the results may not be complete. * * Warns if the process was not cancelled. */ public void postCanceledDialog(Project project) { if (!canceled) { LOG.warn("Displaying cancel message dialog when the process was not canceled."); } final Project messageProject = project; ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { // TODO: Put this message and title in a resource bundle. String title = "Call Hierarchy Search Timed Out"; String msg = "Search took too long (>" + max_duration_seconds + "seconds). Results may be incomplete."; if (DEBUG) { msg += " Canceled after " + (stopTime - startTime) + " milliseconds."; } Messages.showMessageDialog(messageProject, msg, title, Messages.getWarningIcon()); } }); } }