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: RecordFile.java,v 1.6 2005/06/25 23:12:32 doomdark Exp $ 046 */ 047 package jdbm.recman; 048 049 050 import java.io.*; 051 import java.util.*; 052 053 import org.apache.directory.server.i18n.I18n; 054 055 056 /** 057 * This class represents a random access file as a set of fixed size 058 * records. Each record has a physical record number, and records are 059 * cached in order to improve access. 060 *<p> 061 * The set of dirty records on the in-use list constitutes a transaction. 062 * Later on, we will send these records to some recovery thingy. 063 */ 064 public final class RecordFile 065 { 066 final TransactionManager txnMgr; 067 068 // state transitions: free -> inUse -> dirty -> inTxn -> free 069 // free is a cache, thus a FIFO. The rest are hashes. 070 private final LinkedList<BlockIo> free = new LinkedList<BlockIo>(); 071 private final HashMap<Long,BlockIo> inUse = new HashMap<Long,BlockIo>(); 072 private final HashMap<Long,BlockIo> dirty = new HashMap<Long,BlockIo>(); 073 private final HashMap<Long,BlockIo> inTxn = new HashMap<Long,BlockIo>(); 074 075 // transactions disabled? 076 private boolean transactionsDisabled = false; 077 078 /** The length of a single block. */ 079 public final static int BLOCK_SIZE = 8192;//4096; 080 081 /** The extension of a record file */ 082 final static String extension = ".db"; 083 084 /** A block of clean data to wipe clean pages. */ 085 final static byte[] cleanData = new byte[BLOCK_SIZE]; 086 087 private RandomAccessFile file; 088 private final String fileName; 089 090 091 /** 092 * Creates a new object on the indicated filename. The file is 093 * opened in read/write mode. 094 * 095 * @param fileName the name of the file to open or create, without 096 * an extension. 097 * @throws IOException whenever the creation of the underlying 098 * RandomAccessFile throws it. 099 */ 100 RecordFile ( String fileName ) throws IOException 101 { 102 this.fileName = fileName; 103 file = new RandomAccessFile(fileName + extension, "rw"); 104 txnMgr = new TransactionManager( this ); 105 } 106 107 108 /** 109 * Returns the file name. 110 */ 111 String getFileName() 112 { 113 return fileName; 114 } 115 116 117 /** 118 * Disables transactions: doesn't sync and doesn't use the 119 * transaction manager. 120 */ 121 void disableTransactions() 122 { 123 transactionsDisabled = true; 124 } 125 126 127 /** 128 * Gets a block from the file. The returned byte array is the in-memory 129 * copy of the record, and thus can be written (and subsequently released 130 * with a dirty flag in order to write the block back). 131 * 132 * @param blockid The record number to retrieve. 133 */ 134 BlockIo get( long blockid ) throws IOException 135 { 136 // try in transaction list, dirty list, free list 137 138 BlockIo node = inTxn.get( blockid ); 139 if ( node != null ) 140 { 141 inTxn.remove( blockid ); 142 inUse.put( blockid, node ); 143 return node; 144 } 145 146 node = dirty.get( blockid ); 147 if ( node != null ) 148 { 149 dirty.remove( blockid ); 150 inUse.put( blockid, node ); 151 return node; 152 } 153 154 for ( Iterator<BlockIo> i = free.iterator(); i.hasNext(); ) 155 { 156 BlockIo cur = i.next(); 157 if ( cur.getBlockId() == blockid ) 158 { 159 node = cur; 160 i.remove(); 161 inUse.put( blockid, node ); 162 return node; 163 } 164 } 165 166 // sanity check: can't be on in use list 167 if ( inUse.get( blockid ) != null ) 168 { 169 throw new Error( I18n.err( I18n.ERR_554, blockid ) ); 170 } 171 172 // get a new node and read it from the file 173 node = getNewNode( blockid ); 174 long offset = blockid * BLOCK_SIZE; 175 if ( file.length() > 0 && offset <= file.length() ) 176 { 177 read( file, offset, node.getData(), BLOCK_SIZE ); 178 } 179 else 180 { 181 System.arraycopy( cleanData, 0, node.getData(), 0, BLOCK_SIZE ); 182 } 183 184 inUse.put( blockid, node ); 185 node.setClean(); 186 return node; 187 } 188 189 190 /** 191 * Releases a block. 192 * 193 * @param blockid The record number to release. 194 * @param isDirty If true, the block was modified since the get(). 195 */ 196 void release( long blockid, boolean isDirty ) throws IOException 197 { 198 BlockIo node = inUse.get( blockid ); 199 200 if ( node == null ) 201 { 202 throw new IOException( I18n.err( I18n.ERR_555, blockid ) ); 203 } 204 205 if ( ! node.isDirty() && isDirty ) 206 { 207 node.setDirty(); 208 } 209 210 release( node ); 211 } 212 213 214 /** 215 * Releases a block. 216 * 217 * @param block The block to release. 218 */ 219 void release( BlockIo block ) 220 { 221 inUse.remove( block.getBlockId() ); 222 223 if ( block.isDirty() ) 224 { 225 // System.out.println( "Dirty: " + key + block ); 226 dirty.put( block.getBlockId(), block ); 227 } 228 else 229 { 230 if ( ! transactionsDisabled && block.isInTransaction() ) 231 { 232 inTxn.put( block.getBlockId(), block ); 233 } 234 else 235 { 236 free.add( block ); 237 } 238 } 239 } 240 241 242 /** 243 * Discards a block (will not write the block even if it's dirty) 244 * 245 * @param block The block to discard. 246 */ 247 void discard( BlockIo block ) 248 { 249 inUse.remove( block.getBlockId() ); 250 251 // note: block not added to free list on purpose, because 252 // it's considered invalid 253 } 254 255 256 /** 257 * Commits the current transaction by flushing all dirty buffers to disk. 258 */ 259 void commit() throws IOException 260 { 261 // debugging... 262 if ( ! inUse.isEmpty() && inUse.size() > 1 ) 263 { 264 showList( inUse.values().iterator() ); 265 throw new Error( I18n.err( I18n.ERR_556, inUse.size() ) ); 266 } 267 268 // System.out.println("committing..."); 269 270 if ( dirty.size() == 0 ) 271 { 272 // if no dirty blocks, skip commit process 273 return; 274 } 275 276 277 if ( ! transactionsDisabled ) 278 { 279 txnMgr.start(); 280 } 281 282 283 for ( Iterator<BlockIo> i = dirty.values().iterator(); i.hasNext(); ) 284 { 285 BlockIo node = ( BlockIo ) i.next(); 286 i.remove(); 287 288 // System.out.println("node " + node + " map size now " + dirty.size()); 289 if ( transactionsDisabled ) 290 { 291 long offset = node.getBlockId() * BLOCK_SIZE; 292 file.seek( offset ); 293 file.write( node.getData() ); 294 node.setClean(); 295 free.add( node ); 296 } 297 else 298 { 299 txnMgr.add( node ); 300 inTxn.put( node.getBlockId(), node ); 301 } 302 } 303 304 if ( ! transactionsDisabled ) 305 { 306 txnMgr.commit(); 307 } 308 } 309 310 311 /** 312 * Rollback the current transaction by discarding all dirty buffers 313 */ 314 void rollback() throws IOException 315 { 316 // debugging... 317 if ( ! inUse.isEmpty() ) 318 { 319 showList( inUse.values().iterator() ); 320 throw new Error( I18n.err( I18n.ERR_557, inUse.size() ) ); 321 } 322 323 // System.out.println("rollback..."); 324 dirty.clear(); 325 326 txnMgr.synchronizeLogFromDisk(); 327 328 if ( ! inTxn.isEmpty() ) 329 { 330 showList( inTxn.values().iterator() ); 331 throw new Error( I18n.err( I18n.ERR_558, inTxn.size() ) ); 332 }; 333 } 334 335 336 /** 337 * Commits and closes file. 338 */ 339 void close() throws IOException 340 { 341 if ( ! dirty.isEmpty() ) 342 { 343 commit(); 344 } 345 346 txnMgr.shutdown(); 347 348 if ( ! inTxn.isEmpty() ) 349 { 350 showList( inTxn.values().iterator() ); 351 throw new Error( I18n.err( I18n.ERR_559 ) ); 352 } 353 354 // these actually ain't that bad in a production release 355 if ( ! dirty.isEmpty() ) 356 { 357 System.out.println( "ERROR: dirty blocks at close time" ); 358 showList( dirty.values().iterator() ); 359 throw new Error( I18n.err( I18n.ERR_560 ) ); 360 } 361 362 if ( ! inUse.isEmpty() ) 363 { 364 System.out.println( "ERROR: inUse blocks at close time" ); 365 showList( inUse.values().iterator() ); 366 throw new Error( I18n.err( I18n.ERR_561 ) ); 367 } 368 369 // debugging stuff to keep an eye on the free list 370 // System.out.println("Free list size:" + free.size()); 371 file.close(); 372 file = null; 373 } 374 375 376 /** 377 * Force closing the file and underlying transaction manager. 378 * Used for testing purposed only. 379 */ 380 void forceClose() throws IOException 381 { 382 txnMgr.forceClose(); 383 file.close(); 384 } 385 386 387 /** 388 * Prints contents of a list 389 */ 390 private void showList( Iterator<BlockIo> i ) 391 { 392 int cnt = 0; 393 while ( i.hasNext() ) 394 { 395 System.out.println( "elem " + cnt + ": " + i.next() ); 396 cnt++; 397 } 398 } 399 400 401 /** 402 * Returns a new node. The node is retrieved (and removed) from the 403 * released list or created new. 404 */ 405 private BlockIo getNewNode( long blockid ) throws IOException 406 { 407 BlockIo retval = null; 408 409 if ( ! free.isEmpty() ) 410 { 411 retval = ( BlockIo ) free.removeFirst(); 412 } 413 414 if ( retval == null ) 415 { 416 retval = new BlockIo( 0, new byte[BLOCK_SIZE] ); 417 } 418 419 retval.setBlockId(blockid); 420 retval.setView( null ); 421 return retval; 422 } 423 424 425 /** 426 * Synchronizes a node to disk. This is called by the transaction manager's 427 * synchronization code. 428 */ 429 void synch( BlockIo node ) throws IOException 430 { 431 byte[] data = node.getData(); 432 if ( data != null ) 433 { 434 long offset = node.getBlockId() * BLOCK_SIZE; 435 file.seek( offset ); 436 file.write( data ); 437 } 438 } 439 440 441 /** 442 * Releases a node from the transaction list, if it was sitting there. 443 * 444 * @param recycle true if block data can be reused 445 */ 446 void releaseFromTransaction( BlockIo node, boolean recycle ) throws IOException 447 { 448 if ( ( inTxn.remove( node.getBlockId() ) != null ) && recycle ) 449 { 450 free.add( node ); 451 } 452 } 453 454 455 /** 456 * Synchronizes the file. 457 */ 458 void sync() throws IOException 459 { 460 file.getFD().sync(); 461 } 462 463 464 /** 465 * Utility method: Read a block from a RandomAccessFile 466 */ 467 private static void read( RandomAccessFile file, long offset, byte[] buffer, int nBytes ) throws IOException 468 { 469 file.seek( offset ); 470 int remaining = nBytes; 471 int pos = 0; 472 while ( remaining > 0 ) 473 { 474 int read = file.read( buffer, pos, remaining ); 475 if ( read == -1 ) 476 { 477 System.arraycopy( cleanData, 0, buffer, pos, remaining ); 478 break; 479 } 480 remaining -= read; 481 pos += read; 482 } 483 } 484 }