Index: src/org/openide/filesystems/StreamPool.java =================================================================== RCS file: /cvs/openide/src/org/openide/filesystems/StreamPool.java,v retrieving revision 1.5 diff -c -r1.5 StreamPool.java *** src/org/openide/filesystems/StreamPool.java 22 Apr 2004 23:06:05 -0000 1.5 --- src/org/openide/filesystems/StreamPool.java 26 Jul 2004 14:04:58 -0000 *************** *** 51,63 **** * @param fo FileObject that issues is * @param InputStream that should be issued * @return subclassed InputStream that is registered as mentioned above */ ! public static synchronized InputStream createInputStream (AbstractFolder fo, InputStream is) { ! InputStream retVal = new NotifyInputStream (fo, is); ! get (fo).iStream ().add (retVal); ! get (fo.getFileSystem()).iStream ().add (retVal); return retVal; } ! /** This method creates subclassed NotifyOutputStream (extends OutputStream). * NotifyOutputStream saves stacktrace in constrcuctor (creates new Exception) that * is used in method annotate. --- 51,73 ---- * @param fo FileObject that issues is * @param InputStream that should be issued * @return subclassed InputStream that is registered as mentioned above */ ! public static synchronized InputStream createInputStream (final AbstractFolder fo, final InputStream is) { ! InputStream retVal; ! try { ! get (fo).waitForOutputStreamsClosed(2000); ! retVal = new NotifyInputStream (fo, is); ! get (fo).iStream ().add (retVal); ! get (fo.getFileSystem()).iStream ().add (retVal); ! } catch (InterruptedException e) { ! retVal = new InputStream() { ! public int read() throws IOException { ! throw new FileAlreadyLockedException(fo.getPath()); ! } ! }; ! } return retVal; } ! /** This method creates subclassed NotifyOutputStream (extends OutputStream). * NotifyOutputStream saves stacktrace in constrcuctor (creates new Exception) that * is used in method annotate. *************** *** 70,79 **** * @param fireFileChanged defines if should be fired fileChanged event after close of stream * @param fo FileObject that issues is * @param os OutputStream that should be issued */ ! public static synchronized OutputStream createOutputStream (AbstractFolder fo, OutputStream os, boolean fireFileChanged) { ! OutputStream retVal = new NotifyOutputStream (fo, os, fireFileChanged); ! get (fo).oStream ().add (retVal); ! get (fo.getFileSystem()).oStream ().add (retVal); return retVal; } --- 80,100 ---- * @param fireFileChanged defines if should be fired fileChanged event after close of stream * @param fo FileObject that issues is * @param os OutputStream that should be issued */ ! public static synchronized OutputStream createOutputStream (final AbstractFolder fo, OutputStream os, boolean fireFileChanged) { ! OutputStream retVal = null; ! try { ! get (fo).waitForInputStreamsClosed(2000); ! get (fo).waitForOutputStreamsClosed(2000); ! retVal = new NotifyOutputStream (fo, os, fireFileChanged); ! get (fo).oStream ().add (retVal); ! get (fo.getFileSystem()).oStream ().add (retVal); ! } catch (InterruptedException e) { ! retVal = new OutputStream() { ! public void write(int b) throws IOException { ! throw new FileAlreadyLockedException(fo.getPath()); ! } ! }; ! } return retVal; } *************** *** 131,136 **** --- 152,179 ---- public boolean isInputStreamOpen () { return iStreams != null && !iStreams.isEmpty (); } + + private void waitForInputStreamsClosed (int timeInMs) throws InterruptedException { + synchronized (StreamPool.class) { + if (isInputStreamOpen ()) { + StreamPool.class.wait(timeInMs); + if (isInputStreamOpen ()) { + throw new InterruptedException(); + } + } + } + } + + private void waitForOutputStreamsClosed (int timeInMs) throws InterruptedException { + synchronized (StreamPool.class) { + if (isOutputStreamOpen ()) { + StreamPool.class.wait(timeInMs); + if (isOutputStreamOpen ()) { + throw new InterruptedException(); + } + } + } + } /** * @return true if there is any OutputStream that was not closed yet */ *************** *** 245,250 **** --- 288,296 ---- super.out.flush(); super.close (); closeOutputStream (fo, this, fireFileChanged); + synchronized (StreamPool.class) { + StreamPool.class.notifyAll(); + } } } public Exception getException () { *************** *** 269,274 **** --- 315,326 ---- ex = null; super.close (); closeInputStream (fo, this); + + synchronized (StreamPool.class) { + if (!StreamPool.get (fo).isInputStreamOpen()) { + StreamPool.class.notifyAll(); + } + } } } cvs server: Diffing test/unit/src/org/openide/filesystems Index: test/unit/src/org/openide/filesystems/FileObjectTestHid.java =================================================================== RCS file: /cvs/openide/test/unit/src/org/openide/filesystems/FileObjectTestHid.java,v retrieving revision 1.36 diff -c -r1.36 FileObjectTestHid.java *** test/unit/src/org/openide/filesystems/FileObjectTestHid.java 24 Jun 2004 19:57:17 -0000 1.36 --- test/unit/src/org/openide/filesystems/FileObjectTestHid.java 26 Jul 2004 14:04:58 -0000 *************** *** 241,246 **** --- 241,374 ---- } } + + /** Test whether the read is forbiden while somebody is writing + */ + public void testWriteReadExclusion() throws Exception { + testWriteReadExclusion(false); + } + + public void testWriteReadExclusionDeadlock() throws Exception { + testWriteReadExclusion(true); + } + + private void testWriteReadExclusion(final boolean deadlockInWrite) throws Exception { + checkSetUp(); + FileObject fold = getTestFolder1(root); + final FileObject fo1 = getTestFile1(fold); + + try { + writeStr(fo1, "text"); + firstThreadImpl1(fo1, deadlockInWrite); + writeStr(fo1, "text"); + firstThreadImpl2(fo1, deadlockInWrite); + } catch (IOException iex) { + fsAssert( + "expected move will success on writable FS", + fs.isReadOnly() || fo1.isReadOnly() + ); + } + } + + private static void firstThreadImpl1(final FileObject fo1, final boolean deadlockInWrite) throws InterruptedException { + RequestProcessor.Task secondThread; + secondThread = startSecondThreadAndWait(fo1, deadlockInWrite); + + InputStream is = null; + try { + is = fo1.getInputStream(); + byte[] arr = new byte[200]; + int len = is.read(arr); + assertEquals("Read all four bytes", 4, len); + for (int i = 1; i <= 4; i++) { + assertEquals(i + " th byte is " + i, i, arr[i - 1]); + } + } catch (IOException ex) { + // FileAlreadyLockedException is fine, means we could not read the stream + assertTrue("FileAlreadyLockedException is fine here but just for dedlock in write", deadlockInWrite); + } finally { + try { + if (is != null) + is.close(); + } catch (IOException e) { + } + synchronized (FileObjectTestHid.class) { + // let the writer thread finish + FileObjectTestHid.class.notifyAll(); + } + secondThread.waitFinished(); + } + } + + private static void firstThreadImpl2(final FileObject fo1, final boolean deadlockInWrite) throws IOException ,InterruptedException { + RequestProcessor.Task secondThread = null; + + InputStream is = null; + try { + is = fo1.getInputStream(); + secondThread = startSecondThreadAndWait(fo1, deadlockInWrite); + + byte[] arr = new byte[200]; + int len = is.read(arr); + assertEquals("No byte is available" , -1, len); + } finally { + try { + if (is != null) + is.close(); + } catch (IOException e) { + } + synchronized (FileObjectTestHid.class) { + // let the writer thread finish + FileObjectTestHid.class.notifyAll(); + } + if (secondThread != null) secondThread.waitFinished(); + } + } + + private static void secondThreadImpl(final FileObject fo1, final boolean deadlockInWrite) { + OutputStream os = null; + FileLock lock = null; + try { + try { + lock = fo1.lock(); + os = fo1.getOutputStream(lock); + os.write(1); + os.write(2); + os.flush(); + + synchronized (FileObjectTestHid.class) { + FileObjectTestHid.class.notify(); + // block for a while so the other thread + // can do the partial read + FileObjectTestHid.class.wait(deadlockInWrite ? 0 : 1000); + } + + os.write(3); + os.write(4); + } finally { + if (lock != null) lock.releaseLock(); + if (os != null) os.close(); + synchronized (FileObjectTestHid.class) { + FileObjectTestHid.class.notify(); + } + } + } catch (Exception e) { + } + } + + private static RequestProcessor.Task startSecondThreadAndWait(final FileObject fo1, final boolean deadlockInWrite) throws InterruptedException { + RequestProcessor.Task secondThread; + synchronized (FileObjectTestHid.class) { + secondThread = new RequestProcessor("Writes with delay").post(new Runnable() { + public void run() { + secondThreadImpl(fo1, deadlockInWrite); + } + }); + FileObjectTestHid.class.wait(); + } + return secondThread; + } + public void testGetPath1() { checkSetUp(); FileObject fold1 = getTestFolder1(root);