--- a/masterfs/apichanges.xml Wed May 05 16:38:27 2010 +0200 +++ a/masterfs/apichanges.xml Wed May 05 17:27:45 2010 +0200 @@ -46,6 +46,23 @@ MasterFileSystem API + + + ProvidedExtensions.priorityIO to suspend background refresh + + + + + +

+ ProvidedExtensions.priorityIO allows + parsing API to suspend background I/O activity after refresh + of main window. +

+
+ + +
ProvidedExtensions.refreshRecursively was added. --- a/masterfs/manifest.mf Wed May 05 16:38:27 2010 +0200 +++ a/masterfs/manifest.mf Wed May 05 17:27:45 2010 +0200 @@ -1,7 +1,7 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.masterfs/2 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/masterfs/resources/Bundle.properties -OpenIDE-Module-Specification-Version: 2.24 +OpenIDE-Module-Specification-Version: 2.25 AutoUpdate-Show-In-Client: false AutoUpdate-Essential-Module: true --- a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/utils/FileChangedManager.java Wed May 05 16:38:27 2010 +0200 +++ a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/utils/FileChangedManager.java Wed May 05 17:27:45 2010 +0200 @@ -42,6 +42,7 @@ import java.security.Permission; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import org.netbeans.modules.masterfs.filebasedfs.naming.NamingFactory; @@ -58,10 +59,12 @@ private static final int CREATE_HINT = 2; private static final int DELETE_HINT = 1; private static final int AMBIGOUS_HINT = 3; + private final ConcurrentHashMap hints = new ConcurrentHashMap(); private long shrinkTime = System.currentTimeMillis(); private static volatile long ioTime = -1; private static volatile int ioLoad; + private static final AtomicInteger priorityIO = new AtomicInteger(); private static final ThreadLocal IDLE_IO = new ThreadLocal(); private static final ThreadLocal IDLE_CALL = new ThreadLocal(); private static final ThreadLocal IDLE_ON = new ThreadLocal(); @@ -140,6 +143,15 @@ return retval; } + public static void priorityIO(Runnable run) { + try { + priorityIO.incrementAndGet(); + run.run(); + } finally { + priorityIO.decrementAndGet(); + } + } + private static boolean isIdleIO() { return IDLE_IO.get() != null; } @@ -170,7 +182,7 @@ throw new InterruptedException(msg); } int l = pingIO(0); - if (l < load) { + if (l < load && priorityIO.get() == 0) { return; } synchronized (IDLE_IO) { --- a/masterfs/src/org/netbeans/modules/masterfs/providers/ProvidedExtensions.java Wed May 05 16:38:27 2010 +0200 +++ a/masterfs/src/org/netbeans/modules/masterfs/providers/ProvidedExtensions.java Wed May 05 17:27:45 2010 +0200 @@ -44,6 +44,7 @@ import java.io.File; import java.io.IOException; import java.util.List; +import org.netbeans.modules.masterfs.filebasedfs.utils.FileChangedManager; import org.openide.filesystems.FileObject; /** @@ -209,4 +210,15 @@ public long refreshRecursively(File dir, long lastTimeStamp, List children) { return -1; } + + /** Allows registered exceptions to execute some I/O priority action. + * This will stop all other "idle I/O" operations (like background refresh + * after window is activated). + * + * @param run the runnable to run + * @since 2.35 + */ + public final void priorityIO(Runnable run) { + FileChangedManager.priorityIO(run); + } } --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ a7a9ba5aba75 Wed May 05 17:27:45 2010 +0200 @@ -0,0 +1,216 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.masterfs; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.FileOutputStream; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.netbeans.junit.NbTestCase; +import org.netbeans.junit.RandomlyFails; +import org.netbeans.modules.masterfs.filebasedfs.utils.FileChangedManager; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileEvent; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; +import org.openide.util.RequestProcessor; + +public class SlowRefreshAndPriorityIOTest extends NbTestCase { + private Logger LOG; + private FileObject testFolder; + + public SlowRefreshAndPriorityIOTest(String testName) { + super(testName); + } + + @Override + protected Level logLevel() { + return Level.FINE; + } + + @Override + protected void setUp() throws Exception { + clearWorkDir(); + + + LOG = Logger.getLogger("test." + getName()); + Logger.getLogger("org.openide.util.Mutex").setUseParentHandlers(false); + + File dir = new File(getWorkDir(), "test"); + dir.mkdirs(); + testFolder = FileUtil.toFileObject(dir); + assertNotNull("Test folder created", testFolder); + + System.setSecurityManager(new FileChangedManager()); + } + + public void testRefreshCanBeSuspendedByPriorityIO() throws Exception { + long lm = System.currentTimeMillis(); + LOG.info("starting testRefreshCanBeSuspended " + lm); + FileObject fileObject1 = testFolder.createData("fileObject1"); + assertNotNull("Just to initialize the stamp", lm); + FileObject[] arr = testFolder.getChildren(); + assertEquals("One child", 1, arr.length); + assertEquals("Right child", fileObject1, arr[0]); + + File file = FileUtil.toFile(fileObject1); + assertNotNull("File found", file); + Reference ref = new WeakReference(fileObject1); + arr = null; + fileObject1 = null; + assertGC("File Object can disappear", ref); + + class L extends FileChangeAdapter { + int cnt; + FileEvent event; + + @Override + public void fileChanged(FileEvent fe) { + LOG.info("file change " + fe.getFile()); + cnt++; + event = fe; + } + } + L listener = new L(); + testFolder.addRecursiveListener(listener); + + Thread.sleep(1000); + + FileOutputStream os = new FileOutputStream(file); + os.write(10); + os.close(); + + if (lm > file.lastModified() - 50) { + fail("New modification time shall be at last 50ms after the original one: " + (file.lastModified() - lm)); + } + + Object obj = testFolder.getFileSystem().getRoot().getAttribute("refreshSlow"); + assertNotNull("Refresh attribute found", obj); + assertTrue("It is instance of runnable: " + obj, obj instanceof Runnable); + + Runnable r = (Runnable)obj; + class AE extends ActionEvent implements Runnable { + List files = new ArrayList(); + boolean boosted; + boolean finished; + int goingIdle; + + public AE() { + super("", 0, ""); + } + + @Override + public void setSource(Object newSource) { + LOG.log(Level.INFO, "Set source called: {0}", newSource); + assertTrue(newSource instanceof Object[]); + Object[] arr = (Object[])newSource; + assertTrue("Three elements at leat ", 3 <= arr.length); + assertTrue("first is int", arr[0] instanceof Integer); + assertTrue("2nd is int", arr[1] instanceof Integer); + assertTrue("3rd is fileobject", arr[2] instanceof FileObject); + files.add((FileObject)arr[2]); + super.setSource(newSource); + } + + @Override + public void run() { + goingIdle++; + } + + public synchronized void waitBoosted() throws Exception { + while (!boosted) { + wait(); + } + } + + public synchronized void notifyBoosted() { + boosted = true; + notifyAll(); + } + } + final AE counter = new AE(); + + LOG.info("Posting AE into RP"); + // starts 5s of disk checking + RequestProcessor.Task task = RequestProcessor.getDefault().post(new Runnable() { + boolean snd; + @Override + public void run() { + if (!snd) { + snd = true; + FileChangedManager.priorityIO(this); + } else { + counter.notifyBoosted(); + try { + Thread.sleep(5000); + counter.finished = true; + } catch (InterruptedException ex) { + Exceptions.printStackTrace(ex); + } + } + } + }); + + // connect together + r.equals(counter); + + LOG.info("Waiting for I/O boost"); + counter.waitBoosted(); + LOG.info("Starting refresh"); + // do the refresh + r.run(); + LOG.info("Refresh finished"); + + assertTrue("Background I/O access needs to stop before we finish our task", counter.finished); + + assertEquals("Change notified", 1, listener.cnt); + assertEquals("Right file", file, FileUtil.toFile(listener.event.getFile())); + assertEquals("Right source", file.getParentFile(), FileUtil.toFile((FileObject)listener.event.getSource())); + if (counter.goingIdle == 0) { + fail("The I/O subsystem shall notify the action that it went idle at least once"); + } + } +}