View Javadoc

1   /**
2    * JDBM LICENSE v1.00
3    *
4    * Redistribution and use of this software and associated documentation
5    * ("Software"), with or without modification, are permitted provided
6    * that the following conditions are met:
7    *
8    * 1. Redistributions of source code must retain copyright
9    *    statements and notices.  Redistributions must also contain a
10   *    copy of this document.
11   *
12   * 2. Redistributions in binary form must reproduce the
13   *    above copyright notice, this list of conditions and the
14   *    following disclaimer in the documentation and/or other
15   *    materials provided with the distribution.
16   *
17   * 3. The name "JDBM" must not be used to endorse or promote
18   *    products derived from this Software without prior written
19   *    permission of Cees de Groot.  For written permission,
20   *    please contact cg@cdegroot.com.
21   *
22   * 4. Products derived from this Software may not be called "JDBM"
23   *    nor may "JDBM" appear in their names without prior written
24   *    permission of Cees de Groot.
25   *
26   * 5. Due credit should be given to the JDBM Project
27   *    (http://jdbm.sourceforge.net/).
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
30   * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
31   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
32   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
33   * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
34   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
35   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
36   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
38   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
39   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
40   * OF THE POSSIBILITY OF SUCH DAMAGE.
41   *
42   * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
43   * Contributions are Copyright (C) 2000 by their associated contributors.
44   *
45   * $Id: TransactionManager.java,v 1.7 2005/06/25 23:12:32 doomdark Exp $
46   */
47  
48  package jdbm.recman;
49  
50  import java.io.*;
51  import java.util.*;
52  
53  /**
54   *  This class manages the transaction log that belongs to every
55   *  {@link RecordFile}. The transaction log is either clean, or
56   *  in progress. In the latter case, the transaction manager
57   *  takes care of a roll forward.
58   *<p>
59   *  Implementation note: this is a proof-of-concept implementation
60   *  which hasn't been optimized for speed. For instance, all sorts
61   *  of streams are created for every transaction.
62   */
63  // TODO: Handle the case where we are recovering lg9 and lg0, were we
64  // should start with lg9 instead of lg0!
65  
66  public final class TransactionManager {
67      private RecordFile owner;
68  
69      // streams for transaction log.
70      private FileOutputStream fos;
71      private ObjectOutputStream oos;
72  
73      /** 
74       * By default, we keep 10 transactions in the log file before
75       * synchronizing it with the main database file.
76       */
77      static final int DEFAULT_TXNS_IN_LOG = 10;
78  
79      /** 
80       * Maximum number of transactions before the log file is
81       * synchronized with the main database file.
82       */
83      private int _maxTxns = DEFAULT_TXNS_IN_LOG;
84  
85      /**
86       * In-core copy of transactions. We could read everything back from
87       * the log file, but the RecordFile needs to keep the dirty blocks in
88       * core anyway, so we might as well point to them and spare us a lot
89       * of hassle.
90       */
91      private ArrayList[] txns = new ArrayList[DEFAULT_TXNS_IN_LOG];
92      private int curTxn = -1;
93  
94      /** Extension of a log file. */
95      static final String extension = ".lg";
96  
97      /**
98       *  Instantiates a transaction manager instance. If recovery
99       *  needs to be performed, it is done.
100      *
101      *  @param owner the RecordFile instance that owns this transaction mgr.
102      */
103     TransactionManager(RecordFile owner) throws IOException {
104         this.owner = owner;
105         recover();
106         open();
107     }
108 
109     
110     /**
111      * Synchronize log file data with the main database file.
112      * <p>
113      * After this call, the main database file is guaranteed to be 
114      * consistent and guaranteed to be the only file needed for 
115      * backup purposes.
116      */
117     public void synchronizeLog()
118         throws IOException
119     {
120         synchronizeLogFromMemory();
121     }
122 
123     
124     /**
125      * Set the maximum number of transactions to record in
126      * the log (and keep in memory) before the log is
127      * synchronized with the main database file.
128      * <p>
129      * This method must be called while there are no
130      * pending transactions in the log.
131      */
132     public void setMaximumTransactionsInLog( int maxTxns )
133         throws IOException
134     {
135         if ( maxTxns <= 0 ) {
136             throw new IllegalArgumentException( 
137                 "Argument 'maxTxns' must be greater than 0." );
138         }
139         if ( curTxn != -1 ) {
140             throw new IllegalStateException( 
141                 "Cannot change setting while transactions are pending in the log" );
142         }
143         _maxTxns = maxTxns;
144         txns = new ArrayList[ maxTxns ];
145     }
146 
147     
148     /** Builds logfile name  */
149     private String makeLogName() {
150         return owner.getFileName() + extension;
151     }
152 
153 
154     /** Synchs in-core transactions to data file and opens a fresh log */
155     private void synchronizeLogFromMemory() throws IOException {
156         close();
157 
158         TreeSet blockList = new TreeSet( new BlockIoComparator() );
159 
160         int numBlocks = 0;
161         int writtenBlocks = 0;
162         for (int i = 0; i < _maxTxns; i++) {
163             if (txns[i] == null)
164                 continue;
165             // Add each block to the blockList, replacing the old copy of this
166             // block if necessary, thus avoiding writing the same block twice
167             for (Iterator k = txns[i].iterator(); k.hasNext(); ) {
168                 BlockIo block = (BlockIo)k.next();
169                 if ( blockList.contains( block ) ) {
170                     block.decrementTransactionCount();
171                 }
172                 else {
173                     writtenBlocks++;
174                     boolean result = blockList.add( block );
175                 }
176                 numBlocks++;
177             }
178 
179             txns[i] = null;
180         }
181         // Write the blocks from the blockList to disk
182         synchronizeBlocks(blockList.iterator(), true);
183 
184         owner.sync();
185         open();
186     }
187 
188 
189     /** Opens the log file */
190     private void open() throws IOException {
191         fos = new FileOutputStream(makeLogName());
192         oos = new ObjectOutputStream(fos);
193         oos.writeShort(Magic.LOGFILE_HEADER);
194         oos.flush();
195         curTxn = -1;
196     }
197 
198     /** Startup recovery on all files */
199     private void recover() throws IOException {
200         String logName = makeLogName();
201         File logFile = new File(logName);
202         if (!logFile.exists())
203             return;
204         if (logFile.length() == 0) {
205             logFile.delete();
206             return;
207         }
208 
209         FileInputStream fis = new FileInputStream(logFile);
210         ObjectInputStream ois = new ObjectInputStream(fis);
211 
212         try {
213             if (ois.readShort() != Magic.LOGFILE_HEADER)
214                 throw new Error("Bad magic on log file");
215         } catch (IOException e) {
216             // corrupted/empty logfile
217             logFile.delete();
218             return;
219         }
220 
221         while (true) {
222             ArrayList blocks = null;
223             try {
224                 blocks = (ArrayList) ois.readObject();
225             } catch (ClassNotFoundException e) {
226                 throw new Error("Unexcepted exception: " + e);
227             } catch (IOException e) {
228                 // corrupted logfile, ignore rest of transactions
229                 break;
230             }
231             synchronizeBlocks(blocks.iterator(), false);
232 
233             // ObjectInputStream must match exactly each
234             // ObjectOutputStream created during writes
235             try {
236                 ois = new ObjectInputStream(fis);
237             } catch (IOException e) {
238                 // corrupted logfile, ignore rest of transactions
239                 break;
240             }
241         }
242         owner.sync();
243         logFile.delete();
244     }
245 
246     /** Synchronizes the indicated blocks with the owner. */
247     private void synchronizeBlocks(Iterator blockIterator, boolean fromCore)
248     throws IOException {
249         // write block vector elements to the data file.
250         while ( blockIterator.hasNext() ) {
251             BlockIo cur = (BlockIo)blockIterator.next();
252             owner.synch(cur);
253             if (fromCore) {
254                 cur.decrementTransactionCount();
255                 if (!cur.isInTransaction()) {
256                     owner.releaseFromTransaction(cur, true);
257                 }
258             }
259         }
260     }
261 
262 
263     /** Set clean flag on the blocks. */
264     private void setClean(ArrayList blocks)
265     throws IOException {
266         for (Iterator k = blocks.iterator(); k.hasNext(); ) {
267             BlockIo cur = (BlockIo) k.next();
268             cur.setClean();
269         }
270     }
271 
272     /** Discards the indicated blocks and notify the owner. */
273     private void discardBlocks(ArrayList blocks)
274     throws IOException {
275         for (Iterator k = blocks.iterator(); k.hasNext(); ) {
276             BlockIo cur = (BlockIo) k.next();
277             cur.decrementTransactionCount();
278             if (!cur.isInTransaction()) {
279                 owner.releaseFromTransaction(cur, false);
280             }
281         }
282     }
283 
284     /**
285      *  Starts a transaction. This can block if all slots have been filled
286      *  with full transactions, waiting for the synchronization thread to
287      *  clean out slots.
288      */
289     void start() throws IOException {
290         curTxn++;
291         if (curTxn == _maxTxns) {
292             synchronizeLogFromMemory();
293             curTxn = 0;
294         }
295         txns[curTxn] = new ArrayList();
296     }
297 
298     /**
299      *  Indicates the block is part of the transaction.
300      */
301     void add(BlockIo block) throws IOException {
302         block.incrementTransactionCount();
303         txns[curTxn].add(block);
304     }
305 
306     /**
307      *  Commits the transaction to the log file.
308      */
309     void commit() throws IOException {
310         oos.writeObject(txns[curTxn]);
311         sync();
312 
313         // set clean flag to indicate blocks have been written to log
314         setClean(txns[curTxn]);
315 
316         // open a new ObjectOutputStream in order to store
317         // newer states of BlockIo
318         oos = new ObjectOutputStream(fos);
319     }
320 
321     /** Flushes and syncs */
322     private void sync() throws IOException {
323         oos.flush();
324         fos.flush();
325         fos.getFD().sync();
326     }
327 
328     /**
329      *  Shutdowns the transaction manager. Resynchronizes outstanding
330      *  logs.
331      */
332     void shutdown() throws IOException {
333         synchronizeLogFromMemory();
334         close();
335     }
336 
337     /**
338      *  Closes open files.
339      */
340     private void close() throws IOException {
341         sync();
342         oos.close();
343         fos.close();
344         oos = null;
345         fos = null;
346     }
347 
348     /**
349      * Force closing the file without synchronizing pending transaction data.
350      * Used for testing purposes only.
351      */
352     void forceClose() throws IOException {
353         oos.close();
354         fos.close();
355         oos = null;
356         fos = null;
357     }
358 
359     /**
360      * Use the disk-based transaction log to synchronize the data file.
361      * Outstanding memory logs are discarded because they are believed
362      * to be inconsistent.
363      */
364     void synchronizeLogFromDisk() throws IOException {
365         close();
366 
367         for ( int i=0; i < _maxTxns; i++ ) {
368             if (txns[i] == null)
369                 continue;
370             discardBlocks(txns[i]);
371             txns[i] = null;
372         }
373 
374         recover();
375         open();
376     }
377 
378 
379     /** INNER CLASS.
380      *  Comparator class for use by the tree set used to store the blocks
381      *  to write for this transaction.  The BlockIo objects are ordered by
382      *  their blockIds.
383      */
384     public static class BlockIoComparator
385         implements Comparator
386     {
387 
388         public int compare( Object o1, Object o2 ) {
389             BlockIo block1 = (BlockIo)o1;
390             BlockIo block2 = (BlockIo)o2;
391             int result = 0;
392             if ( block1.getBlockId() == block2.getBlockId() ) {
393                 result = 0;
394             }
395             else if ( block1.getBlockId() < block2.getBlockId() ) {
396                 result = -1;
397             }
398             else {
399                 result = 1;
400             }
401             return result;
402         }
403 
404         public boolean equals(Object obj) {
405             return super.equals(obj);
406         }
407     } // class BlockIOComparator
408 
409 }