Index: loaders/src/org/openide/loaders/FolderChildren.java =================================================================== RCS file: /cvs/openide/loaders/src/org/openide/loaders/FolderChildren.java,v retrieving revision 1.5 diff -u -r1.5 FolderChildren.java --- loaders/src/org/openide/loaders/FolderChildren.java 11 Jun 2004 08:34:21 -0000 1.5 +++ loaders/src/org/openide/loaders/FolderChildren.java 23 Sep 2004 13:45:58 -0000 @@ -175,34 +175,15 @@ } return super.findChild(name); } - + + + /** - * This code could probably be replaced by - * Children.MUTEX.isReadAccess, if such call would be added to Mutex * @return true if it is safe to wait (our thread is * not in Children.MUTEX.readAccess */ private static boolean checkChildrenMutex() { - class MutexChecker implements Runnable { - public boolean checkReadOrWrite; - public boolean inReadAccess = true; - public boolean inWriteAccess = true; - public void run () { - if (checkReadOrWrite) { - inReadAccess = false; - } else { - inWriteAccess = false; - } - } - } - MutexChecker test = new MutexChecker(); - // the code will run either immediatally or after we leave readAccess - // section - Children.MUTEX.postWriteRequest(test); - test.checkReadOrWrite = true; - Children.MUTEX.postReadRequest(test); - - return !test.inReadAccess && !test.inWriteAccess; + return !Children.MUTEX.isReadAccess() && !Children.MUTEX.isWriteAccess (); } /** Initializes the children. Index: src/org/openide/nodes/Children.java =================================================================== RCS file: /cvs/openide/src/org/openide/nodes/Children.java,v retrieving revision 1.123 diff -u -r1.123 Children.java --- src/org/openide/nodes/Children.java 7 Sep 2004 12:54:22 -0000 1.123 +++ src/org/openide/nodes/Children.java 23 Sep 2004 13:46:04 -0000 @@ -35,7 +35,7 @@ * access to children. */ static final Mutex.Privileged PR = new Mutex.Privileged (); - + /** Lock for access to hierarchy of all node lists. * Anyone who needs to ensure that there will not * be shared accesses to hierarchy nodes can use this @@ -473,21 +473,7 @@ } else { // otherwise, if not initialize yet (arr.children) wait // for the initialization to finish, but only if we can wait - - // we are not in ReadAccess - // Children.MUTEX.isReadAccess, if such call would be added to Mutex - class MutexChecker implements Runnable { - public boolean inReadAccess = true; - public void run () { - inReadAccess = false; - } - } - MutexChecker test = new MutexChecker(); - // the code will run either immediatally or after we leave readAccess - // section - Children.MUTEX.postWriteRequest(test); - - if (test.inReadAccess || initThread == Thread.currentThread()) { + if (MUTEX.isReadAccess() || initThread == Thread.currentThread()) { // fail, we are in read access if (cannotWorkBetter != null) { cannotWorkBetter[0] = true; Index: src/org/openide/util/Mutex.java =================================================================== RCS file: /cvs/openide/src/org/openide/util/Mutex.java,v retrieving revision 1.61 diff -u -r1.61 Mutex.java --- src/org/openide/util/Mutex.java 8 Apr 2004 09:45:19 -0000 1.61 +++ src/org/openide/util/Mutex.java 23 Sep 2004 13:46:04 -0000 @@ -60,6 +60,11 @@ * } catch (MutexException ex) { * throw (IOException)ex.getException (); * } +* +* // check whether you are already in read access +* if (m.isReadAccess ()) { +* // do your work +* } * * * @author Ales Novak @@ -67,6 +72,10 @@ public final class Mutex extends Object { /** Mutex that allows code to be synchronized with the AWT event dispatch thread. */ public static final Mutex EVENT = new Mutex (); + /** this is used from tests to prevent upgrade from readAccess to writeAccess + * by strictly throwing exception. Otherwise we just notify that using ErrorManager. + */ + static boolean beStrict; // lock mode constants /** Lock free */ @@ -328,6 +337,55 @@ } } + /** Tests whether this thread has already entered the mutex in read access. + * If it returns true, calling readAccess + * will be executed immediatelly + * without any blocking. + * Calling postWriteAccess will delay the execution + * of its Runnable until a readAccess section is over + * and calling writeAccess is strongly prohibited and will + * result in a warning as a deadlock prone behaviour. + * + * @return true if the thread is in read access section + * @since JST-PENDING + */ + public boolean isReadAccess () { + Thread t = Thread.currentThread(); + ThreadInfo info; + + synchronized (LOCK) { + info = getThreadInfo(t); + if (info != null) { + if (info.counts[X] == 0) { + return true; + } + } + } + return false; + } + + /** Tests whether this thread has already entered the mutex in write access. + * If it returns true, calling writeAccess will be executed + * immediatelly without any other blocking. postReadAccess + * will be delayed until a write access runnable is over. + * + * @return true if the thread is in write access section + * @since JST-PENDING + */ + public boolean isWriteAccess () { + Thread t = Thread.currentThread(); + ThreadInfo info; + + synchronized (LOCK) { + info = getThreadInfo(t); + if (info != null) { + if (info.counts[S] == 0) { + return true; + } + } + } + return false; + } /** Posts a read request. This request runs immediately iff * this Mutex is in the shared mode or this Mutex is not contended @@ -425,6 +483,14 @@ if (info.forced) { info.forced = false; } else { + if (requested == X && info.counts[S] > 0) { + IllegalStateException e = new IllegalStateException("WARNING: Going from readAccess to writeAccess, see #10778: http://www.netbeans.org/issues/show_bug.cgi?id=10778 "); // NOI18N + if (beStrict) { + throw e; + } + ErrorManager.getDefault().notify(e); + } + info.counts[requested]++; if ((requested == S) && (info.counts[requested] == 1)) { readersNo++; @@ -432,7 +498,10 @@ } return true; } else if (canUpgrade(info.mode, requested)) { // S - X and no holders - IllegalStateException e = new IllegalStateException("WARNING: Going from readAccess to writeAccess, see #10778: http://www.netbeans.org/issues/show_bug.cgi?id=10778 "); + IllegalStateException e = new IllegalStateException("WARNING: Going from readAccess to writeAccess, see #10778: http://www.netbeans.org/issues/show_bug.cgi?id=10778 "); // NOI18N + if (beStrict) { + throw e; + } ErrorManager.getDefault().notify(e); info.mode = X; @@ -447,7 +516,10 @@ return true; } else { // S - X and holders - IllegalStateException e = new IllegalStateException("WARNING: Going from readAccess to writeAccess through queue, see #10778: http://www.netbeans.org/issues/show_bug.cgi?id=10778 "); + IllegalStateException e = new IllegalStateException("WARNING: Going from readAccess to writeAccess through queue, see #10778: http://www.netbeans.org/issues/show_bug.cgi?id=10778 "); // NOI18N + if (beStrict) { + throw e; + } ErrorManager.getDefault().notify(e); // chain follows } Index: test/unit/src/org/openide/util/MutexTest.java =================================================================== RCS file: /cvs/openide/test/unit/src/org/openide/util/MutexTest.java,v retrieving revision 1.9 diff -u -r1.9 MutexTest.java --- test/unit/src/org/openide/util/MutexTest.java 19 Apr 2004 13:24:21 -0000 1.9 +++ test/unit/src/org/openide/util/MutexTest.java 23 Sep 2004 13:46:04 -0000 @@ -20,8 +20,8 @@ import org.netbeans.junit.*; public class MutexTest extends NbTestCase { - private Mutex.Privileged p; - private Mutex m; + Mutex.Privileged p; + Mutex m; @@ -44,6 +44,7 @@ protected void setUp () { p = new Mutex.Privileged (); m = new Mutex (p); + Mutex.beStrict = true; } public void testReadWriteRead() throws Exception { @@ -529,6 +530,134 @@ p.enterWriteAccess(); p.exitWriteAccess(); } + + public void testNoWayToDoReadAndThenWrite () { + class R implements Runnable { + public void run () { + m.writeAccess (this); + } + } + + try { + m.readAccess (new R ()); + fail ("This is supposed to throw an IllegalStateException"); + } catch (IllegalStateException ex) { + // ok, this is expected + } + } + + public void testNoWayToDoWriteThenReadAndThenWrite () { + class R implements Runnable { + public boolean second; + public boolean end; + public boolean ending; + + public void run () { + if (end) { + ending = true; + return; + } + + if (second) { + end = true; + m.writeAccess (this); + } else { + second = true; + m.readAccess (this); + } + } + } + R r = new R (); + try { + m.writeAccess (r); + fail ("This is supposed to throw an IllegalStateException"); + } catch (IllegalStateException ex) { + // ok, this is expected + assertTrue ("We were in the write access section", r.second); + assertTrue ("We were before the writeAcess(this)", r.end); + assertFalse ("We never reached ending", r.ending); + } + } + + public void testIsOrIsNotInReadOrWriteAccess () { + new ReadWriteChecking ("No r/w", Boolean.FALSE, Boolean.FALSE).run (); + m.readAccess (new ReadWriteChecking ("r but no w", Boolean.TRUE, Boolean.FALSE)); + m.writeAccess (new ReadWriteChecking ("w but no r", Boolean.FALSE, Boolean.TRUE)); + m.readAccess (new Runnable () { + public void run () { + m.postReadRequest (new ReadWriteChecking ("+r -w", Boolean.TRUE, Boolean.FALSE)); + } + }); + m.readAccess (new Runnable () { + public void run () { + m.postWriteRequest (new ReadWriteChecking ("-r +w", Boolean.FALSE, Boolean.TRUE)); + } + }); + m.writeAccess (new Runnable () { + public void run () { + m.postReadRequest (new ReadWriteChecking ("+r -w", Boolean.TRUE, Boolean.FALSE)); + } + }); + m.writeAccess (new Runnable () { + public void run () { + m.postWriteRequest (new ReadWriteChecking ("-r +w", Boolean.FALSE, Boolean.TRUE)); + } + }); + + // write->read->test (downgrade from write to read) + m.writeAccess (new Runnable () { + public boolean second; + + public void run () { + if (!second) { + second = true; + m.readAccess (this); + return; + } + + class P implements Runnable { + public boolean exec; + public void run () { + exec = true; + } + } + P r = new P (); + P w = new P (); + m.postWriteRequest (w); + m.postReadRequest (r); + assertFalse ("Writer not executed", w.exec); + assertFalse ("Reader not executed", r.exec); + + m.readAccess (new ReadWriteChecking ("+r no w", Boolean.FALSE, Boolean.FALSE)); + } + }); + + new ReadWriteChecking ("None at the end", Boolean.FALSE, Boolean.FALSE).run (); + } + + private class ReadWriteChecking implements Runnable { + public Boolean read; + public Boolean write; + public String msg; + + public ReadWriteChecking (String msg, Boolean read, Boolean write) { + assertNotNull ("Msg cannot be null", msg); + this.msg = msg; + this.read = read; + this.write = write; + } + + protected void finalize () { + assertNull ("Run method was not called!", msg); + } + + public void run () { + if (write != null) assertEquals (msg, write.booleanValue (), m.isWriteAccess ()); + if (read != null) assertEquals (msg, read.booleanValue (), m.isReadAccess ()); + msg = null; + } + } + private static class State implements Runnable { public int state;