All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.core.internal.events.AutoBuildJob Maven / Gradle / Ivy

Go to download

AspectJ tools most notably contains the AspectJ compiler (AJC). AJC applies aspects to Java classes during compilation, fully replacing Javac for plain Java classes and also compiling native AspectJ or annotation-based @AspectJ syntax. Furthermore, AJC can weave aspects into existing class files in a post-compile binary weaving step. This library is a superset of AspectJ weaver and hence also of AspectJ runtime.

There is a newer version: 1.9.22.1
Show newest version
/*******************************************************************************
 * Copyright (c) 2003, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM - Initial API and implementation
 *     Warren Paul (Nokia) - Fix for build scheduling bug 209236
 *******************************************************************************/
package org.eclipse.core.internal.events;

import org.eclipse.core.internal.resources.ICoreConstants;
import org.eclipse.core.internal.resources.ResourceException;
import org.eclipse.core.internal.resources.Workspace;
import org.eclipse.core.internal.utils.Messages;
import org.eclipse.core.internal.utils.Policy;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.osgi.framework.Bundle;

/**
 * The job for performing workspace auto-builds, and pre- and post- autobuild
 * notification.  This job is run whenever the workspace changes regardless
 * of whether autobuild is on or off.
 */
class AutoBuildJob extends Job implements IEclipsePreferences.IPreferenceChangeListener {
	private volatile boolean avoidBuild;
	private volatile boolean buildNeeded;
	private volatile boolean forceBuild;
	/**
	 * Indicates that another thread tried to modify the workspace during
	 * the autobuild.  The autobuild should be immediately rescheduled
	 * so that it will run as soon as the next workspace modification completes.
	 */
	private volatile boolean interrupted;
	private volatile boolean isAutoBuilding;
	private volatile long lastBuild = 0L;
	private final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$
	private Workspace workspace;
	final Job noBuildJob;

