001 /**
002 * JDBM LICENSE v1.00
003 *
004 * Redistribution and use of this software and associated documentation
005 * ("Software"), with or without modification, are permitted provided
006 * that the following conditions are met:
007 *
008 * 1. Redistributions of source code must retain copyright
009 * statements and notices. Redistributions must also contain a
010 * copy of this document.
011 *
012 * 2. Redistributions in binary form must reproduce the
013 * above copyright notice, this list of conditions and the
014 * following disclaimer in the documentation and/or other
015 * materials provided with the distribution.
016 *
017 * 3. The name "JDBM" must not be used to endorse or promote
018 * products derived from this Software without prior written
019 * permission of Cees de Groot. For written permission,
020 * please contact cg@cdegroot.com.
021 *
022 * 4. Products derived from this Software may not be called "JDBM"
023 * nor may "JDBM" appear in their names without prior written
024 * permission of Cees de Groot.
025 *
026 * 5. Due credit should be given to the JDBM Project
027 * (http://jdbm.sourceforge.net/).
028 *
029 * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
030 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
031 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
032 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
033 * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
034 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
035 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
036 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
037 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
038 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
039 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
040 * OF THE POSSIBILITY OF SUCH DAMAGE.
041 *
042 * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
043 * Contributions are Copyright (C) 2000 by their associated contributors.
044 *
045 * $Id: TransactionManager.java,v 1.7 2005/06/25 23:12:32 doomdark Exp $
046 */
047
048 package jdbm.recman;
049
050 import java.io.*;
051 import java.util.*;
052
053 import org.apache.directory.server.i18n.I18n;
054
055 /**
056 * This class manages the transaction log that belongs to every
057 * {@link RecordFile}. The transaction log is either clean, or
058 * in progress. In the latter case, the transaction manager
059 * takes care of a roll forward.
060 *<p>
061 * Implementation note: this is a proof-of-concept implementation
062 * which hasn't been optimized for speed. For instance, all sorts
063 * of streams are created for every transaction.
064 */
065 // TODO: Handle the case where we are recovering lg9 and lg0, were we
066 // should start with lg9 instead of lg0!
067
068 public final class TransactionManager {
069 private RecordFile owner;
070
071 // streams for transaction log.
072 private FileOutputStream fos;
073 private ObjectOutputStream oos;
074
075 /**
076 * By default, we keep 10 transactions in the log file before
077 * synchronizing it with the main database file.
078 */
079 static final int DEFAULT_TXNS_IN_LOG = 10;
080
081 /**
082 * Maximum number of transactions before the log file is
083 * synchronized with the main database file.
084 */
085 private int _maxTxns = DEFAULT_TXNS_IN_LOG;
086
087 /**
088 * In-core copy of transactions. We could read everything back from
089 * the log file, but the RecordFile needs to keep the dirty blocks in
090 * core anyway, so we might as well point to them and spare us a lot
091 * of hassle.
092 */
093 private ArrayList[] txns = new ArrayList[DEFAULT_TXNS_IN_LOG];
094 private int curTxn = -1;
095
096 /** Extension of a log file. */
097 static final String extension = ".lg";
098
099 /**
100 * Instantiates a transaction manager instance. If recovery
101 * needs to be performed, it is done.
102 *
103 * @param owner the RecordFile instance that owns this transaction mgr.
104 */
105 TransactionManager(RecordFile owner) throws IOException {
106 this.owner = owner;
107 recover();
108 open();
109 }
110
111
112 /**
113 * Synchronize log file data with the main database file.
114 * <p>
115 * After this call, the main database file is guaranteed to be
116 * consistent and guaranteed to be the only file needed for
117 * backup purposes.
118 */
119 public void synchronizeLog()
120 throws IOException
121 {
122 synchronizeLogFromMemory();
123 }
124
125
126 /**
127 * Set the maximum number of transactions to record in
128 * the log (and keep in memory) before the log is
129 * synchronized with the main database file.
130 * <p>
131 * This method must be called while there are no
132 * pending transactions in the log.
133 */
134 public void setMaximumTransactionsInLog( int maxTxns )
135 throws IOException
136 {
137 if ( maxTxns <= 0 ) {
138 throw new IllegalArgumentException( I18n.err( I18n.ERR_563 ) );
139 }
140 if ( curTxn != -1 ) {
141 throw new IllegalStateException( I18n.err( I18n.ERR_564 ) );
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 ois.close();
215 throw new Error( I18n.err( I18n.ERR_565 ) );
216 }
217 } catch (IOException e) {
218 // corrupted/empty logfile
219 ois.close();
220 logFile.delete();
221 return;
222 }
223
224 while (true) {
225 ArrayList blocks = null;
226 try {
227 blocks = (ArrayList) ois.readObject();
228 } catch (ClassNotFoundException e) {
229 ois.close();
230 throw new Error( I18n.err( I18n.ERR_566, e ) );
231 } catch (IOException e) {
232 // corrupted logfile, ignore rest of transactions
233 break;
234 }
235 synchronizeBlocks(blocks.iterator(), false);
236
237 // ObjectInputStream must match exactly each
238 // ObjectOutputStream created during writes
239 try {
240 ois = new ObjectInputStream(fis);
241 } catch (IOException e) {
242 // corrupted logfile, ignore rest of transactions
243 break;
244 }
245 }
246 owner.sync();
247 ois.close();
248 logFile.delete();
249 }
250
251 /** Synchronizes the indicated blocks with the owner. */
252 private void synchronizeBlocks(Iterator blockIterator, boolean fromCore)
253 throws IOException {
254 // write block vector elements to the data file.
255 while ( blockIterator.hasNext() ) {
256 BlockIo cur = (BlockIo)blockIterator.next();
257 owner.synch(cur);
258 if (fromCore) {
259 cur.decrementTransactionCount();
260 if (!cur.isInTransaction()) {
261 owner.releaseFromTransaction(cur, true);
262 }
263 }
264 }
265 }
266
267
268 /** Set clean flag on the blocks. */
269 private void setClean(ArrayList blocks)
270 throws IOException {
271 for (Iterator k = blocks.iterator(); k.hasNext(); ) {
272 BlockIo cur = (BlockIo) k.next();
273 cur.setClean();
274 }
275 }
276
277 /** Discards the indicated blocks and notify the owner. */
278 private void discardBlocks(ArrayList blocks)
279 throws IOException {
280 for (Iterator k = blocks.iterator(); k.hasNext(); ) {
281 BlockIo cur = (BlockIo) k.next();
282 cur.decrementTransactionCount();
283 if (!cur.isInTransaction()) {
284 owner.releaseFromTransaction(cur, false);
285 }
286 }
287 }
288
289 /**
290 * Starts a transaction. This can block if all slots have been filled
291 * with full transactions, waiting for the synchronization thread to
292 * clean out slots.
293 */
294 void start() throws IOException {
295 curTxn++;
296 if (curTxn == _maxTxns) {
297 synchronizeLogFromMemory();
298 curTxn = 0;
299 }
300 txns[curTxn] = new ArrayList();
301 }
302
303 /**
304 * Indicates the block is part of the transaction.
305 */
306 void add(BlockIo block) throws IOException {
307 block.incrementTransactionCount();
308 txns[curTxn].add(block);
309 }
310
311 /**
312 * Commits the transaction to the log file.
313 */
314 void commit() throws IOException {
315 oos.writeObject(txns[curTxn]);
316 sync();
317
318 // set clean flag to indicate blocks have been written to log
319 setClean(txns[curTxn]);
320
321 // reset ObjectOutputStream in order to store
322 // newer states of BlockIo
323 oos = new ObjectOutputStream(fos);
324 oos.reset();
325 }
326
327 /** Flushes and syncs */
328 private void sync() throws IOException {
329 oos.flush();
330 fos.flush();
331 fos.getFD().sync();
332 }
333
334 /**
335 * Shutdowns the transaction manager. Resynchronizes outstanding
336 * logs.
337 */
338 void shutdown() throws IOException {
339 synchronizeLogFromMemory();
340 close();
341 }
342
343 /**
344 * Closes open files.
345 */
346 private void close() throws IOException {
347 sync();
348 oos.close();
349 fos.close();
350 oos = null;
351 fos = null;
352 }
353
354 /**
355 * Force closing the file without synchronizing pending transaction data.
356 * Used for testing purposes only.
357 */
358 void forceClose() throws IOException {
359 oos.close();
360 fos.close();
361 oos = null;
362 fos = null;
363 }
364
365 /**
366 * Use the disk-based transaction log to synchronize the data file.
367 * Outstanding memory logs are discarded because they are believed
368 * to be inconsistent.
369 */
370 void synchronizeLogFromDisk() throws IOException {
371 close();
372
373 for ( int i=0; i < _maxTxns; i++ ) {
374 if (txns[i] == null)
375 continue;
376 discardBlocks(txns[i]);
377 txns[i] = null;
378 }
379
380 recover();
381 open();
382 }
383
384
385 /** INNER CLASS.
386 * Comparator class for use by the tree set used to store the blocks
387 * to write for this transaction. The BlockIo objects are ordered by
388 * their blockIds.
389 */
390 public static class BlockIoComparator
391 implements Comparator
392 {
393
394 public int compare( Object o1, Object o2 ) {
395 BlockIo block1 = (BlockIo)o1;
396 BlockIo block2 = (BlockIo)o2;
397 int result = 0;
398 if ( block1.getBlockId() == block2.getBlockId() ) {
399 result = 0;
400 }
401 else if ( block1.getBlockId() < block2.getBlockId() ) {
402 result = -1;
403 }
404 else {
405 result = 1;
406 }
407 return result;
408 }
409
410 public boolean equals(Object obj) {
411 return super.equals(obj);
412 }
413 } // class BlockIOComparator
414
415 }