| /******************************************************************************* |
| * Copyright (c) 2002, 2016 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 |
| * Mickael Istria (Red Hat Inc.) - Bug 488937 |
| * Mikael Barbero (Eclipse Foundation) - 286681 handle WAIT_ABANDONED_0 return value |
| *******************************************************************************/ |
| package org.eclipse.core.internal.resources.refresh.win32; |
| |
| import java.io.File; |
| import java.util.*; |
| import org.eclipse.core.internal.utils.Messages; |
| import org.eclipse.core.internal.utils.Policy; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.resources.refresh.IRefreshMonitor; |
| import org.eclipse.core.resources.refresh.IRefreshResult; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.osgi.util.NLS; |
| import org.osgi.framework.Bundle; |
| |
| /** |
| * A monitor that works on Win32 platforms. Provides simple notification of |
| * entire trees by reporting that the root of the tree has changed to depth |
| * DEPTH_INFINITE. |
| */ |
| class Win32Monitor extends Job implements IRefreshMonitor { |
| /** |
| * The delay between invocations of the refresh job. |
| */ |
| private static final long RESCHEDULE_DELAY = 3000; |
| /** |
| * The time to wait on blocking call to native refresh hook. |
| */ |
| private static final int WAIT_FOR_MULTIPLE_OBJECTS_TIMEOUT = 1000; |
| private static final String DEBUG_PREFIX = "Win32RefreshMonitor: "; //$NON-NLS-1$ |
| |
| /** |
| * A ChainedHandle is a linked list of handles. |
| */ |
| protected abstract class ChainedHandle extends Handle { |
| private ChainedHandle next; |
| private ChainedHandle previous; |
| |
| public abstract boolean exists(); |
| |
| public ChainedHandle getNext() { |
| return next; |
| } |
| |
| public ChainedHandle getPrevious() { |
| return previous; |
| } |
| |
| public void setNext(ChainedHandle next) { |
| this.next = next; |
| } |
| |
| public void setPrevious(ChainedHandle previous) { |
| this.previous = previous; |
| } |
| } |
| |
| protected class FileHandle extends ChainedHandle { |
| private File file; |
| |
| public FileHandle(File file) { |
| this.file = file; |
| } |
| |
| @Override |
| public boolean exists() { |
| return file.exists(); |
| } |
| |
| @Override |
| public void handleNotification() { |
| if (!isOpen()) |
| return; |
| ChainedHandle next = getNext(); |
| if (next != null) { |
| if (next.isOpen()) { |
| if (!next.exists()) { |
| if (next instanceof LinkedResourceHandle) { |
| next.close(); |
| LinkedResourceHandle linkedResourceHandle = (LinkedResourceHandle) next; |
| linkedResourceHandle.postRefreshRequest(); |
| } else { |
| next.close(); |
| } |
| ChainedHandle previous = getPrevious(); |
| if (previous != null) |
| previous.open(); |
| } |
| } else { |
| next.open(); |
| if (next.isOpen()) { |
| Handle previous = getPrevious(); |
| previous.close(); |
| if (next instanceof LinkedResourceHandle) |
| ((LinkedResourceHandle) next).postRefreshRequest(); |
| } |
| } |
| } |
| findNextChange(); |
| } |
| |
| @Override |
| public void open() { |
| if (!isOpen()) { |
| Handle next = getNext(); |
| if (next != null && next.isOpen()) { |
| openHandleOn(file); |
| } else { |
| if (exists()) { |
| openHandleOn(file); |
| } |
| Handle previous = getPrevious(); |
| if (previous != null) { |
| previous.open(); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return file.toString(); |
| } |
| } |
| |
| protected abstract class Handle { |
| protected long handleValue; |
| |
| public Handle() { |
| handleValue = Win32Natives.INVALID_HANDLE_VALUE; |
| } |
| |
| public void close() { |
| if (isOpen()) { |
| if (!Win32Natives.FindCloseChangeNotification(handleValue)) { |
| int error = Win32Natives.GetLastError(); |
| if (error != Win32Natives.ERROR_INVALID_HANDLE) |
| addException(NLS.bind(Messages.WM_errCloseHandle, Integer.toString(error))); |
| } |
| if (Policy.DEBUG_AUTO_REFRESH) |
| Policy.debug(DEBUG_PREFIX + "removed handle: " + handleValue); //$NON-NLS-1$ |
| handleValue = Win32Natives.INVALID_HANDLE_VALUE; |
| } |
| } |
| |
| private long createHandleValue(String path, boolean monitorSubtree, int flags) { |
| long handle = Win32Natives.FindFirstChangeNotification(path, monitorSubtree, flags); |
| if (handle == Win32Natives.INVALID_HANDLE_VALUE) { |
| int error = Win32Natives.GetLastError(); |
| addException(NLS.bind(Messages.WM_errCreateHandle, path, Integer.toString(error))); |
| } |
| return handle; |
| } |
| |
| public void destroy() { |
| close(); |
| } |
| |
| protected void findNextChange() { |
| if (!Win32Natives.FindNextChangeNotification(handleValue)) { |
| int error = Win32Natives.GetLastError(); |
| if (error != Win32Natives.ERROR_INVALID_HANDLE && error != Win32Natives.ERROR_SUCCESS) { |
| addException(NLS.bind(Messages.WM_errFindChange, Integer.toString(error))); |
| } |
| removeHandle(this); |
| } |
| } |
| |
| public long getHandleValue() { |
| return handleValue; |
| } |
| |
| public abstract void handleNotification(); |
| |
| public boolean isOpen() { |
| return handleValue != Win32Natives.INVALID_HANDLE_VALUE; |
| } |
| |
| public abstract void open(); |
| |
| protected void openHandleOn(File file) { |
| openHandleOn(file.getAbsolutePath(), false); |
| } |
| |
| protected void openHandleOn(IResource resource) { |
| IPath location = resource.getLocation(); |
| if (location != null) { |
| openHandleOn(location.toOSString(), true); |
| } |
| } |
| |
| private void openHandleOn(String path, boolean subtree) { |
| setHandleValue(createHandleValue(path, subtree, Win32Natives.FILE_NOTIFY_CHANGE_FILE_NAME | Win32Natives.FILE_NOTIFY_CHANGE_DIR_NAME | Win32Natives.FILE_NOTIFY_CHANGE_LAST_WRITE | Win32Natives.FILE_NOTIFY_CHANGE_SIZE)); |
| if (isOpen()) { |
| fHandleValueToHandle.put(getHandleValue(), this); |
| setHandleValueArrays(createHandleArrays()); |
| } else { |
| close(); |
| } |
| } |
| |
| protected void postRefreshRequest(IResource resource) { |
| //native callback occurs even if resource was changed within workspace |
| if (!resource.isSynchronized(IResource.DEPTH_INFINITE)) |
| refreshResult.refresh(resource); |
| } |
| |
| public void setHandleValue(long handleValue) { |
| this.handleValue = handleValue; |
| } |
| } |
| |
| protected class LinkedResourceHandle extends ChainedHandle { |
| private List<FileHandle> fileHandleChain; |
| private IResource resource; |
| |
| /** |
| * @param resource |
| */ |
| public LinkedResourceHandle(IResource resource) { |
| this.resource = resource; |
| createFileHandleChain(); |
| } |
| |
| protected void createFileHandleChain() { |
| fileHandleChain = new ArrayList<>(1); |
| File file = new File(resource.getLocation().toOSString()); |
| file = file.getParentFile(); |
| while (file != null) { |
| fileHandleChain.add(0, new FileHandle(file)); |
| file = file.getParentFile(); |
| } |
| int size = fileHandleChain.size(); |
| for (int i = 0; i < size; i++) { |
| ChainedHandle handle = fileHandleChain.get(i); |
| handle.setPrevious((i > 0) ? fileHandleChain.get(i - 1) : null); |
| handle.setNext((i + 1 < size) ? fileHandleChain.get(i + 1) : this); |
| } |
| setPrevious((size > 0) ? fileHandleChain.get(size - 1) : null); |
| } |
| |
| @Override |
| public void destroy() { |
| super.destroy(); |
| for (Iterator<FileHandle> i = fileHandleChain.iterator(); i.hasNext();) { |
| Handle handle = i.next(); |
| handle.destroy(); |
| } |
| } |
| |
| @Override |
| public boolean exists() { |
| IPath location = resource.getLocation(); |
| return location == null ? false : location.toFile().exists(); |
| } |
| |
| @Override |
| public void handleNotification() { |
| if (isOpen()) { |
| postRefreshRequest(resource); |
| findNextChange(); |
| } |
| } |
| |
| @Override |
| public void open() { |
| if (!isOpen()) { |
| if (exists()) { |
| openHandleOn(resource); |
| } |
| FileHandle handle = (FileHandle) getPrevious(); |
| if (handle != null && !handle.isOpen()) { |
| handle.open(); |
| } |
| } |
| } |
| |
| public void postRefreshRequest() { |
| postRefreshRequest(resource); |
| } |
| |
| @Override |
| public String toString() { |
| return resource.toString(); |
| } |
| } |
| |
| protected class ResourceHandle extends Handle { |
| private IResource resource; |
| |
| public ResourceHandle(IResource resource) { |
| super(); |
| this.resource = resource; |
| } |
| |
| public IResource getResource() { |
| return resource; |
| } |
| |
| @Override |
| public void handleNotification() { |
| if (isOpen()) { |
| postRefreshRequest(resource); |
| findNextChange(); |
| } |
| } |
| |
| @Override |
| public void open() { |
| if (!isOpen()) { |
| openHandleOn(resource); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return resource.toString(); |
| } |
| } |
| |
| /** |
| * Any errors that have occurred |
| */ |
| protected MultiStatus errors; |
| /** |
| * Arrays of handles, split evenly when the number of handles is larger |
| * than Win32Natives.MAXIMUM_WAIT_OBJECTS |
| */ |
| protected long[][] fHandleValueArrays; |
| /** |
| * Mapping of handles (java.lang.Long) to absolute paths |
| * (java.lang.String). |
| */ |
| protected Map<Long, Handle> fHandleValueToHandle; |
| protected IRefreshResult refreshResult; |
| |
| /* |
| * Creates a new monitor. @param result A result that will receive refresh |
| * callbacks and error notifications |
| */ |
| public Win32Monitor(IRefreshResult result) { |
| super(Messages.WM_jobName); |
| this.refreshResult = result; |
| setPriority(Job.DECORATE); |
| setSystem(true); |
| fHandleValueToHandle = new HashMap<>(1); |
| setHandleValueArrays(createHandleArrays()); |
| } |
| |
| /** |
| * Logs an exception |
| */ |
| protected synchronized void addException(String message) { |
| if (errors == null) { |
| String msg = Messages.WM_errors; |
| errors = new MultiStatus(ResourcesPlugin.PI_RESOURCES, 1, msg, null); |
| } |
| errors.add(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, message, null)); |
| } |
| |
| /* |
| * Splits the given array into arrays of length no greater than <code> max |
| * </code> . The lengths of the sub arrays differ in size by no more than |
| * one element. <p> Examples: <ul><li> If an array of size 11 is split |
| * with a max of 4, the resulting arrays are of size 4, 4, and 3. </li> |
| * <li> If an array of size 18 is split with a max of 5, the resulting |
| * arrays are of size 5, 5, 4, and 4. </li></ul> |
| */ |
| private long[][] balancedSplit(final long[] array, final int max) { |
| int elementCount = array.length; |
| // want to handle [1, max] rather than [0, max) |
| int subArrayCount = ((elementCount - 1) / max) + 1; |
| int subArrayBaseLength = elementCount / subArrayCount; |
| int overflow = elementCount % subArrayCount; |
| long[][] result = new long[subArrayCount][]; |
| int count = 0; |
| for (int i = 0; i < subArrayCount; i++) { |
| int subArrayLength = subArrayBaseLength + (overflow-- > 0 ? 1 : 0); |
| long[] subArray = new long[subArrayLength]; |
| for (int j = 0; j < subArrayLength; j++) { |
| subArray[j] = array[count++]; |
| } |
| result[i] = subArray; |
| } |
| return result; |
| } |
| |
| private Handle createHandle(IResource resource) { |
| if (resource.isLinked()) |
| return new LinkedResourceHandle(resource); |
| return new ResourceHandle(resource); |
| } |
| |
| /* |
| * Since the Win32Natives.WaitForMultipleObjects(...) method cannot accept |
| * more than a certain number of objects, we are forced to split the array |
| * of objects to monitor and monitor each one individually. <p> This method |
| * splits the list of handles into arrays no larger than |
| * Win32Natives.MAXIMUM_WAIT_OBJECTS. The arrays are balanced so that they |
| * differ in size by no more than one element. |
| */ |
| protected long[][] createHandleArrays() { |
| long[] handles; |
| // synchronized: in order to protect the map during iteration |
| synchronized (fHandleValueToHandle) { |
| Set<Long> keys = fHandleValueToHandle.keySet(); |
| int size = keys.size(); |
| if (size == 0) { |
| return new long[0][0]; |
| } |
| handles = new long[size]; |
| int count = 0; |
| for (Iterator<Long> i = keys.iterator(); i.hasNext();) { |
| handles[count++] = i.next().longValue(); |
| } |
| } |
| return balancedSplit(handles, Win32Natives.MAXIMUM_WAIT_OBJECTS); |
| } |
| |
| private Handle getHandle(IResource resource) { |
| if (resource == null) { |
| return null; |
| } |
| // synchronized: in order to protect the map during iteration |
| synchronized (fHandleValueToHandle) { |
| for (Iterator<Handle> i = fHandleValueToHandle.values().iterator(); i.hasNext();) { |
| Handle handle = i.next(); |
| if (handle instanceof ResourceHandle) { |
| ResourceHandle resourceHandle = (ResourceHandle) handle; |
| if (resourceHandle.getResource().equals(resource)) { |
| return handle; |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| /* |
| * Answers arrays of handles. The handles are split evenly when the number |
| * of handles becomes larger than Win32Natives.MAXIMUM_WAIT_OBJECTS. |
| * @return long[][] |
| */ |
| private long[][] getHandleValueArrays() { |
| return fHandleValueArrays; |
| } |
| |
| /** |
| * Adds a resource to be monitored by this native monitor |
| */ |
| public boolean monitor(IResource resource) { |
| IPath location = resource.getLocation(); |
| if (location == null) { |
| // cannot monitor remotely managed containers |
| return false; |
| } |
| Handle handle = createHandle(resource); |
| // synchronized: handle creation must be atomic |
| synchronized (this) { |
| handle.open(); |
| } |
| if (!handle.isOpen()) { |
| //ignore errors if we can't even create a handle on the resource |
| //it will fall back to polling anyway |
| errors = null; |
| return false; |
| } |
| //make sure the job is running |
| schedule(RESCHEDULE_DELAY); |
| if (Policy.DEBUG_AUTO_REFRESH) |
| Policy.debug(DEBUG_PREFIX + " added monitor for: " + resource); //$NON-NLS-1$ |
| return true; |
| } |
| |
| /** |
| * Removes the handle from the <code>fHandleValueToHandle</code> map. |
| * |
| * @param handle |
| * a handle, not <code>null</code> |
| */ |
| protected void removeHandle(Handle handle) { |
| List<Handle> handles = new ArrayList<>(1); |
| handles.add(handle); |
| removeHandles(handles); |
| } |
| |
| /** |
| * Removes all of the handles in the given collection from the <code>fHandleValueToHandle</code> |
| * map. If collections from the <code>fHandleValueToHandle</code> map are |
| * used, copy them before passing them in as this method modifies the |
| * <code>fHandleValueToHandle</code> map. |
| * |
| * @param handles |
| * a collection of handles, not <code>null</code> |
| */ |
| private void removeHandles(Collection<Handle> handles) { |
| // synchronized: protect the array, removal must be atomic |
| synchronized (this) { |
| for (Iterator<Handle> i = handles.iterator(); i.hasNext();) { |
| Handle handle = i.next(); |
| fHandleValueToHandle.remove(handle.getHandleValue()); |
| handle.destroy(); |
| } |
| setHandleValueArrays(createHandleArrays()); |
| } |
| } |
| |
| /* |
| * @see java.lang.Runnable#run() |
| */ |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| long start = -System.currentTimeMillis(); |
| if (Policy.DEBUG_AUTO_REFRESH) |
| Policy.debug(DEBUG_PREFIX + "job started."); //$NON-NLS-1$ |
| try { |
| long[][] handleArrays = getHandleValueArrays(); |
| monitor.beginTask(Messages.WM_beginTask, handleArrays.length); |
| // If changes occur to the list of handles, |
| // ignore them until the next time through the loop. |
| for (int i = 0, length = handleArrays.length; i < length; i++) { |
| if (monitor.isCanceled()) |
| return Status.CANCEL_STATUS; |
| waitForNotification(handleArrays[i]); |
| monitor.worked(1); |
| } |
| } finally { |
| monitor.done(); |
| start += System.currentTimeMillis(); |
| if (Policy.DEBUG_AUTO_REFRESH) |
| Policy.debug(DEBUG_PREFIX + "job finished in: " + start + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| //always reschedule the job - so it will come back after errors or cancelation |
| long delay = Math.max(RESCHEDULE_DELAY, start); |
| if (Policy.DEBUG_AUTO_REFRESH) |
| Policy.debug(DEBUG_PREFIX + "rescheduling in: " + delay / 1000 + " seconds"); //$NON-NLS-1$ //$NON-NLS-2$ |
| final Bundle bundle = Platform.getBundle(ResourcesPlugin.PI_RESOURCES); |
| //if the bundle is null then the framework has shutdown - just bail out completely (bug 98219) |
| if (bundle == null) |
| return Status.OK_STATUS; |
| //don't reschedule the job if the resources plugin has been shut down |
| if (bundle.getState() == Bundle.ACTIVE) |
| schedule(delay); |
| MultiStatus result = errors; |
| errors = null; |
| //just log native refresh failures |
| if (result != null && !result.isOK()) |
| ResourcesPlugin.getPlugin().getLog().log(result); |
| return Status.OK_STATUS; |
| } |
| |
| protected void setHandleValueArrays(long[][] arrays) { |
| fHandleValueArrays = arrays; |
| } |
| |
| @Override |
| public boolean shouldRun() { |
| return !fHandleValueToHandle.isEmpty(); |
| } |
| |
| @Override |
| public void unmonitor(IResource resource) { |
| if (resource == null) { |
| // resource == null means stop monitoring all resources |
| synchronized (fHandleValueToHandle) { |
| removeHandles(new ArrayList<>(fHandleValueToHandle.values())); |
| } |
| } else { |
| Handle handle = getHandle(resource); |
| if (handle != null) |
| removeHandle(handle); |
| } |
| //stop the job if there are no more handles |
| if (fHandleValueToHandle.isEmpty()) |
| cancel(); |
| } |
| |
| /** |
| * Performs the native call to wait for notification on one of the given |
| * handles. |
| * |
| * @param handleValues |
| * an array of handles, it must contain no duplicates. |
| */ |
| private void waitForNotification(long[] handleValues) { |
| int handleCount = handleValues.length; |
| int index = Win32Natives.WaitForMultipleObjects(handleCount, handleValues, false, WAIT_FOR_MULTIPLE_OBJECTS_TIMEOUT); |
| if (index == Win32Natives.WAIT_TIMEOUT) { |
| // nothing happened. |
| return; |
| } |
| if (index == Win32Natives.WAIT_FAILED) { |
| // we ran into a problem |
| int error = Win32Natives.GetLastError(); |
| if (error != Win32Natives.ERROR_INVALID_HANDLE && error != Win32Natives.ERROR_SUCCESS) { |
| addException(NLS.bind(Messages.WM_nativeErr, Integer.toString(error))); |
| refreshResult.monitorFailed(this, null); |
| } |
| return; |
| } |
| if (index >= Win32Natives.WAIT_ABANDONED_0) { |
| // abandoned mutex object that satisfied the wait |
| // WaitForMultipleObjects returns WAIT_ABANDONED_0 + index |
| index -= Win32Natives.WAIT_ABANDONED_0; |
| Handle handle = fHandleValueToHandle.get(handleValues[index]); |
| addException(NLS.bind(Messages.WM_mutexAbandoned, handle)); |
| refreshResult.monitorFailed(this, null); |
| return; |
| } |
| // a change occurred |
| // WaitForMultipleObjects returns WAIT_OBJECT_0 + index |
| index -= Win32Natives.WAIT_OBJECT_0; |
| Handle handle = fHandleValueToHandle.get(handleValues[index]); |
| if (handle != null) |
| handle.handleNotification(); |
| } |
| } |