	AutoBuildJob(Workspace workspace) {
		super(Messages.events_building_0);
		setRule(workspace.getRoot());
		setPriority(BUILD);
		isAutoBuilding = workspace.isAutoBuilding();
		this.workspace = workspace;
		InstanceScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES).addPreferenceChangeListener(this);
		noBuildJob = new AutoBuildOffJob();
	}

	/**
	 * Used to prevent auto-builds at the end of operations that contain
	 * explicit builds
	 */
	synchronized void avoidBuild() {
		avoidBuild = true;
	}

	/**
	 * Prevent auto-builds if the auto-build job was not interrupted.
	 */
	synchronized void avoidBuildIfNotInterrupted() {
		if (!interrupted) {
			avoidBuild();
		}
	}

	@Override
	public boolean belongsTo(Object family) {
		return family == ResourcesPlugin.FAMILY_AUTO_BUILD;
	}

	/**
	 * Instructs the build job that a build is required.  Ensure the build
	 * job is scheduled to run.
	 * @param needsBuild Whether a build is required, either due to
	 * workspace change or other factor that invalidates the built state.
	 */
	synchronized void build(boolean needsBuild) {
		buildNeeded |= needsBuild;
		long delay = computeScheduleDelay();
		int state = getState();
		if (Policy.DEBUG_BUILD_NEEDED) {
			Policy.debug("build requested, needsBuild: " + needsBuild + " state: " + state + ", delay: " + delay); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}

		//don't mess with the interrupt flag if the job is still running
		if (state != Job.RUNNING)
			setInterrupted(false);

		switch (state) {
			case Job.SLEEPING :
				if (Policy.DEBUG_BUILD_INVOKING) {
					traceMessageOrFullStack("wakeup, needsBuild: " + needsBuild + ", delay: " + delay); //$NON-NLS-1$ //$NON-NLS-2$
				}
				wakeUp(delay);
				break;
			case NONE :
				if (isAutoBuilding) {
					if (Policy.DEBUG_BUILD_INVOKING) {
						traceMessageOrFullStack("scheduled, needsBuild: " + needsBuild + ", delay: " + delay); //$NON-NLS-1$//$NON-NLS-2$
					}
					schedule(delay);
				} else {
					// The code below is required to maintain the ancient contract
					// in IResourceChangeEvent, stating that even if autobuild is
					// switched off, we still send PRE_BUILD/POST_BUILD events
					if (noBuildJob.getState() != Job.RUNNING) {
						noBuildJob.schedule(delay);
					}
				}
				break;
			case RUNNING:
				// In rare cases, we can end up in a situation where some other thread calls
				// WorkManager.setBuild(true) while the AutoBuildJob is already running.
				// In this case the AutoBuildJob should be rescheduled so we are certain that
				// the new build request is eventually processed and does not get lost.
				// Therefore, if a build is needed and the autobuild is enabled, then reschedule
				// the job
				if (isAutoBuilding && buildNeeded && !avoidBuild && Job.getJobManager().currentJob() != this) {
					if (Policy.DEBUG_BUILD_INVOKING) {
						traceMessageOrFullStack("scheduled from other thread with delay: " + delay); //$NON-NLS-1$
					}
					schedule(delay);
				}
				break;
		}
	}

	/**
	 * Should only be called after check for enabled trace flag
	 */
	private static void traceMessageOrFullStack(String message) {
		message = "AutoBuildJob: " + message; //$NON-NLS-1$
		if (Policy.DEBUG_BUILD_NEEDED_STACK) {
			Policy.debug(new RuntimeException(message));
		} else {
			Policy.debug(message);
		}
	}

	/**
	 * Computes the delay time that autobuild should be scheduled with.  The
	 * value will be in the range (MIN_BUILD_DELAY, MAX_BUILD_DELAY).
	 */
	private long computeScheduleDelay() {
		// don't assume that the last build time is always less than the current system time
		long maxDelay = Math.min(Policy.MAX_BUILD_DELAY, Policy.MAX_BUILD_DELAY + lastBuild - System.currentTimeMillis());
		return Math.max(Policy.MIN_BUILD_DELAY, maxDelay);
	}

	/**
	 * The autobuild job has been canceled.  There are two flavours of
	 * cancel, explicit user cancelation, and implicit interruption due to another
	 * thread trying to modify the workspace.  In the latter case, we must
	 * make sure the build is immediately rescheduled if it was interrupted
	 * by another thread, so that clients waiting to join autobuild will properly
	 * continue waiting
	 * @return a status with severity CANCEL
	 */
	private synchronized IStatus canceled() {
		//regardless of the form of cancelation, the build state is not happy
		buildNeeded = true;
		//schedule a rebuild immediately if build was implicitly canceled
		if (interrupted) {
			if (Policy.DEBUG_BUILD_INTERRUPT) {
				traceMessageOrFullStack("scheduling due to interruption"); //$NON-NLS-1$
			}
			setInterrupted(false);
			schedule(computeScheduleDelay());
		}
		return Status.CANCEL_STATUS;
	}

	private void doBuild(IProgressMonitor monitor) throws CoreException, OperationCanceledException {
		SubMonitor subMonitor = SubMonitor.convert(monitor, Policy.opWork + 1);
		final ISchedulingRule rule = workspace.getRuleFactory().buildRule();
		SubMonitor split = subMonitor.split(1); // will throw OperationCanceledException if autobuild canceled
		try {
			workspace.prepareOperation(rule, split);
			workspace.beginOperation(true);
			final int trigger = IncrementalProjectBuilder.AUTO_BUILD;
			workspace.broadcastBuildEvent(workspace, IResourceChangeEvent.PRE_BUILD, trigger);
			IStatus result = Status.OK_STATUS;
			try {
				// Note: shouldBuild() also resets the need/force/avoid build flags!
				if (shouldBuild())
					result = workspace.getBuildManager().build(workspace.getBuildOrder(), ICoreConstants.EMPTY_BUILD_CONFIG_ARRAY, trigger, subMonitor.split(Policy.opWork));
			} finally {
				//always send POST_BUILD if there has been a PRE_BUILD
				workspace.broadcastBuildEvent(workspace, IResourceChangeEvent.POST_BUILD, trigger);
			}
			if (!result.isOK()) {
				throw new ResourceException(result);
			}
		} finally {
			//building may close the tree, but we are still inside an
			// operation so open it
			if (workspace.getElementTree().isImmutable()) {
				workspace.newWorkingTree();
			}
			workspace.endOperation(rule, false);
		}
	}

	/**
	 * Forces an autobuild to occur, even if nothing has changed since the last
	 * build. This is used to force a build after a clean.
	 */
	public void forceBuild() {
		forceBuild = true;
	}

	/**
	 * Another thread is attempting to modify the workspace. Flag the auto-build
	 * as interrupted so that it will cancel and reschedule itself
	 */
	synchronized void interrupt() {
		//if already interrupted, do nothing
		if (interrupted)
			return;
		switch (getState()) {
			case NONE :
				return;
			case WAITING :
				//put the job to sleep if it is waiting to run
				setInterrupted(!sleep());
				break;
			case RUNNING :
				//make sure autobuild doesn't interrupt itself
				if (Job.getJobManager().currentJob() == this)
					return;
				setInterrupted(true);
				break;
		}
		//clear the autobuild avoidance flag if we were interrupted
		if (interrupted)
			avoidBuild = false;
	}

	synchronized boolean isInterrupted() {
		if (interrupted)
			return true;
		//check if another job is blocked by the build job
		if (isBlocking())
			setInterrupted(true);
		return interrupted;
	}

	@Override
	public void preferenceChange(PreferenceChangeEvent event) {
		if (!ResourcesPlugin.PREF_AUTO_BUILDING.equals(event.getKey()))
			return;
		// get the new value of auto-build directly from the preferences
		boolean wasAutoBuilding = isAutoBuilding;
		isAutoBuilding = Platform.getPreferencesService().getBoolean(ResourcesPlugin.PI_RESOURCES,
				ResourcesPlugin.PREF_AUTO_BUILDING, false, null);
		if (wasAutoBuilding && !isAutoBuilding) {
			// stop the current autobuild when autobuild has been turned off
			interrupt();
		} else if (!wasAutoBuilding && isAutoBuilding) {
			// force a build when autobuild has been turned on
			noBuildJob.cancel();
			forceBuild = true;
			build(false);
		}
	}

	@Override
	public IStatus run(IProgressMonitor monitor) {
		SubMonitor subMonitor = SubMonitor.convert(monitor, 1);
		synchronized (this) {
			if (subMonitor.isCanceled() || isInterrupted()) {
				return canceled();
			}
		}
		//if the system is shutting down, don't build
		if (systemBundle.getState() == Bundle.STOPPING)
			return Status.OK_STATUS;
		try {
			doBuild(subMonitor.split(1));
			lastBuild = System.currentTimeMillis();
			//if the build was successful then it should not be recorded as interrupted
			setInterrupted(false);
			return Status.OK_STATUS;
		} catch (OperationCanceledException e) {
			return canceled();
		} catch (CoreException sig) {
			return sig.getStatus();
		}
	}

	/**
	 * Sets or clears the interrupted flag.
	 */
	private synchronized void setInterrupted(boolean value) {
		interrupted = value;
		// we do not "cancel" in case of interrupt but let the builder decide because
		// for example JDT builder can not resume from canceled autobuild but requires full build
		// cancel = explicit user request
		// interrupt = automatic conflict solving
		if (interrupted && Policy.DEBUG_BUILD_INTERRUPT) {
			traceMessageOrFullStack("was interrupted"); //$NON-NLS-1$
		}
	}

	/**
	 * Returns true if a build is actually needed, and false otherwise.
	 */
	private synchronized boolean shouldBuild() {
		try {
			//if auto-build is off then we never run
			if (!workspace.isAutoBuilding())
				return false;
			//build if the workspace requires a build (description changes)
			if (forceBuild)
				return true;
			if (avoidBuild)
				return false;
			//return whether there have been any changes to the workspace tree.
			return buildNeeded;
		} finally {
			//regardless of the result, clear the build flags for next time
			forceBuild = avoidBuild = buildNeeded = false;
		}
	}

	/**
	 * The class is required to maintain the ancient contract in
	 * IResourceChangeEvent, stating that even if autobuild is switched off, we
	 * still should send PRE_BUILD/POST_BUILD events. This job only send events, and
	 * never triggers a build.
	 */
	private final class AutoBuildOffJob extends Job {

		private AutoBuildOffJob() {
			super("Sending build events with disabled autobuild"); //$NON-NLS-1$
			setRule(workspace.getRoot());
			setSystem(true);
		}

		@Override
		public boolean belongsTo(Object family) {
			return family == ResourcesPlugin.FAMILY_AUTO_BUILD;
		}

		@Override
		protected IStatus run(IProgressMonitor monitor) {
			// if the system is shutting down, don't build
			if (systemBundle.getState() == Bundle.STOPPING)
				return Status.OK_STATUS;
			final ISchedulingRule rule = workspace.getRuleFactory().buildRule();
			try {
				workspace.prepareOperation(rule, monitor);
				workspace.beginOperation(true);
				final int trigger = IncrementalProjectBuilder.AUTO_BUILD;
				workspace.broadcastBuildEvent(workspace, IResourceChangeEvent.PRE_BUILD, trigger);
				workspace.broadcastBuildEvent(workspace, IResourceChangeEvent.POST_BUILD, trigger);
			} catch (CoreException e) {
				return e.getStatus();
			} finally {
				try {
					workspace.endOperation(rule, false);
				} catch (CoreException e) {
					return e.getStatus();
				}
			}
			return Status.OK_STATUS;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy