blob: 9f59a93613a32f44d4c243260e2d1a269a21317b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2015 IBM Corporation and others.
* 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 - Initial API and implementation
* James Blackburn (Broadcom Corp.) - ongoing development
* Lars Vogel <Lars.Vogel@vogella.com> - Bug 473427
*******************************************************************************/
package org.eclipse.core.internal.refresh;
import java.util.ArrayList;
import org.eclipse.core.internal.resources.Resource;
import org.eclipse.core.internal.utils.Messages;
import org.eclipse.core.internal.utils.Policy;
import org.eclipse.core.resources.*;
import org.eclipse.core.resources.refresh.IRefreshMonitor;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.osgi.framework.Bundle;
/**
* The <code>PollingMonitor</code> is an <code>IRefreshMonitor</code> that
* polls the file system rather than registering natively for call-backs.
*
* The polling monitor operates in iterations that span multiple invocations
* of the job's run method. At the beginning of an iteration, a set of
* all resource roots is collected. Each time the job runs, it removes items
* from the set and searches for changes for a fixed period of time.
* This ensures that the refresh job is broken into very small discrete
* operations that do not interrupt the user's main-line activity.
*
* @since 3.0
*/
public class PollingMonitor extends Job implements IRefreshMonitor {
/**
* The maximum duration of a single polling iteration
*/
private static final long MAX_DURATION = 250;
/**
* The amount of time that a changed root should remain hot.
*/
private static final long HOT_ROOT_DECAY = 90000;
/**
* The minimum delay between executions of the polling monitor
*/
private static final long MIN_FREQUENCY = 4000;
/**
* The roots of resources which should be polled
*/
private final ArrayList<IResource> resourceRoots;
/**
* The resources remaining to be refreshed in this iteration
*/
private final ArrayList<IResource> toRefresh;
/**
* The root that has most recently been out of sync
*/
private IResource hotRoot;
/**
* The time the hot root was last refreshed
*/
private long hotRootTime;
private final RefreshManager refreshManager;
/**
* True if this job has never been run. False otherwise.
*/
private boolean firstRun = true;
/**
* Creates a new polling monitor.
*/
public PollingMonitor(RefreshManager manager) {
super(Messages.refresh_pollJob);
this.refreshManager = manager;
setPriority(Job.DECORATE);
setSystem(true);
resourceRoots = new ArrayList<>();
toRefresh = new ArrayList<>();
}
/**
* Add the given root to the list of roots that need to be polled.
*/
public synchronized void monitor(IResource root) {
resourceRoots.add(root);
schedule(MIN_FREQUENCY);
}
/**
* Polls the file system under the root containers for changes.
*/
@Override
protected IStatus run(IProgressMonitor monitor) {
//sleep until resources plugin has finished starting
if (firstRun) {
firstRun = false;
Bundle bundle = Platform.getBundle(ResourcesPlugin.PI_RESOURCES);
long waitStart = System.currentTimeMillis();
while (bundle.getState() == Bundle.STARTING) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
//ignore
}
//don't wait forever
if ((System.currentTimeMillis() - waitStart) > 90000)
break;
}
}
long time = System.currentTimeMillis();
//check to see if we need to start an iteration
if (toRefresh.isEmpty()) {
beginIteration();
if (Policy.DEBUG_AUTO_REFRESH)
Policy.debug(RefreshManager.DEBUG_PREFIX + "New polling iteration on " + toRefresh.size() + " roots"); //$NON-NLS-1$ //$NON-NLS-2$
}
final int oldSize = toRefresh.size();
if (Policy.DEBUG_AUTO_REFRESH)
Policy.debug(RefreshManager.DEBUG_PREFIX + "started polling"); //$NON-NLS-1$
//refresh the hot root if applicable
if (time - hotRootTime > HOT_ROOT_DECAY)
hotRoot = null;
else if (hotRoot != null && !monitor.isCanceled())
poll(hotRoot);
//process roots that have not yet been refreshed this iteration
final long loopStart = System.currentTimeMillis();
while (!toRefresh.isEmpty()) {
if (monitor.isCanceled())
break;
poll(toRefresh.remove(toRefresh.size() - 1));
//stop the iteration if we have exceed maximum duration
if (System.currentTimeMillis() - loopStart > MAX_DURATION)
break;
}
time = System.currentTimeMillis() - time;
if (Policy.DEBUG_AUTO_REFRESH)
Policy.debug(RefreshManager.DEBUG_PREFIX + "polled " + (oldSize - toRefresh.size()) + " roots in " + time + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
//reschedule automatically - shouldRun will cancel if not needed
//make sure it doesn't run more than 5% of the time
long delay = Math.max(MIN_FREQUENCY, time * 20);
//back off even more if there are other jobs running
if (!getJobManager().isIdle())
delay *= 2;
if (Policy.DEBUG_AUTO_REFRESH)
Policy.debug(RefreshManager.DEBUG_PREFIX + "rescheduling polling job in: " + delay / 1000 + " seconds"); //$NON-NLS-1$ //$NON-NLS-2$
//don't reschedule the job if the resources plugin has been shut down
Bundle bundle = Platform.getBundle(ResourcesPlugin.PI_RESOURCES);
if (bundle != null && bundle.getState() == Bundle.ACTIVE)
schedule(delay);
return Status.OK_STATUS;
}
/**
* Instructs the polling job to do one complete iteration of all workspace roots, and
* then discard itself. This is used when
* the refresh manager is first turned on if there is a native monitor installed (which
* don't handle changes that occurred while the monitor was turned off).
*/
void runOnce() {
synchronized (this) {
//add all roots to the refresh list, but not to the real set of roots
//this will cause the job to never run again once it has exhausted
//the set of roots to refresh
IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects(IContainer.INCLUDE_HIDDEN);
for (IProject project : projects)
toRefresh.add(project);
}
schedule(MIN_FREQUENCY);
}
private void poll(IResource resource) {
if (resource.isSynchronized(IResource.DEPTH_INFINITE))
return;
//don't refresh links with no local content
if (resource.isLinked() && !((Resource) resource).getStore().fetchInfo().exists())
return;
//submit refresh request
refreshManager.refresh(resource);
hotRoot = resource;
hotRootTime = System.currentTimeMillis();
if (Policy.DEBUG_AUTO_REFRESH)
Policy.debug(RefreshManager.DEBUG_PREFIX + "new hot root: " + resource); //$NON-NLS-1$
}
@Override
public boolean shouldRun() {
//only run if there is something to refresh
return !resourceRoots.isEmpty() || !toRefresh.isEmpty();
}
/**
* Copies the resources to be polled into the list of resources
* to refresh this iteration. This method is synchronized to
* guard against concurrent access to the resourceRoots field.
*/
private synchronized void beginIteration() {
toRefresh.addAll(resourceRoots);
if (hotRoot != null)
toRefresh.remove(hotRoot);
}
/*
* @see org.eclipse.core.resources.refresh.IRefreshMonitor#unmonitor(IContainer)
*/
@Override
public synchronized void unmonitor(IResource resource) {
if (resource == null)
resourceRoots.clear();
else
resourceRoots.remove(resource);
if (resourceRoots.isEmpty())
cancel();
}
}