Index: loaders/src/org/openide/loaders/DataObject.java =================================================================== RCS file: /cvs/openide/loaders/src/org/openide/loaders/DataObject.java,v retrieving revision 1.3 diff -u -r1.3 DataObject.java --- loaders/src/org/openide/loaders/DataObject.java 16 May 2003 12:08:42 -0000 1.3 +++ loaders/src/org/openide/loaders/DataObject.java 22 May 2003 16:45:05 -0000 @@ -503,7 +503,7 @@ public final DataObject copy (final DataFolder f) throws IOException { final DataObject[] result = new DataObject[1]; FileSystem fs = f.getPrimaryFile ().getFileSystem (); - fs.runAtomicAction (new FileSystem.AtomicAction () { + invokeAtomicAction (fs, new FileSystem.AtomicAction () { public void run () throws IOException { result[0] = handleCopy (f); } @@ -529,7 +529,7 @@ synchronized ( synchObject() ) { // the object is ready to be closed FileSystem fs = getPrimaryFile ().getFileSystem (); - fs.runAtomicAction (new FileSystem.AtomicAction () { + invokeAtomicAction (fs, new FileSystem.AtomicAction () { public void run () throws IOException { handleDelete (); item.deregister(false); @@ -574,7 +574,7 @@ // executes atomic action with renaming FileSystem fs = files[0].getFileSystem (); - fs.runAtomicAction (new FileSystem.AtomicAction () { + invokeAtomicAction (fs, new FileSystem.AtomicAction () { public void run () throws IOException { files[1] = handleRename (name); if (files[0] != files[1]) @@ -610,7 +610,7 @@ // executes atomic action for moving old = getPrimaryFile (); FileSystem fs = old.getFileSystem (); - fs.runAtomicAction (new FileSystem.AtomicAction () { + invokeAtomicAction (fs, new FileSystem.AtomicAction () { public void run () throws IOException { FileObject mf = handleMove (df); item.changePrimaryFile (mf); @@ -654,7 +654,7 @@ final DataShadow[] result = new DataShadow[1]; FileSystem fs = f.getPrimaryFile ().getFileSystem (); - fs.runAtomicAction (new FileSystem.AtomicAction () { + invokeAtomicAction (fs, new FileSystem.AtomicAction () { public void run () throws IOException { result[0] = handleCreateShadow (f); } @@ -692,7 +692,7 @@ final DataObject[] result = new DataObject[1]; FileSystem fs = f.getPrimaryFile ().getFileSystem (); - fs.runAtomicAction (new FileSystem.AtomicAction () { + invokeAtomicAction (fs, new FileSystem.AtomicAction () { public void run () throws IOException { result[0] = handleCreateFromTemplate (f, name); } @@ -736,6 +736,19 @@ Object synchObject() { return nodeCreationLock; } + + /** Invokes atomic action. + */ + private void invokeAtomicAction (FileSystem fs, FileSystem.AtomicAction action) throws IOException { + if (this instanceof DataFolder) { + // action is slow + fs.runAtomicAction(action); + } else { + // it is quick, make it block DataObject recognition + DataObjectPool.getPOOL ().runAtomicAction (fs, action); + } + } + // // Property change support Index: loaders/src/org/openide/loaders/DataObjectPool.java =================================================================== RCS file: /cvs/openide/loaders/src/org/openide/loaders/DataObjectPool.java,v retrieving revision 1.3 diff -u -r1.3 DataObjectPool.java --- loaders/src/org/openide/loaders/DataObjectPool.java 16 May 2003 12:08:42 -0000 1.3 +++ loaders/src/org/openide/loaders/DataObjectPool.java 22 May 2003 16:45:05 -0000 @@ -79,6 +79,9 @@ Object prev = FIND.get (); try { + // make sure this thread is allowed to recognize + getPOOL ().enterRecognition(); + FIND.set (loader); ret = loader.handleFindDataObject (fo, rec); @@ -142,6 +145,82 @@ return ret; } + + // + // Support for running really atomic actions + // + private Thread atomic; + private RequestProcessor priviledged; + public void runAtomicAction (FileSystem fs, FileSystem.AtomicAction action) + throws java.io.IOException { + Thread prev; + synchronized (this) { + // make sure that we are the ones that own + // the recognition process + enterRecognition (); + prev = atomic; + atomic = Thread.currentThread (); + } + + try { + fs.runAtomicAction(action); + } finally { + synchronized (this) { + atomic = prev; + notifyAll (); + } + } + } + + /** The thread that runs in atomic action wants to delegate its priviledia + * to somebody else. Used in DataFolder.getChildren that blocks on + * Folder Recognizer thread. + * + * @param delegate the priviledged processor + */ + public synchronized void enterPriviledgedProcessor (RequestProcessor delegate) { + if (atomic == Thread.currentThread()) { + if (priviledged != null) throw new IllegalStateException ("Previous priviledged is not null: " + priviledged + " now: " + delegate); // NOI18N + priviledged = delegate; + } + } + + /** Exits the priviledged processor. + */ + public synchronized void exitPriviledgedProcessor (RequestProcessor delegate) { + if (atomic == Thread.currentThread ()) { + if (priviledged != delegate) throw new IllegalStateException ("Trying to unregister wrong priviledged. Prev: " + priviledged + " now: " + delegate); // NOI18N + priviledged = null; + } + } + + /** Checks whether it is safe to enter the recognition. + */ + private synchronized void enterRecognition () { + // wait till nobody else stops the recognition + for (;;) { + if (atomic == null) { + // ok, I am the one who can enter + return; + } + if (atomic == Thread.currentThread()) { + // ok, reentering again + return; + } + + if (priviledged != null && priviledged.isRequestProcessorThread()) { + // ok, we have priviledged request processor thread + return; + } + + try { + wait (); + } catch (InterruptedException ex) { + // means nothing, go on + } + } + } + /** Collection of all objects that has been created but their * creation has not been yet notified to OperationListener.postCreate * method. @@ -183,7 +262,7 @@ private DataObjectPool () { } - + /** Checks whether there is a data object with primary file * passed thru the parameter. @@ -193,6 +272,8 @@ */ public DataObject find (FileObject fo) { synchronized (this) { + enterRecognition(); + Item doh = (Item)map.get (fo); if (doh == null) { return null; @@ -313,6 +394,8 @@ public void waitNotified (DataObject obj) { try { synchronized (this) { + enterRecognition (); + if (toNotify.isEmpty()) { return; } Index: loaders/src/org/openide/loaders/DataShadow.java =================================================================== RCS file: /cvs/openide/loaders/src/org/openide/loaders/DataShadow.java,v retrieving revision 1.2 diff -u -r1.2 DataShadow.java --- loaders/src/org/openide/loaders/DataShadow.java 2 Apr 2003 09:45:52 -0000 1.2 +++ loaders/src/org/openide/loaders/DataShadow.java 22 May 2003 16:45:10 -0000 @@ -231,7 +231,7 @@ final FileObject fo = folder.getPrimaryFile (); final DataShadow[] arr = new DataShadow[1]; - fo.getFileSystem ().runAtomicAction (new FileSystem.AtomicAction () { + DataObjectPool.getPOOL().runAtomicAction (fo.getFileSystem (), new FileSystem.AtomicAction () { public void run () throws IOException { FileObject file = writeOriginal (name, ext, fo, original); DataObject obj = DataObject.find (file); Index: loaders/src/org/openide/loaders/DataTransferSupport.java =================================================================== RCS file: /cvs/openide/loaders/src/org/openide/loaders/DataTransferSupport.java,v retrieving revision 1.2 diff -u -r1.2 DataTransferSupport.java --- loaders/src/org/openide/loaders/DataTransferSupport.java 2 Apr 2003 09:45:52 -0000 1.2 +++ loaders/src/org/openide/loaders/DataTransferSupport.java 22 May 2003 16:45:10 -0000 @@ -165,7 +165,7 @@ nd.setInputText (name); if (NotifyDescriptor.OK_OPTION == DialogDisplayer.getDefault ().notify (nd)) { - trg.getPrimaryFile ().getFileSystem ().runAtomicAction (new FileSystem.AtomicAction () { + DataObjectPool.getPOOL().runAtomicAction (trg.getPrimaryFile ().getFileSystem (), new FileSystem.AtomicAction () { public void run () throws IOException { FileObject fo = trg.getPrimaryFile ().createData (nd.getInputText (), "ser"); // NOI18N FileLock lock = fo.lock (); Index: loaders/src/org/openide/loaders/FolderList.java =================================================================== RCS file: /cvs/openide/loaders/src/org/openide/loaders/FolderList.java,v retrieving revision 1.2 diff -u -r1.2 FolderList.java --- loaders/src/org/openide/loaders/FolderList.java 2 Apr 2003 09:45:55 -0000 1.2 +++ loaders/src/org/openide/loaders/FolderList.java 22 May 2003 16:45:10 -0000 @@ -217,8 +217,14 @@ * @return array with children */ public List getChildrenList () { - ListTask lt = getChildrenList (null); - lt.task.waitFinished (); + ListTask lt; + try { + DataObjectPool.getPOOL().enterPriviledgedProcessor (PROCESSOR); + lt = getChildrenList (null); + lt.task.waitFinished(); + } finally { + DataObjectPool.getPOOL().exitPriviledgedProcessor (PROCESSOR); + } return lt.result; } Index: loaders/src/org/openide/loaders/InstanceDataObject.java =================================================================== RCS file: /cvs/openide/loaders/src/org/openide/loaders/InstanceDataObject.java,v retrieving revision 1.3 diff -u -r1.3 InstanceDataObject.java --- loaders/src/org/openide/loaders/InstanceDataObject.java 7 Apr 2003 13:18:00 -0000 1.3 +++ loaders/src/org/openide/loaders/InstanceDataObject.java 22 May 2003 16:45:11 -0000 @@ -219,7 +219,7 @@ if (newFile == null) { final FileObject[] fos = new FileObject[1]; - fo.getFileSystem().runAtomicAction(new FileSystem.AtomicAction() { + DataObjectPool.getPOOL().runAtomicAction (fo.getFileSystem(), new FileSystem.AtomicAction() { public void run () throws IOException { String fileName; if (name == null) { @@ -1375,7 +1375,7 @@ me.name = name; me.create = create; - folder.getPrimaryFile().getFileSystem().runAtomicAction(me); + DataObjectPool.getPOOL().runAtomicAction (folder.getPrimaryFile().getFileSystem(), me); me.mi = null; me.folder = null; me.instance = null; Index: test/unit/src/org/openide/loaders/SeparationOfThreadsTest.java =================================================================== RCS file: test/unit/src/org/openide/loaders/SeparationOfThreadsTest.java diff -N test/unit/src/org/openide/loaders/SeparationOfThreadsTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ test/unit/src/org/openide/loaders/SeparationOfThreadsTest.java 22 May 2003 16:45:24 -0000 @@ -0,0 +1,310 @@ +/* + * Sun Public License Notice + * + * The contents of this file are subject to the Sun Public License + * Version 1.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://www.sun.com/ + * + * The Original Code is NetBeans. The Initial Developer of the Original + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.openide.loaders; + +import org.openide.filesystems.*; +import org.openide.loaders.*; +import java.beans.*; +import java.io.IOException; +import junit.textui.TestRunner; +import org.netbeans.junit.*; + +/* + * Checks whether a during a modify operation (copy, move) some + * other thread can get a grip on unfinished and uncostructed + * content on filesystem. + * + * @author Jaroslav Tulach + */ +public class SeparationOfThreadsTest extends NbTestCase { + private DataFolder root; + private DataFolder to; + private DataObject a; + private DataObject b; + private DataObject res; + + /** Creates the test */ + public SeparationOfThreadsTest(String name) { + super(name); + } + + // For each test setup a FileSystem and DataObjects + protected void setUp() throws Exception { + String fsstruct [] = new String [] { + "A.attr", + "B.attr", + "dir/", + "fake/A.instance" + }; + TestUtilHid.destroyLocalFileSystem (getName()); + FileSystem fs = TestUtilHid.createLocalFileSystem (getName(), fsstruct); + root = DataFolder.findFolder (fs.getRoot ()); + + AddLoaderManuallyHid.addRemoveLoader (ALoader.getLoader (ALoader.class), true); + AddLoaderManuallyHid.addRemoveLoader (BLoader.getLoader (BLoader.class), true); + + to = DataFolder.findFolder (fs.findResource (fsstruct[2])); + + fs.findResource (fsstruct[0]).setAttribute ("A", Boolean.TRUE); + + a = DataObject.find (fs.findResource (fsstruct[0])); + b = DataObject.find (fs.findResource (fsstruct[1])); + + ALoader loaderA = (ALoader)ALoader.getLoader (ALoader.class); + + assertEquals ("A is loaded by ALoader", loaderA, a.getLoader()); + assertEquals ("B is loaded by BLoader", ALoader.getLoader (BLoader.class), b.getLoader()); + + // following code tests one bug that I have made during implementation + // the runAtomicAction has to be run in finally block as some operation + // can throw exceptions. This simulates operation that throws exception + try { + a.delete (); + fail ("Should throw exception"); + } catch (IOException ex) { + assertEquals ("Not implemented", ex.getMessage ()); + } + + synchronized (loaderA) { + new Thread ((Runnable)loaderA).start (); + loaderA.wait (); + } + } + + //Clear all stuff when the test finish + protected void tearDown() throws Exception { + ALoader loader = (ALoader)ALoader.getLoader(ALoader.class); + synchronized (loader) { + try { + while (!loader.finished) { + loader.wait (); + } + + assertNotNull (res); + assertEquals ("The right loader synchronously", loader, res.getLoader ()); + + if (loader.asyncError != null) { + throw loader.asyncError; + } + + assertNotNull (loader.asyncRes); + assertEquals ("It is the right loader asynchronously", loader, loader.asyncRes.getLoader()); + + } finally { + loader.asyncError = null; + loader.currentThread = null; + loader.current = null; + loader.asyncRes = null; + loader.finished = false; + // clears any such flag + Thread.interrupted(); + + + TestUtilHid.destroyLocalFileSystem (getName()); + AddLoaderManuallyHid.addRemoveLoader (ALoader.getLoader (ALoader.class), false); + AddLoaderManuallyHid.addRemoveLoader (BLoader.getLoader (BLoader.class), false); + + // end of test + loader.notify (); + } + } + } + + public static void main (String[] args) throws Exception { + TestRunner.run(new NbTestSuite(SeparationOfThreadsTest.class)); + } + + public void testCopy () throws Exception { + res = a.copy (to); + } + + public void testCreateFromTemplate () throws Exception { + res = a.createFromTemplate (to); + } + public void testMove () throws Exception { + a.move (to); + res = a; + } + public void testRename () throws Exception { + a.rename ("AnyThing"); + res = a; + } + + // + // Inner classes + // + + public static final class ALoader extends UniFileLoader implements Runnable { + DataObject asyncRes; + Exception asyncError; + FileObject current; + Thread currentThread; + boolean finished; + + public ALoader() { + super(DataObject.class.getName()); + } + protected void initialize() { + super.initialize(); + getExtensions().addExtension("attr"); + } + protected String displayName() { + return getClass().getName (); + } + protected MultiDataObject createMultiObject(FileObject pf) throws IOException { + return new MultiDataObject(pf, this); + } + + protected org.openide.loaders.MultiDataObject.Entry createPrimaryEntry(org.openide.loaders.MultiDataObject multiDataObject, org.openide.filesystems.FileObject fileObject) { + return new SlowEntry (multiDataObject, fileObject); + } + + // + // Notification that the copy is in middle + // + + public void notifyCopied (FileObject current) { + // first of all do some really ugly and complex operation + try { + DataObject[] arr = DataFolder.findFolder (current.getFileSystem ().findResource ("fake")).getChildren (); + assertEquals ("In folder fake there is one object", 1, arr.length); + } catch (FileStateInvalidException ex) { + fail ("Wrong exception" + ex); + } + + synchronized (this) { + this.current = current; + this.currentThread = Thread.currentThread (); + this.notify (); + } + int cnt = 1; + while (cnt-- > 0) { + try { + Thread.sleep (500); + } catch (InterruptedException ex) { + // is interrupted to be wake up sooner + } + } + } + + // + // The second thread that waits for the copy operation + // + public void run () { + DataLoader loader = this; + synchronized (loader) { + // continue execution in setUp + loader.notify (); + // wait for being notify about copying + try { + loader.wait (); + } catch (InterruptedException ex) { + asyncError = ex; + } + + try { + asyncRes = DataObject.find (current); + currentThread.interrupt(); + } catch (IOException ex) { + asyncError = ex; + } + + // notify that we have computed everything + finished = true; + loader.notify (); + + while (asyncRes != null && asyncError != null) { + try { + loader.wait (); + } catch (InterruptedException ex) { + } + } + } + } + + } + + public static final class BLoader extends UniFileLoader { + public BLoader() { + super(DataObject.class.getName()); + } + protected void initialize() { + super.initialize(); + getExtensions().addExtension("attr"); + } + protected String displayName() { + return getClass ().getName (); + } + protected org.openide.filesystems.FileObject findPrimaryFile(org.openide.filesystems.FileObject fileObject) { + if (Boolean.TRUE.equals (fileObject.getAttribute ("A"))) { + return null; + } + + org.openide.filesystems.FileObject retValue; + + retValue = super.findPrimaryFile(fileObject); + return retValue; + } + + protected MultiDataObject createMultiObject(FileObject pf) throws IOException { + return new MultiDataObject(pf, this); + } + } + + private static final class SlowEntry extends MultiDataObject.Entry { + public SlowEntry (MultiDataObject obj, FileObject fo) { + obj.super (fo); + } + + private void notifyCopied (FileObject fo) { + ALoader l = (ALoader)ALoader.getLoader(ALoader.class); + l.notifyCopied (fo); + } + + public org.openide.filesystems.FileObject copy(org.openide.filesystems.FileObject fileObject, String str) throws java.io.IOException { + FileObject ret = fileObject.createData ("copy", "attr"); + notifyCopied (ret); + ret.setAttribute ("A", Boolean.TRUE); + return ret; + } + + public org.openide.filesystems.FileObject createFromTemplate(org.openide.filesystems.FileObject fileObject, String str) throws java.io.IOException { + FileObject ret = fileObject.createData ("createFromTemplate", "attr"); + notifyCopied (ret); + ret.setAttribute ("A", Boolean.TRUE); + return ret; + } + + public void delete() throws java.io.IOException { + throw new IOException ("Not implemented"); + } + + public org.openide.filesystems.FileObject move(org.openide.filesystems.FileObject fileObject, String str) throws java.io.IOException { + FileObject ret = fileObject.createData ("move", "attr"); + notifyCopied (ret); + ret.setAttribute ("A", Boolean.TRUE); + super.getFile ().delete (); + return ret; + } + + public org.openide.filesystems.FileObject rename(String str) throws java.io.IOException { + FileObject ret = getFile ().getParent ().createData ("rename", "attr"); + notifyCopied (ret); + ret.setAttribute ("A", Boolean.TRUE); + super.getFile ().delete (); + return ret; + } + + } +}