Author: toad
Date: 2007-08-10 22:04:04 +0000 (Fri, 10 Aug 2007)
New Revision: 14582

Modified:
   trunk/freenet/src/freenet/support/io/BaseFileBucket.java
Log:
Fix possible NPE, log when streams are open when they shouldn't be

Modified: trunk/freenet/src/freenet/support/io/BaseFileBucket.java
===================================================================
--- trunk/freenet/src/freenet/support/io/BaseFileBucket.java    2007-08-10 
20:49:36 UTC (rev 14581)
+++ trunk/freenet/src/freenet/support/io/BaseFileBucket.java    2007-08-10 
22:04:04 UTC (rev 14582)
@@ -7,6 +7,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.Vector;

 import org.tanukisoftware.wrapper.WrapperManager;

@@ -20,6 +21,12 @@
        // need to track it ourselves
        protected long length;
        protected long fileRestartCounter;
+       /** Has the bucket been freed? If so, no further operations may be done 
*/
+       private boolean freed;
+       /** Vector of streams (FileBucketInputStream or FileBucketOutputStream) 
which 
+        * are open to this file. So we can be sure they are all closed when we 
free it. 
+        * Can be null. */
+       private Vector streams;

        protected static String tempDir = null;

@@ -42,21 +49,43 @@
        public OutputStream getOutputStream() throws IOException {
                synchronized (this) {
                        File file = getFile();
+                       if(freed)
+                               throw new IOException("File already freed");
                        if(isReadOnly())
                                throw new IOException("Bucket is read-only: 
"+this);

                        if(createFileOnly() && file.exists())
                                throw new FileExistsException(file);

-                       // FIXME: behaviour depends on UNIX semantics, to 
totally abstract
-                       // it out we would have to kill the old write streams 
here
-                       // FIXME: what about existing streams? Will ones on 
append append
-                       // to the new truncated file? Do we want them to? What 
about
-                       // truncated ones? We should kill old streams here, 
right?
-                       return newFileBucketOutputStream(createFileOnly() ? 
getTempfile() : file, file.getPath(), ++fileRestartCounter);
+                       if(streams != null && !streams.isEmpty())
+                               Logger.error(this, "Streams open on "+this+" 
while opening an output stream!: "+streams);
+                       
+                       File tempfile = createFileOnly() ? getTempfile() : file;
+                       long streamNumber = ++fileRestartCounter;
+                       
+                       FileBucketOutputStream os = 
+                               new FileBucketOutputStream(tempfile, 
streamNumber);
+                       
+                       addStream(os);
+                       return os;
                }
        }

+       private synchronized void addStream(Object stream) {
+               // BaseFileBucket is a very common object, and often very long 
lived,
+               // so we need to minimize memory usage even at the cost of 
frequent allocations.
+               if(streams == null)
+                       streams = new Vector(1,1);
+               streams.add(stream);
+       }
+       
+       private synchronized void removeStream(Object stream) {
+               // Race condition is possible
+               if(streams == null) return;
+               streams.remove(stream);
+               if(streams.isEmpty()) streams = null;
+       }
+
        protected abstract boolean createFileOnly();

        protected abstract boolean deleteOnExit();
@@ -75,11 +104,6 @@
                return f;
        }

-       protected FileBucketOutputStream newFileBucketOutputStream(
-               File tempfile, String s, long streamNumber) throws IOException {
-               return new FileBucketOutputStream(tempfile, s, streamNumber);
-       }
-
        protected synchronized void resetLength() {
                length = 0;
        }
@@ -99,7 +123,7 @@
                private boolean closed;

                protected FileBucketOutputStream(
-                       File tempfile, String s, long restartCount)
+                       File tempfile, long restartCount)
                        throws FileNotFoundException {
                        super(tempfile, false);
                        if(Logger.shouldLog(Logger.MINOR, this))
@@ -111,10 +135,15 @@
                }

                protected void confirmWriteSynchronized() throws IOException {
-                       if (fileRestartCounter > restartCount)
-                               throw new IllegalStateException("writing to 
file after restart");
+                       synchronized(BaseFileBucket.this) {
+                               if (fileRestartCounter > restartCount)
+                                       throw new 
IllegalStateException("writing to file after restart");
+                               if(freed)
+                                       throw new IOException("writing to file 
after it has been freed");
+                       }
                        if(isReadOnly())
                                throw new IOException("File is read-only");
+                               
                }

                public void write(byte[] b) throws IOException {
@@ -148,6 +177,7 @@
                                closed = true;
                                file = getFile();
                        }
+                       removeStream(this);
                        boolean logMINOR = Logger.shouldLog(Logger.MINOR, this);
                        if(logMINOR)
                                Logger.minor(this, "Closing 
"+BaseFileBucket.this);
@@ -181,21 +211,37 @@

        class FileBucketInputStream extends FileInputStream {
                Exception e;
+               boolean closed;

                public FileBucketInputStream(File f) throws IOException {
                        super(f);
                        if (Logger.shouldLog(Logger.DEBUG, this))
                                e = new Exception("debug");
                }
+               
+               public void close() throws IOException {
+                       synchronized(this) {
+                               if(closed) return;
+                               closed = true;
+                       }
+                       removeStream(this);
+                       super.close();
+               }
        }

        public synchronized InputStream getInputStream() throws IOException {
+               if(freed)
+                       throw new IOException("File already freed");
                File file = getFile();
                if(!file.exists()) {
                        Logger.normal(this, "File does not exist: "+file+" for 
"+this);
                        return new NullInputStream();
-               } else 
-                       return new FileBucketInputStream(file);
+               } else {
+                       FileBucketInputStream is =
+                               new FileBucketInputStream(file);
+                       addStream(is);
+                       return is;
+               }
        }

        /**
@@ -324,7 +370,32 @@
                free(false);
        }

-       public synchronized void free(boolean forceFree) {
+       public void free(boolean forceFree) {
+               Object[] toClose;
+               synchronized(this) {
+                       if(freed) return;
+                       freed = true;
+                       toClose = streams == null ? null : streams.toArray();
+                       streams = null;
+               }
+               
+               if(toClose != null) {
+                       Logger.error(this, "Streams open free()ing "+this+" : 
"+toClose);
+                       for(int i=0;i<toClose.length;i++) {
+                               try {
+                                       if(toClose[i] instanceof 
FileBucketOutputStream) {
+                                               ((FileBucketOutputStream) 
toClose[i]).close();
+                                       } else {
+                                               ((FileBucketInputStream) 
toClose[i]).close();
+                                       }
+                               } catch (IOException e) {
+                                       Logger.error(this, "Caught closing 
stream in free(): "+e, e);
+                               } catch (Throwable t) {
+                                       Logger.error(this, "Caught closing 
stream in free(): "+t, t);
+                               }
+                       }
+               }
+               
                File file = getFile();
                if ((deleteOnFree() || forceFree) && file.exists()) {
                        Logger.debug(this,


Reply via email to