diff --git a/masterfs/apichanges.xml b/masterfs/apichanges.xml --- a/masterfs/apichanges.xml +++ b/masterfs/apichanges.xml @@ -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. diff --git a/masterfs/manifest.mf b/masterfs/manifest.mf --- a/masterfs/manifest.mf +++ b/masterfs/manifest.mf @@ -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 diff --git a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/utils/FileChangedManager.java b/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/utils/FileChangedManager.java --- a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/utils/FileChangedManager.java +++ b/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/utils/FileChangedManager.java @@ -40,8 +40,10 @@ import java.io.File; import java.security.Permission; +import java.util.concurrent.Callable; 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 +60,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 +144,15 @@ return retval; } + public static T priorityIO(Callable callable) throws Exception { + try { + priorityIO.incrementAndGet(); + return callable.call(); + } finally { + priorityIO.decrementAndGet(); + } + } + private static boolean isIdleIO() { return IDLE_IO.get() != null; } @@ -170,7 +183,7 @@ throw new InterruptedException(msg); } int l = pingIO(0); - if (l < load) { + if (l < load && priorityIO.get() == 0) { return; } synchronized (IDLE_IO) { diff --git a/masterfs/src/org/netbeans/modules/masterfs/providers/ProvidedExtensions.java b/masterfs/src/org/netbeans/modules/masterfs/providers/ProvidedExtensions.java --- a/masterfs/src/org/netbeans/modules/masterfs/providers/ProvidedExtensions.java +++ b/masterfs/src/org/netbeans/modules/masterfs/providers/ProvidedExtensions.java @@ -44,6 +44,8 @@ import java.io.File; import java.io.IOException; import java.util.List; +import java.util.concurrent.Callable; +import org.netbeans.modules.masterfs.filebasedfs.utils.FileChangedManager; import org.openide.filesystems.FileObject; /** @@ -209,4 +211,16 @@ 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 callable the {@link Callable} to run + * @throws Exception the exception thrown by the callable + * @since 2.35 + */ + public static T priorityIO(Callable run) throws Exception { + return FileChangedManager.priorityIO(run); + } } diff --git a/masterfs/test/unit/src/org/netbeans/modules/masterfs/SlowRefreshAndPriorityIOTest.java b/masterfs/test/unit/src/org/netbeans/modules/masterfs/SlowRefreshAndPriorityIOTest.java new file mode 100644 --- /dev/null +++ b/masterfs/test/unit/src/org/netbeans/modules/masterfs/SlowRefreshAndPriorityIOTest.java @@ -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"); + } + } +} diff --git a/parsing.api/nbproject/project.xml b/parsing.api/nbproject/project.xml --- a/parsing.api/nbproject/project.xml +++ b/parsing.api/nbproject/project.xml @@ -101,7 +101,7 @@ 2 - 2.22 + 2.25 diff --git a/parsing.api/src/org/netbeans/modules/parsing/impl/TaskProcessor.java b/parsing.api/src/org/netbeans/modules/parsing/impl/TaskProcessor.java --- a/parsing.api/src/org/netbeans/modules/parsing/impl/TaskProcessor.java +++ b/parsing.api/src/org/netbeans/modules/parsing/impl/TaskProcessor.java @@ -53,6 +53,7 @@ import java.util.Map; import java.util.Set; import java.util.WeakHashMap; +import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; @@ -190,7 +191,13 @@ } } lockCount++; - task.run (); + Utilities.runPriorityIO(new Callable() { + @Override + public Void call() throws Exception { + task.run (); + return null; + } + }); } catch (final Exception e) { final ParseException ioe = new ParseException (); ioe.initCause(e); diff --git a/parsing.api/src/org/netbeans/modules/parsing/impl/Utilities.java b/parsing.api/src/org/netbeans/modules/parsing/impl/Utilities.java --- a/parsing.api/src/org/netbeans/modules/parsing/impl/Utilities.java +++ b/parsing.api/src/org/netbeans/modules/parsing/impl/Utilities.java @@ -40,6 +40,8 @@ package org.netbeans.modules.parsing.impl; import java.util.Collections; +import java.util.concurrent.Callable; +import org.netbeans.modules.masterfs.providers.ProvidedExtensions; import org.netbeans.modules.parsing.api.Source; import org.netbeans.modules.parsing.impl.event.EventSupport; import org.netbeans.modules.parsing.impl.indexing.RepositoryUpdater; @@ -48,12 +50,18 @@ import org.openide.util.Parameters; /** - * Temporary helpe functions needed by the java.source + * Temporary helper functions needed by the java.source * @author Tomas Zezula */ public class Utilities { private Utilities () {} + + //MasterFS bridge + public static T runPriorityIO (final Callable r) throws Exception { + assert r != null; + return ProvidedExtensions.priorityIO(r); + } //Helpers for java reformatter, may be removed when new reformat api will be done public static void acquireParserLock () { diff --git a/parsing.api/src/org/netbeans/modules/parsing/spi/indexing/support/QuerySupport.java b/parsing.api/src/org/netbeans/modules/parsing/spi/indexing/support/QuerySupport.java --- a/parsing.api/src/org/netbeans/modules/parsing/spi/indexing/support/QuerySupport.java +++ b/parsing.api/src/org/netbeans/modules/parsing/spi/indexing/support/QuerySupport.java @@ -54,12 +54,14 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Callable; import java.util.logging.Level; import java.util.logging.Logger; import org.netbeans.api.java.classpath.ClassPath; import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; import org.netbeans.modules.parsing.api.indexing.IndexingManager; +import org.netbeans.modules.parsing.impl.Utilities; import org.netbeans.modules.parsing.impl.indexing.CacheFolder; import org.netbeans.modules.parsing.impl.indexing.IndexDocumentImpl; import org.netbeans.modules.parsing.impl.indexing.IndexFactoryImpl; @@ -73,6 +75,7 @@ import org.netbeans.modules.parsing.impl.indexing.lucene.LuceneIndexFactory; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileStateInvalidException; +import org.openide.util.Exceptions; import org.openide.util.Parameters; /** @@ -242,52 +245,56 @@ Parameters.notNull("fieldName", fieldName); //NOI18N Parameters.notNull("fieldValue", fieldValue); //NOI18N Parameters.notNull("kind", kind); //NOI18N + try { + return Utilities.runPriorityIO(new Callable>() { - Iterable> indices = indexerQuery.getIndices(roots); - - // check if there are stale indices - for (Pair pair : indices) { - final IndexImpl index = pair.second; - final Collection staleFiles = index.getStaleFiles(); - if (LOG.isLoggable(Level.FINE)) { - LOG.fine("Index: " + index + ", staleFiles: " + staleFiles); //NOI18N - } - - if (staleFiles != null && staleFiles.size() > 0) { - final URL root = pair.first; - LinkedList list = new LinkedList(); - for(String staleFile : staleFiles) { - try { - list.add(Util.resolveUrl(root, staleFile)); - } catch (MalformedURLException ex) { - LOG.log(Level.WARNING, null, ex); + @Override + public Collection call() throws Exception { + Iterable> indices = indexerQuery.getIndices(roots); + // check if there are stale indices + for (Pair pair : indices) { + final IndexImpl index = pair.second; + final Collection staleFiles = index.getStaleFiles(); + if (LOG.isLoggable(Level.FINE)) { + LOG.fine("Index: " + index + ", staleFiles: " + staleFiles); //NOI18N + } + if (staleFiles != null && staleFiles.size() > 0) { + final URL root = pair.first; + LinkedList list = new LinkedList(); + for (String staleFile : staleFiles) { + try { + list.add(Util.resolveUrl(root, staleFile)); + } catch (MalformedURLException ex) { + LOG.log(Level.WARNING, null, ex); + } + } + IndexingManager.getDefault().refreshIndexAndWait(root, list); + } } + final List result = new LinkedList(); + for (Pair pair : indices) { + final IndexImpl index = pair.second; + final URL root = pair.first; + final Collection pr = index.query(fieldName, fieldValue, kind, fieldsToLoad); + if (LOG.isLoggable(Level.FINE)) { + LOG.fine("query(\"" + fieldName + "\", \"" + fieldValue + "\", " + kind + ", " + printFiledToLoad(fieldsToLoad) + ") invoked at " + getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) + "[indexer=" + indexerQuery.getIndexerId() + "]:"); //NOI18N + for (IndexDocumentImpl idi : pr) { + LOG.fine(" " + idi); //NOI18N + } + LOG.fine("----"); //NOI18N + } + for (IndexDocumentImpl di : pr) { + result.add(new IndexResult(di, root)); + } + } + return result; } - - IndexingManager.getDefault().refreshIndexAndWait(root, list); - } + }); + } catch (IOException ioe) { + throw ioe; + } catch (Exception ex) { + throw new IOException(ex); } - - final List result = new LinkedList(); - for (Pair pair : indices) { - final IndexImpl index = pair.second; - final URL root = pair.first; - final Collection pr = index.query(fieldName, fieldValue, kind, fieldsToLoad); - if (LOG.isLoggable(Level.FINE)) { - LOG.fine("query(\"" + fieldName + "\", \"" + fieldValue + "\", " + kind + ", " //NOI18N - + printFiledToLoad(fieldsToLoad) + ") invoked at " //NOI18N - + getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) //NOI18N - + "[indexer=" + indexerQuery.getIndexerId() + "]:"); //NOI18N - for(IndexDocumentImpl idi : pr) { - LOG.fine(" " + idi); //NOI18N - } - LOG.fine("----"); //NOI18N - } - for (IndexDocumentImpl di : pr) { - result.add(new IndexResult(di,root)); - } - } - return result; } /** diff --git a/parsing.api/test/unit/src/org/netbeans/modules/parsing/impl/TaskProcessorTest.java b/parsing.api/test/unit/src/org/netbeans/modules/parsing/impl/TaskProcessorTest.java --- a/parsing.api/test/unit/src/org/netbeans/modules/parsing/impl/TaskProcessorTest.java +++ b/parsing.api/test/unit/src/org/netbeans/modules/parsing/impl/TaskProcessorTest.java @@ -43,6 +43,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.concurrent.Callable; import java.util.logging.Handler; import java.util.logging.LogRecord; import java.util.logging.Logger; @@ -51,6 +52,7 @@ import org.netbeans.api.editor.mimelookup.MimePath; import org.netbeans.api.editor.mimelookup.test.MockMimeLookup; import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.masterfs.providers.ProvidedExtensions; import org.netbeans.modules.parsing.api.ParserManager; import org.netbeans.modules.parsing.api.ResultIterator; import org.netbeans.modules.parsing.api.Snapshot; @@ -160,10 +162,16 @@ public void run(ResultIterator resultIterator) throws Exception { ArrayList filteredStackTrace = new ArrayList(); StackTraceElement [] stackTrace = Thread.currentThread().getStackTrace(); + boolean active = false; for(StackTraceElement e : stackTrace) { - if (!getClass().getName().equals(e.getClassName())) { - filteredStackTrace.add(e); + if (!active) { + if (e.getClassName().equals(TaskProcessor.class.getName()) && e.getMethodName().equals("runUserTask")) { + active = true; + } else { + continue; + } } + filteredStackTrace.add(e); } caller = Util.findCaller(filteredStackTrace.toArray(new StackTraceElement[filteredStackTrace.size()])); }