package org.prevayler.foundation; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; public class DurableOutputStream { /** * These two locks allow the two main activities of this class, * serializing transactions to a buffer on the one hand and flushing * the buffer and syncing to disk on the other hand, to proceed * concurrently. Note that where both locks are required, we always * acquire the _syncLock before acquiring the _writeLock to avoid * deadlock. */ private final Object _writeLock=new Object(); private final Object _syncLock=new Object(); /** * The File object is only stashed for the sake of the file() getter. */ private final File _file; /** * All access guarded by _syncLock. */ private final FileOutputStream _fileOutputStream; /** * All access guarded by _syncLock. */ private final FileDescriptor _fileDescriptor; /** * All access guarded by _writeLock. */ private ByteArrayOutputStream _active=new ByteArrayOutputStream(); /** * All access guarded by _syncLock. */ private ByteArrayOutputStream _inactive=new ByteArrayOutputStream(); /** * All access guarded by _writeLock. */ private boolean _closed=false; /** * All access guarded by _writeLock. */ private int _objectsWritten=0; /** * All access guarded by _syncLock. */ private int _objectsSynced=0; /** * All access guarded by _syncLock. */ private int _fileSyncCount=0; public DurableOutputStream( File file) throws IOException { _file=file; _fileOutputStream=new FileOutputStream(file); _fileDescriptor=_fileOutputStream.getFD(); } public void sync( Guided guide) throws IOException { int thisWrite; guide.startTurn(); try { thisWrite=writeObject(guide); } finally { guide.endTurn(); } waitUntilSynced(thisWrite); } private int writeObject( Guided guide) throws IOException { synchronized (_writeLock) { if (_closed) { throw new IOException("already closed"); } try { guide.writeTo(_active); } catch ( IOException exception) { internalClose(); throw exception; } _objectsWritten++; return _objectsWritten; } } private void waitUntilSynced( int thisWrite) throws IOException { synchronized (_syncLock) { if (_objectsSynced < thisWrite) { int objectsWritten; synchronized (_writeLock) { if (_closed) { throw new IOException("already closed"); } ByteArrayOutputStream swap=_active; _active=_inactive; _inactive=swap; objectsWritten=_objectsWritten; } try { _inactive.writeTo(_fileOutputStream); _inactive.reset(); _fileOutputStream.flush(); _fileDescriptor.sync(); } catch ( IOException exception) { internalClose(); throw exception; } _objectsSynced=objectsWritten; _fileSyncCount++; } } } public void close() throws IOException { synchronized (_syncLock) { synchronized (_writeLock) { if (_closed) { return; } internalClose(); _fileOutputStream.close(); } } } private void internalClose(){ synchronized (_writeLock) { _closed=true; _active=null; _inactive=null; } } public File file(){ return _file; } public synchronized int fileSyncCount(){ synchronized (_syncLock) { return _fileSyncCount; } } public boolean reallyClosed(){ synchronized (_writeLock) { return _closed; } } }