1 /******************************************************************************* 2 * Copyright (c) 2005, 2016 QNX Software Systems and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * QNX - Initial API and implementation 13 * Symbian - Add some non-javadoc implementation notes 14 * Markus Schorn (Wind River Systems) 15 * IBM Corporation 16 * Sergey Prigogin (Google) 17 * Stefan Xenos (Google) 18 *******************************************************************************/ 19 package org.eclipse.jdt.internal.core.nd.db; 20 21 import java.io.File; 22 import java.io.FileNotFoundException; 23 import java.io.IOException; 24 import java.io.RandomAccessFile; 25 import java.nio.ByteBuffer; 26 import java.nio.channels.ClosedByInterruptException; 27 import java.nio.channels.ClosedChannelException; 28 import java.nio.channels.FileChannel; 29 import java.text.DecimalFormat; 30 import java.util.ArrayList; 31 import java.util.HashSet; 32 import java.util.Set; 33 34 import org.eclipse.core.runtime.IStatus; 35 import org.eclipse.core.runtime.OperationCanceledException; 36 import org.eclipse.core.runtime.Status; 37 import org.eclipse.jdt.internal.core.nd.IndexExceptionBuilder; 38 import org.eclipse.jdt.internal.core.nd.db.ModificationLog.Tag; 39 import org.eclipse.osgi.util.NLS; 40 41 /** 42 * Database encapsulates access to a flat binary format file with a memory-manager-like API for 43 * obtaining and releasing areas of storage (memory). 44 * <p> 45 * Some terminology is used throughout this class: A block is a variable-size piece of 46 * contiguous memory returned by malloc. A chunk is a fixed-size piece of contiguous memory 47 * that is the atomic unit for paging, caching, reads, and writes. A free block is contiguous 48 * variable-length piece of memory that is created by free and is potentially usable by malloc. 49 * Most chunks contain multiple blocks and free blocks, but it is possible for a single block 50 * to use multiple chunks. Such blocks are referred to as "large blocks". A free block is always 51 * smaller than a chunk. 52 */ 53 /* 54 * The file encapsulated is divided into Chunks of size CHUNK_SIZE, and a table of contents 55 * mapping chunk index to chunk address is maintained. Chunk structure exists only conceptually - 56 * it is not a structure that appears in the file. 57 * 58 * ===== The first chunk is used by Database itself for house-keeping purposes and has structure 59 * 60 * offset content 61 * _____________________________ 62 * 0 | version number 63 * INT_SIZE | pointer to head of linked list of blocks of size MIN_BLOCK_DELTAS*BLOCK_SIZE_DELTA 64 * .. | ... 65 * INT_SIZE * (M + 1) | pointer to head of linked list of blocks of size (M + MIN_BLOCK_DELTAS) * BLOCK_SIZE_DELTA 66 * FREE_BLOCK_OFFSET | chunk number for the root of the large block free space trie 67 * WRITE_NUMBER_OFFSET | long integer which is incremented on every write 68 * MALLOC_STATS_OFFSET | memory usage statistics 69 * DATA_AREA | The database singletons are stored here and use the remainder of chunk 0 70 * 71 * M = CHUNK_SIZE / BLOCK_SIZE_DELTA - MIN_BLOCK_DELTAS 72 * 73 * ===== block structure (for free/unused blocks) 74 * 75 * offset content 76 * _____________________________ 77 * 0 | size of block (positive indicates an unused block) (2 bytes) 78 * PREV_OFFSET | pointer to previous block (of same size) (only in free blocks) 79 * NEXT_OFFSET | pointer to next block (of same size) (only in free blocks) 80 * ... | unused space 81 * 82 *====== block structure (for allocated blocks) 83 * 84 * offset content 85 * _____________________________ 86 * 0 | size of block (negative indicates the block is in use) (2 bytes) 87 * 2 | content of the struct 88 * 89 */ 90 public class Database { 91 public static final int CHAR_SIZE = 2; 92 public static final int BYTE_SIZE = 1; 93 public static final int SHORT_SIZE = 2; 94 public static final int INT_SIZE = 4; 95 public static final int LONG_SIZE = 8; 96 public static final int CHUNK_SIZE = 1024 * 4; 97 public static final int OFFSET_IN_CHUNK_MASK= CHUNK_SIZE - 1; 98 public static final int BLOCK_HEADER_SIZE = SHORT_SIZE; 99 100 public static final int BLOCK_SIZE_DELTA_BITS = 3; 101 public static final int BLOCK_SIZE_DELTA= 1 << BLOCK_SIZE_DELTA_BITS; 102 103 // Fields that are only used by free blocks 104 private static final int BLOCK_PREV_OFFSET = BLOCK_HEADER_SIZE; 105 private static final int BLOCK_NEXT_OFFSET = BLOCK_HEADER_SIZE + INT_SIZE; 106 private static final int FREE_BLOCK_HEADER_SIZE = BLOCK_NEXT_OFFSET + INT_SIZE; 107 108 // Must be enough multiples of BLOCK_SIZE_DELTA in order to fit the free block header 109 public static final int MIN_BLOCK_DELTAS = (FREE_BLOCK_HEADER_SIZE + BLOCK_SIZE_DELTA - 1) / BLOCK_SIZE_DELTA; 110 public static final int MAX_BLOCK_DELTAS = (CHUNK_SIZE - LargeBlock.HEADER_SIZE - LargeBlock.FOOTER_SIZE) 111 / BLOCK_SIZE_DELTA; 112 public static final int MAX_SINGLE_BLOCK_MALLOC_SIZE = MAX_BLOCK_DELTAS * BLOCK_SIZE_DELTA - BLOCK_HEADER_SIZE; 113 public static final int PTR_SIZE = 4; // size of a pointer in the database in bytes 114 public static final int STRING_SIZE = PTR_SIZE; 115 public static final int FLOAT_SIZE = INT_SIZE; 116 public static final int DOUBLE_SIZE = LONG_SIZE; 117 public static final long MAX_DB_SIZE= ((long) 1 << (Integer.SIZE + BLOCK_SIZE_DELTA_BITS)); 118 119 public static final long MAX_MALLOC_SIZE = MAX_DB_SIZE - LargeBlock.HEADER_SIZE - LargeBlock.FOOTER_SIZE 120 - CHUNK_SIZE - BLOCK_HEADER_SIZE; 121 122 public static final int VERSION_OFFSET = 0; 123 public static final int MALLOC_TABLE_OFFSET = VERSION_OFFSET + INT_SIZE; 124 public static final int FREE_BLOCK_OFFSET = MALLOC_TABLE_OFFSET 125 + (CHUNK_SIZE / BLOCK_SIZE_DELTA - MIN_BLOCK_DELTAS + 1) * INT_SIZE; 126 public static final int WRITE_NUMBER_OFFSET = FREE_BLOCK_OFFSET + PTR_SIZE; 127 public static final int MALLOC_STATS_OFFSET = WRITE_NUMBER_OFFSET + LONG_SIZE; 128 public static final int DATA_AREA_OFFSET = MALLOC_STATS_OFFSET + MemoryStats.SIZE; 129 public static final int NUM_HEADER_CHUNKS = 1; 130 131 // Malloc pool IDs (used for classifying memory allocations and recording statistics about them) 132 /** Misc pool -- may be used for any purpose that doesn't fit the IDs below. */ 133 public static final short POOL_MISC = 0x0000; 134 public static final short POOL_BTREE = 0x0001; 135 public static final short POOL_DB_PROPERTIES = 0x0002; 136 public static final short POOL_STRING_LONG = 0x0003; 137 public static final short POOL_STRING_SHORT = 0x0004; 138 public static final short POOL_LINKED_LIST = 0x0005; 139 public static final short POOL_STRING_SET = 0x0006; 140 public static final short POOL_GROWABLE_ARRAY = 0x0007; 141 /** Id for the first node type. All node types will record their stats in a pool whose ID is POOL_FIRST_NODE_TYPE + node_id*/ 142 public static final short POOL_FIRST_NODE_TYPE = 0x0100; 143 144 public static class ChunkStats { 145 public final int totalChunks; 146 public final int chunksInMemory; 147 public final int dirtyChunks; 148 public final int nonDirtyChunksNotInCache; 149 ChunkStats(int totalChunks, int chunksInMemory, int dirtyChunks, int nonDirtyChunksNotInCache)150 public ChunkStats(int totalChunks, int chunksInMemory, int dirtyChunks, int nonDirtyChunksNotInCache) { 151 this.totalChunks = totalChunks; 152 this.chunksInMemory = chunksInMemory; 153 this.dirtyChunks = dirtyChunks; 154 this.nonDirtyChunksNotInCache = nonDirtyChunksNotInCache; 155 } 156 157 @Override toString()158 public String toString() { 159 return "Chunks: total = " + this.totalChunks + ", in memory = " + this.chunksInMemory //$NON-NLS-1$//$NON-NLS-2$ 160 + ", dirty = " + this.dirtyChunks + ", not in cache = " + this.nonDirtyChunksNotInCache; //$NON-NLS-1$//$NON-NLS-2$ 161 } 162 } 163 164 /** 165 * For loops that scan through the chunks list, this imposes a maximum number of iterations before the loop must 166 * release the chunk cache mutex. 167 */ 168 private static final int MAX_ITERATIONS_PER_LOCK = 256; 169 private static final int WRITE_BUFFER_SIZE = CHUNK_SIZE * 32; 170 171 /** 172 * True iff large chunk self-diagnostics should be enabled. 173 */ 174 public static boolean DEBUG_FREE_SPACE; 175 176 public static boolean DEBUG_PAGE_CACHE; 177 178 private final File fLocation; 179 private final boolean fReadOnly; 180 private RandomAccessFile fFile; 181 private boolean fExclusiveLock; // Necessary for any write operation. 182 private boolean fLocked; // Necessary for any operation. 183 private boolean fIsMarkedIncomplete; 184 185 private int fVersion; 186 private final Chunk fHeaderChunk; 187 /** 188 * Stores the {@link Chunk} associated with each page number or null if the chunk isn't loaded. Synchronize on 189 * {@link #fCache} before accessing. 190 */ 191 Chunk[] fChunks; 192 private int fChunksUsed; 193 private ChunkCache fCache; 194 195 private long malloced; 196 private long freed; 197 private long cacheHits; 198 private long cacheMisses; 199 private long bytesWritten; 200 private long totalReadTimeMs; 201 202 private MemoryStats memoryUsage; 203 public Chunk fMostRecentlyFetchedChunk; 204 /** 205 * Contains the set of Chunks in this Database for which the Chunk.dirty flag is set to true. 206 * Protected by the database write lock. This set does not contain the header chunk, which is 207 * always handled as a special case by the code that flushes chunks. 208 */ 209 private HashSet<Chunk> dirtyChunkSet = new HashSet<>(); 210 private long totalFlushTime; 211 private long totalWriteTimeMs; 212 private long pageWritesBytes; 213 private long nextValidation; 214 private long validateCounter; 215 public static final double MIN_BYTES_PER_MILLISECOND = 20480.0; 216 217 private final ModificationLog log = new ModificationLog(0); 218 private final Tag mallocTag; 219 private final Tag freeTag; 220 221 /** 222 * Construct a new Database object, creating a backing file if necessary. 223 * @param location the local file path for the database 224 * @param cache the cache to be used optimization 225 * @param version the version number to store in the database (only applicable for new databases) 226 * @param openReadOnly whether this Database object will ever need writing to 227 * @throws IndexException 228 */ Database(File location, ChunkCache cache, int version, boolean openReadOnly)229 public Database(File location, ChunkCache cache, int version, boolean openReadOnly) throws IndexException { 230 this.mallocTag = ModificationLog.createTag("Calling Database.malloc"); //$NON-NLS-1$ 231 this.freeTag = ModificationLog.createTag("Calling Database.free"); //$NON-NLS-1$ 232 try { 233 this.fLocation = location; 234 this.fReadOnly= openReadOnly; 235 this.fCache= cache; 236 openFile(); 237 238 int nChunksOnDisk = (int) (this.fFile.length() / CHUNK_SIZE); 239 this.fHeaderChunk= new Chunk(this, 0); 240 if (nChunksOnDisk <= 0) { 241 this.fVersion= version; 242 this.fChunks= new Chunk[1]; 243 this.fChunksUsed = this.fChunks.length; 244 } else { 245 this.fHeaderChunk.read(); 246 this.fVersion= this.fHeaderChunk.getInt(VERSION_OFFSET); 247 this.fChunks = new Chunk[nChunksOnDisk]; // chunk[0] is unused. 248 this.fChunksUsed = nChunksOnDisk; 249 } 250 } catch (IOException e) { 251 throw new IndexException(new DBStatus(e)); 252 } 253 this.memoryUsage = new MemoryStats(this.fHeaderChunk, MALLOC_STATS_OFFSET); 254 } 255 divideRoundingUp(long num, long den)256 private static int divideRoundingUp(long num, long den) { 257 return (int) ((num + den - 1) / den); 258 } 259 openFile()260 private void openFile() throws FileNotFoundException { 261 this.fFile = new RandomAccessFile(this.fLocation, this.fReadOnly ? "r" : "rw"); //$NON-NLS-1$ //$NON-NLS-2$ 262 } 263 read(ByteBuffer buf, long position)264 void read(ByteBuffer buf, long position) throws IOException { 265 int retries= 0; 266 do { 267 try { 268 this.fFile.getChannel().read(buf, position); 269 return; 270 } catch (ClosedChannelException e) { 271 // Always reopen the file if possible or subsequent reads will fail. 272 openFile(); 273 274 // This is the most common type of interruption. If another thread called Thread.interrupt, 275 // throw an OperationCanceledException. 276 if (e instanceof ClosedByInterruptException) { 277 throw new OperationCanceledException(); 278 } 279 280 // If we've retried too many times, just rethrow the exception. 281 if (++retries >= 20) { 282 throw e; 283 } 284 285 // Otherwise, retry 286 } 287 } while (true); 288 } 289 getLog()290 public ModificationLog getLog() { 291 return this.log; 292 } 293 294 /** 295 * Attempts to write to the given position in the file. Will retry if interrupted by Thread.interrupt() until, 296 * the write succeeds. It will return true if any call to Thread.interrupt() was detected. 297 * 298 * @return true iff a call to Thread.interrupt() was detected at any point during the operation. 299 * @throws IOException 300 */ write(ByteBuffer buf, long position)301 boolean write(ByteBuffer buf, long position) throws IOException { 302 this.bytesWritten += buf.limit(); 303 return performUninterruptableWrite(() -> {this.fFile.getChannel().write(buf, position);}); 304 } 305 306 private static interface IORunnable { run()307 void run() throws IOException; 308 } 309 310 /** 311 * Attempts to perform an uninterruptable write operation on the database. Returns true if an attempt was made 312 * to interrupt it. 313 * 314 * @throws IOException 315 */ performUninterruptableWrite(IORunnable runnable)316 private boolean performUninterruptableWrite(IORunnable runnable) throws IOException { 317 boolean interrupted = false; 318 int retries= 0; 319 while (true) { 320 try { 321 runnable.run(); 322 return interrupted; 323 } catch (ClosedChannelException e) { 324 openFile(); 325 326 if (e instanceof ClosedByInterruptException) { 327 // Retry forever if necessary as long as another thread is calling Thread.interrupt 328 interrupted = true; 329 } else { 330 if (++retries > 20) { 331 throw e; 332 } 333 } 334 } 335 } 336 } 337 transferTo(FileChannel target)338 public void transferTo(FileChannel target) throws IOException { 339 assert this.fLocked; 340 final FileChannel from= this.fFile.getChannel(); 341 long nRead = 0; 342 long position = 0; 343 long size = from.size(); 344 while (position < size) { 345 nRead = from.transferTo(position, 4096 * 16, target); 346 if (nRead == 0) { 347 break; // Should not happen. 348 } else { 349 position+= nRead; 350 } 351 } 352 } 353 getVersion()354 public int getVersion() { 355 return this.fVersion; 356 } 357 setVersion(int version)358 public void setVersion(int version) throws IndexException { 359 assert this.fExclusiveLock; 360 this.fHeaderChunk.putInt(VERSION_OFFSET, version); 361 this.fVersion= version; 362 } 363 364 /** 365 * Empty the contents of the Database, make it ready to start again. Interrupting the thread with 366 * {@link Thread#interrupt()} won't interrupt the write. Returns true iff the thread was interrupted 367 * with {@link Thread#interrupt()}. 368 * 369 * @throws IndexException 370 */ clear(int version)371 public boolean clear(int version) throws IndexException { 372 assert this.fExclusiveLock; 373 boolean wasCanceled = false; 374 removeChunksFromCache(); 375 376 this.log.clear(); 377 this.fVersion= version; 378 // Clear the first chunk. 379 this.fHeaderChunk.clear(0, CHUNK_SIZE); 380 // Chunks have been removed from the cache, so we may just reset the array of chunks. 381 this.fChunks = new Chunk[] {null}; 382 this.dirtyChunkSet.clear(); 383 this.fChunksUsed = this.fChunks.length; 384 try { 385 wasCanceled = this.fHeaderChunk.flush() || wasCanceled; // Zero out header chunk. 386 wasCanceled = performUninterruptableWrite(() -> { 387 this.fFile.getChannel().truncate(CHUNK_SIZE); 388 }) || wasCanceled; 389 this.bytesWritten += CHUNK_SIZE; 390 } catch (IOException e) { 391 Package.log(e); 392 } 393 this.malloced = this.freed = 0; 394 /* 395 * This is for debugging purposes in order to simulate having a very large Nd database. 396 * This will set aside the specified number of chunks. 397 * Nothing uses these chunks so subsequent allocations come after these fillers. 398 * The special function createNewChunks allocates all of these chunks at once. 399 * 524288 for a file starting at 2G 400 * 8388608 for a file starting at 32G 401 * 402 */ 403 long setasideChunks = Long.getLong("org.eclipse.jdt.core.parser.nd.chunks", 0); //$NON-NLS-1$ 404 if (setasideChunks != 0) { 405 setVersion(getVersion()); 406 createNewChunks((int) setasideChunks); 407 wasCanceled = flush() || wasCanceled; 408 } 409 this.memoryUsage.refresh(); 410 this.fHeaderChunk.makeDirty(); 411 return wasCanceled; 412 } 413 removeChunksFromCache()414 private void removeChunksFromCache() { 415 int scanIndex = NUM_HEADER_CHUNKS; 416 while (scanIndex < this.fChunksUsed) { 417 synchronized (this.fCache) { 418 int countMax = Math.min(MAX_ITERATIONS_PER_LOCK, this.fChunksUsed - scanIndex); 419 for (int count = 0; count < countMax; count++) { 420 Chunk chunk = this.fChunks[scanIndex++]; 421 if (chunk != null) { 422 this.fCache.remove(chunk); 423 if (DEBUG_PAGE_CACHE) { 424 System.out.println("CHUNK " + chunk.fSequenceNumber //$NON-NLS-1$ 425 + ": removing from vector in removeChunksFromCache - instance " //$NON-NLS-1$ 426 + System.identityHashCode(chunk)); 427 } 428 this.fChunks[chunk.fSequenceNumber] = null; 429 } 430 } 431 } 432 } 433 } 434 435 /** 436 * Return the Chunk that contains the given offset. 437 * 438 * @throws IndexException 439 */ getChunk(long offset)440 public Chunk getChunk(long offset) throws IndexException { 441 assert offset >= 0; 442 assertLocked(); 443 if (offset < CHUNK_SIZE) { 444 this.fMostRecentlyFetchedChunk = this.fHeaderChunk; 445 return this.fHeaderChunk; 446 } 447 long long_index = offset / CHUNK_SIZE; 448 assert long_index < Integer.MAX_VALUE; 449 450 final int index = (int) long_index; 451 Chunk chunk; 452 synchronized (this.fCache) { 453 assert this.fLocked; 454 if (index < 0 || index >= this.fChunks.length) { 455 databaseCorruptionDetected(); 456 } 457 chunk = this.fChunks[index]; 458 } 459 460 long readStartMs = 0; 461 long readEndMs = 0; 462 // Read the new chunk outside of any synchronized block (this allows parallel reads and prevents background 463 // threads from retaining a lock that blocks the UI while the background thread performs I/O). 464 boolean cacheMiss = (chunk == null); 465 if (cacheMiss) { 466 readStartMs = System.currentTimeMillis(); 467 chunk = new Chunk(this, index); 468 chunk.read(); 469 readEndMs = System.currentTimeMillis(); 470 } 471 472 synchronized (this.fCache) { 473 if (cacheMiss) { 474 this.cacheMisses++; 475 this.totalReadTimeMs += (readEndMs - readStartMs); 476 } else { 477 this.cacheHits++; 478 } 479 Chunk newChunk = this.fChunks[index]; 480 if (newChunk != chunk && newChunk != null) { 481 // Another thread fetched this chunk in the meantime. In this case, we should use the chunk fetched 482 // by the other thread. 483 if (DEBUG_PAGE_CACHE) { 484 System.out.println("CHUNK " + chunk.fSequenceNumber //$NON-NLS-1$ 485 + ": already fetched by another thread - instance " //$NON-NLS-1$ 486 + System.identityHashCode(chunk)); 487 } 488 chunk = newChunk; 489 } else if (cacheMiss) { 490 if (DEBUG_PAGE_CACHE) { 491 System.out.println("CHUNK " + chunk.fSequenceNumber + ": inserted into vector - instance " //$NON-NLS-1$//$NON-NLS-2$ 492 + System.identityHashCode(chunk)); 493 } 494 this.fChunks[index] = chunk; 495 } 496 this.fCache.add(chunk); 497 this.fMostRecentlyFetchedChunk = chunk; 498 } 499 500 return chunk; 501 } 502 503 public void assertLocked() { 504 if (!this.fLocked) { 505 throw new IllegalStateException("Database not locked!"); //$NON-NLS-1$ 506 } 507 } 508 509 private void databaseCorruptionDetected() throws IndexException { 510 String msg = "Corrupted database: " + this.fLocation.getName(); //$NON-NLS-1$ 511 throw new IndexException(new DBStatus(msg)); 512 } 513 514 /** 515 * Copies numBytes from source to destination 516 */ 517 public void memcpy(long dest, long source, int numBytes) { 518 assert numBytes >= 0; 519 long endAddress = source + numBytes; 520 assert endAddress <= (long) this.fChunksUsed * CHUNK_SIZE; 521 // TODO: make use of lower-level System.arrayCopy 522 for (int count = 0; count < numBytes; count++) { 523 putByte(dest + count, getByte(source + count)); 524 } 525 } 526 527 /** 528 * Allocate a block out of the database. 529 */ malloc(final long datasize, final short poolId)530 public long malloc(final long datasize, final short poolId) throws IndexException { 531 assert this.fExclusiveLock; 532 assert datasize >= 0; 533 assert datasize <= MAX_MALLOC_SIZE; 534 535 long result; 536 int usedSize; 537 this.log.start(this.mallocTag); 538 try { 539 if (datasize >= MAX_SINGLE_BLOCK_MALLOC_SIZE) { 540 int newChunkNum = createLargeBlock(datasize); 541 usedSize = Math.abs(getBlockHeaderForChunkNum(newChunkNum)) * CHUNK_SIZE; 542 result = (long) newChunkNum * CHUNK_SIZE + LargeBlock.HEADER_SIZE; 543 // Note that we identify large blocks by setting their block size to 0. 544 clearRange(result, usedSize - LargeBlock.HEADER_SIZE - LargeBlock.FOOTER_SIZE); 545 result = result + BLOCK_HEADER_SIZE; 546 } else { 547 long freeBlock = 0; 548 int needDeltas = divideRoundingUp(datasize + BLOCK_HEADER_SIZE, BLOCK_SIZE_DELTA); 549 if (needDeltas < MIN_BLOCK_DELTAS) { 550 needDeltas = MIN_BLOCK_DELTAS; 551 } 552 553 // Which block size. 554 int useDeltas; 555 for (useDeltas = needDeltas; useDeltas <= MAX_BLOCK_DELTAS; useDeltas++) { 556 freeBlock = getFirstBlock(useDeltas * BLOCK_SIZE_DELTA); 557 if (freeBlock != 0) 558 break; 559 } 560 561 // Get the block. 562 Chunk chunk; 563 if (freeBlock == 0) { 564 // Allocate a new chunk. 565 freeBlock = (long) (createLargeBlock(datasize)) * (long) CHUNK_SIZE + LargeBlock.HEADER_SIZE; 566 useDeltas = MAX_BLOCK_DELTAS; 567 chunk = getChunk(freeBlock); 568 } else { 569 chunk = getChunk(freeBlock); 570 chunk.makeDirty(); 571 int blockReportedSize = chunk.getShort(freeBlock); 572 if (blockReportedSize != useDeltas * BLOCK_SIZE_DELTA) { 573 throw describeProblem() 574 .addProblemAddress("block size", freeBlock, SHORT_SIZE) //$NON-NLS-1$ 575 .build( 576 "Heap corruption detected in free space list. Block " + freeBlock //$NON-NLS-1$ 577 + " reports a size of " + blockReportedSize + " but was in the list for blocks of size " //$NON-NLS-1$//$NON-NLS-2$ 578 + useDeltas * BLOCK_SIZE_DELTA); 579 } 580 removeBlock(chunk, useDeltas * BLOCK_SIZE_DELTA, freeBlock); 581 } 582 583 final int unusedDeltas = useDeltas - needDeltas; 584 if (unusedDeltas >= MIN_BLOCK_DELTAS) { 585 // Add in the unused part of our block. 586 addBlock(chunk, unusedDeltas * BLOCK_SIZE_DELTA, freeBlock + needDeltas * BLOCK_SIZE_DELTA); 587 useDeltas = needDeltas; 588 } 589 590 // Make our size negative to show in use. 591 usedSize = useDeltas * BLOCK_SIZE_DELTA; 592 chunk.putShort(freeBlock, (short) -usedSize); 593 594 // Clear out the block, lots of people are expecting this. 595 chunk.clear(freeBlock + BLOCK_HEADER_SIZE, usedSize - BLOCK_HEADER_SIZE); 596 result = freeBlock + BLOCK_HEADER_SIZE; 597 } 598 } finally { 599 this.log.end(this.mallocTag); 600 } 601 602 this.log.recordMalloc(result, usedSize - BLOCK_HEADER_SIZE); 603 this.malloced += usedSize; 604 this.memoryUsage.recordMalloc(poolId, usedSize); 605 606 if (DEBUG_FREE_SPACE) { 607 boolean performedValidation = periodicValidateFreeSpace(); 608 609 if (performedValidation) { 610 verifyNotInFreeSpaceList(result); 611 } 612 } 613 614 return result; 615 } 616 617 /** 618 * Clears all the bytes in the given range by setting them to zero. 619 * 620 * @param startAddress first address to clear 621 * @param bytesToClear number of addresses to clear 622 */ clearRange(long startAddress, long bytesToClear)623 public void clearRange(long startAddress, long bytesToClear) { 624 if (bytesToClear == 0) { 625 return; 626 } 627 long endAddress = startAddress + bytesToClear; 628 assert endAddress <= (long) this.fChunksUsed * CHUNK_SIZE; 629 int blockNumber = (int) (startAddress / CHUNK_SIZE); 630 int firstBlockBytesToClear = (int) Math.min((((long) (blockNumber + 1) * CHUNK_SIZE) - startAddress), bytesToClear); 631 632 Chunk firstBlock = getChunk(startAddress); 633 firstBlock.clear(startAddress, firstBlockBytesToClear); 634 startAddress += firstBlockBytesToClear; 635 bytesToClear -= firstBlockBytesToClear; 636 while (bytesToClear > CHUNK_SIZE) { 637 Chunk nextBlock = getChunk(startAddress); 638 nextBlock.clear(startAddress, CHUNK_SIZE); 639 startAddress += CHUNK_SIZE; 640 bytesToClear -= CHUNK_SIZE; 641 } 642 643 if (bytesToClear > 0) { 644 Chunk nextBlock = getChunk(startAddress); 645 nextBlock.clear(startAddress, (int) bytesToClear); 646 } 647 } 648 649 /** 650 * Obtains a new block that can fit the given number of bytes (at minimum). Returns the 651 * chunk number. 652 * 653 * @param datasize minimum number of bytes needed 654 * @return the chunk number 655 */ createLargeBlock(long datasize)656 private int createLargeBlock(long datasize) { 657 final int neededChunks = getChunksNeededForBytes(datasize); 658 int freeBlockChunkNum = getFreeBlockFromTrie(neededChunks); 659 final int numChunks; 660 661 if (freeBlockChunkNum == 0) { 662 final int lastChunkNum = this.fChunksUsed; 663 664 numChunks = neededChunks; 665 666 // Check if the last block in the database is free. If so, unlink and expand it. 667 int lastBlockSize = getBlockFooterForChunkBefore(lastChunkNum); 668 if (lastBlockSize > 0) { 669 int startChunkNum = getFirstChunkOfBlockBefore(lastChunkNum); 670 671 unlinkFreeBlock(startChunkNum); 672 // Allocate additional new chunks such that the new chunk is large enough to 673 // handle this allocation. 674 createNewChunks(neededChunks - lastBlockSize); 675 freeBlockChunkNum = startChunkNum; 676 } else { 677 freeBlockChunkNum = createNewChunks(numChunks); 678 } 679 } else { 680 numChunks = getBlockHeaderForChunkNum(freeBlockChunkNum); 681 682 if (numChunks < neededChunks) { 683 throw describeProblem() 684 .addProblemAddress("chunk header", (long) freeBlockChunkNum * CHUNK_SIZE, INT_SIZE) //$NON-NLS-1$ 685 .build("A block in the free space trie was too small or wasn't actually free. Reported size = " //$NON-NLS-1$ 686 + numChunks + " chunks, requested size = " + neededChunks + " chunks"); //$NON-NLS-1$//$NON-NLS-2$ 687 } 688 689 int footer = getBlockFooterForChunkBefore(freeBlockChunkNum + numChunks); 690 if (footer != numChunks) { 691 throw describeProblem() 692 .addProblemAddress("chunk header", (long) freeBlockChunkNum * CHUNK_SIZE, INT_SIZE) //$NON-NLS-1$ 693 .addProblemAddress("chunk footer", (long) (freeBlockChunkNum + numChunks) * CHUNK_SIZE - INT_SIZE, INT_SIZE) //$NON-NLS-1$ 694 .build("The header and footer didn't match for a block in the free space trie. Expected " //$NON-NLS-1$ 695 + numChunks + " but found " + footer); //$NON-NLS-1$ 696 } 697 698 unlinkFreeBlock(freeBlockChunkNum); 699 } 700 701 final int resultChunkNum; 702 if (numChunks > neededChunks) { 703 // If the chunk we've selected is larger than necessary, split it. We have the 704 // choice of using either half of the block. In the interest of leaving more 705 // opportunities of merging large blocks, we leave the unused half of the block 706 // next to the larger adjacent block. 707 final int nextBlockChunkNum = freeBlockChunkNum + numChunks; 708 709 final int nextBlockSize = Math.abs(getBlockHeaderForChunkNum(nextBlockChunkNum)); 710 final int prevBlockSize = Math.abs(getBlockFooterForChunkBefore(freeBlockChunkNum)); 711 712 final int unusedChunks = numChunks - neededChunks; 713 if (nextBlockSize >= prevBlockSize) { 714 // Use the start of the block 715 resultChunkNum = freeBlockChunkNum; 716 // Return the last half of the block to the free block pool 717 linkFreeBlockToTrie(freeBlockChunkNum + neededChunks, unusedChunks); 718 } else { 719 // Use the end of the block 720 resultChunkNum = freeBlockChunkNum + unusedChunks; 721 // Return the first half of the block to the free block pool 722 linkFreeBlockToTrie(freeBlockChunkNum, unusedChunks); 723 } 724 } else { 725 resultChunkNum = freeBlockChunkNum; 726 } 727 728 // Fill in the header and footer 729 setBlockHeader(resultChunkNum, -neededChunks); 730 return resultChunkNum; 731 } 732 733 /** 734 * Unlinks a free block (which currently belongs to the free block trie) so that it may 735 * be reused. 736 * 737 * @param freeBlockChunkNum chunk number of the block to be unlinked 738 */ unlinkFreeBlock(int freeBlockChunkNum)739 private void unlinkFreeBlock(int freeBlockChunkNum) { 740 long freeBlockAddress = (long) freeBlockChunkNum * CHUNK_SIZE; 741 int anotherBlockOfSameSize = 0; 742 int nextBlockChunkNum = getInt(freeBlockAddress + LargeBlock.NEXT_BLOCK_OFFSET); 743 int prevBlockChunkNum = getInt(freeBlockAddress + LargeBlock.PREV_BLOCK_OFFSET); 744 // Relink the linked list 745 if (nextBlockChunkNum != 0) { 746 anotherBlockOfSameSize = nextBlockChunkNum; 747 putInt((long) nextBlockChunkNum * CHUNK_SIZE + LargeBlock.PREV_BLOCK_OFFSET, prevBlockChunkNum); 748 } 749 if (prevBlockChunkNum != 0) { 750 anotherBlockOfSameSize = prevBlockChunkNum; 751 putInt((long) prevBlockChunkNum * CHUNK_SIZE + LargeBlock.NEXT_BLOCK_OFFSET, nextBlockChunkNum); 752 } 753 754 /** 755 * True iff this block was a block in the trie. False if it was attached to to the list of siblings but some 756 * other node in the list is the one in the trie. 757 */ 758 boolean wasInTrie = false; 759 long root = getInt(FREE_BLOCK_OFFSET); 760 if (root == freeBlockChunkNum) { 761 putInt(FREE_BLOCK_OFFSET, 0); 762 wasInTrie = true; 763 } 764 765 int freeBlockSize = getBlockHeaderForChunkNum(freeBlockChunkNum); 766 int parentChunkNum = getInt(freeBlockAddress + LargeBlock.PARENT_OFFSET); 767 if (parentChunkNum != 0) { 768 int currentSize = getBlockHeaderForChunkNum(parentChunkNum); 769 int difference = currentSize ^ freeBlockSize; 770 if (difference != 0) { 771 int firstDifference = LargeBlock.SIZE_OF_SIZE_FIELD * 8 - Integer.numberOfLeadingZeros(difference) - 1; 772 long locationOfChildPointer = (long) parentChunkNum * CHUNK_SIZE + LargeBlock.CHILD_TABLE_OFFSET 773 + (firstDifference * INT_SIZE); 774 int childChunkNum = getInt(locationOfChildPointer); 775 if (childChunkNum == freeBlockChunkNum) { 776 wasInTrie = true; 777 putInt(locationOfChildPointer, 0); 778 } 779 } 780 } 781 782 // If the removed block was the head of the linked list, we need to reinsert the following entry as the 783 // new head. 784 if (wasInTrie && anotherBlockOfSameSize != 0) { 785 insertChild(parentChunkNum, anotherBlockOfSameSize); 786 } 787 788 int currentParent = parentChunkNum; 789 for (int childIdx = 0; childIdx < LargeBlock.ENTRIES_IN_CHILD_TABLE; childIdx++) { 790 long childAddress = freeBlockAddress + LargeBlock.CHILD_TABLE_OFFSET + (childIdx * INT_SIZE); 791 int nextChildChunkNum = getInt(childAddress); 792 if (nextChildChunkNum != 0) { 793 if (!wasInTrie) { 794 throw describeProblem() 795 .addProblemAddress("non-null child pointer", childAddress, INT_SIZE) //$NON-NLS-1$ 796 .build("All child pointers should be null for a free chunk that is in the sibling list but" //$NON-NLS-1$ 797 + " not part of the trie. Problematic chunk number: " + freeBlockChunkNum); //$NON-NLS-1$ 798 } 799 insertChild(currentParent, nextChildChunkNum); 800 // Parent all subsequent children under the child that was most similar to the old parent 801 if (currentParent == parentChunkNum) { 802 currentParent = nextChildChunkNum; 803 } 804 } 805 } 806 807 } 808 809 /** 810 * Returns the chunk number of a free block that contains at least the given number of chunks, or 811 * 0 if there is no existing contiguous free block containing at least the given number of chunks. 812 * 813 * @param numChunks minumum number of chunks desired 814 * @return the chunk number of a free block containing at least the given number of chunks or 0 815 * if there is no existing free block containing that many chunks. 816 */ getFreeBlockFromTrie(int numChunks)817 private int getFreeBlockFromTrie(int numChunks) { 818 int currentChunkNum = getInt(FREE_BLOCK_OFFSET); 819 820 int resultChunkNum = getSmallestChildNoSmallerThan(currentChunkNum, numChunks); 821 if (resultChunkNum == 0) { 822 return 0; 823 } 824 825 // Try not to return the trie node itself if there is a linked list entry available, since unlinking 826 // something from the linked list is faster than unlinking a trie node. 827 int nextResultChunkNum = getInt((long) resultChunkNum * CHUNK_SIZE + LargeBlock.NEXT_BLOCK_OFFSET); 828 if (nextResultChunkNum != 0) { 829 return nextResultChunkNum; 830 } 831 return resultChunkNum; 832 } 833 834 /** 835 * Given the chunk number of a block somewhere in the free space trie, this returns the smallest 836 * child in the subtree that is no smaller than the given number of chunks. 837 * 838 * @param trieNodeChunkNum chunk number of a block in the free space trie 839 * @param numChunks desired number of chunks 840 * @return the chunk number of the first chunk in a contiguous free block containing at least the 841 * given number of chunks 842 */ getSmallestChildNoSmallerThan(int trieNodeChunkNum, int numChunks)843 private int getSmallestChildNoSmallerThan(int trieNodeChunkNum, int numChunks) { 844 if (trieNodeChunkNum == 0) { 845 return 0; 846 } 847 int currentSize = getBlockHeaderForChunkNum(trieNodeChunkNum); 848 assert (currentSize >= 0); 849 int difference = currentSize ^ numChunks; 850 if (difference == 0) { 851 return trieNodeChunkNum; 852 } 853 854 int bitMask = Integer.highestOneBit(difference); 855 int firstDifference = LargeBlock.SIZE_OF_SIZE_FIELD * 8 - Integer.numberOfLeadingZeros(bitMask) - 1; 856 boolean lookingForSmallerChild = (currentSize > numChunks); 857 for (int testPosition = firstDifference; testPosition < LargeBlock.ENTRIES_IN_CHILD_TABLE; testPosition++) { 858 if (((currentSize & bitMask) != 0) == lookingForSmallerChild) { 859 int nextChildChunkNum = getInt( 860 (long) trieNodeChunkNum * CHUNK_SIZE + LargeBlock.CHILD_TABLE_OFFSET + (testPosition * INT_SIZE)); 861 int childResultChunkNum = getSmallestChildNoSmallerThan(nextChildChunkNum, numChunks); 862 if (childResultChunkNum != 0) { 863 return childResultChunkNum; 864 } 865 } 866 bitMask <<= 1; 867 } 868 869 if (lookingForSmallerChild) { 870 return trieNodeChunkNum; 871 } else { 872 return 0; 873 } 874 } 875 876 /** 877 * Link the given unused block into the free block tries. The block does not need to have 878 * its header filled in already. 879 * 880 * @param freeBlockChunkNum chunk number of the start of the block 881 * @param numChunks number of chunks in the block 882 */ linkFreeBlockToTrie(int freeBlockChunkNum, int numChunks)883 private void linkFreeBlockToTrie(int freeBlockChunkNum, int numChunks) { 884 setBlockHeader(freeBlockChunkNum, numChunks); 885 long freeBlockAddress = (long) freeBlockChunkNum * CHUNK_SIZE; 886 Chunk chunk = getChunk(freeBlockAddress); 887 chunk.clear(freeBlockAddress + LargeBlock.HEADER_SIZE, 888 LargeBlock.UNALLOCATED_HEADER_SIZE - LargeBlock.HEADER_SIZE); 889 890 insertChild(getInt(FREE_BLOCK_OFFSET), freeBlockChunkNum); 891 } 892 validateFreeSpace()893 public void validateFreeSpace() { 894 validateFreeSpaceLists(); 895 validateFreeSpaceTries(); 896 } 897 898 /** 899 * Performs a self-test on the free space lists used by malloc to check for corruption 900 */ validateFreeSpaceLists()901 private void validateFreeSpaceLists() { 902 int useDeltas; 903 for (useDeltas = MIN_BLOCK_DELTAS; useDeltas <= MAX_BLOCK_DELTAS; useDeltas++) { 904 validateFreeBlocksFor(useDeltas); 905 } 906 } 907 verifyNotInFreeSpaceList(long result)908 private void verifyNotInFreeSpaceList(long result) { 909 int useDeltas; 910 for (useDeltas = MIN_BLOCK_DELTAS; useDeltas <= MAX_BLOCK_DELTAS; useDeltas++) { 911 int correctSize = useDeltas * BLOCK_SIZE_DELTA; 912 long block = getFirstBlock(correctSize); 913 long addressOfPrevBlockPointer = getAddressOfFirstBlockPointer(correctSize); 914 while (block != 0) { 915 if (block == result) { 916 throw describeProblem() 917 .addProblemAddress("incoming pointer", addressOfPrevBlockPointer, PTR_SIZE) //$NON-NLS-1$ 918 .build("Block " + result //$NON-NLS-1$ 919 + " was found in the free space list, even though it wasn't free"); //$NON-NLS-1$ 920 } 921 addressOfPrevBlockPointer = block + BLOCK_NEXT_OFFSET; 922 long followingBlock = getFreeRecPtr(addressOfPrevBlockPointer); 923 block = followingBlock; 924 } 925 } 926 927 int currentChunkNum = getInt(FREE_BLOCK_OFFSET); 928 929 if (currentChunkNum == 0) { 930 return; 931 } 932 int targetChunkNum = (int) (result / CHUNK_SIZE); 933 934 if (currentChunkNum == targetChunkNum) { 935 throw describeProblem().build("Block " + result //$NON-NLS-1$ 936 + " was not supposed to be in the free space list, but was linked as the root of the list"); //$NON-NLS-1$ 937 } 938 939 verifyNotInLargeBlockFreeSpaceTrie(targetChunkNum, currentChunkNum, 0); 940 } 941 verifyNotInLargeBlockFreeSpaceTrie(int targetChunkNum, int chunkNum, int parent)942 private void verifyNotInLargeBlockFreeSpaceTrie(int targetChunkNum, int chunkNum, int parent) { 943 long chunkStart = (long) chunkNum * CHUNK_SIZE; 944 945 for (int testPosition = 0; testPosition < LargeBlock.ENTRIES_IN_CHILD_TABLE; testPosition++) { 946 long chunkAddress = chunkStart + LargeBlock.CHILD_TABLE_OFFSET + (testPosition * INT_SIZE); 947 int nextChildChunkNum = getInt(chunkAddress); 948 949 if (nextChildChunkNum == 0) { 950 continue; 951 } 952 953 if (nextChildChunkNum == targetChunkNum) { 954 throw describeProblem() 955 .addProblemAddress("trie child address", chunkAddress, INT_SIZE) //$NON-NLS-1$ 956 .build("Chunk number " + nextChildChunkNum //$NON-NLS-1$ 957 + " was found in the free space trie even though it was in use"); //$NON-NLS-1$ 958 } 959 960 verifyNotInLargeBlockFreeSpaceTrie(targetChunkNum, nextChildChunkNum, chunkNum); 961 } 962 } 963 validateFreeBlocksFor(int numberOfDeltas)964 private void validateFreeBlocksFor(int numberOfDeltas) { 965 int correctSize = numberOfDeltas * BLOCK_SIZE_DELTA; 966 long lastBlock = 0; 967 long block = getFirstBlock(correctSize); 968 long addressOfPrevBlockPointer = getAddressOfFirstBlockPointer(correctSize); 969 while (block != 0) { 970 long measuredLastBlock = getFreeRecPtr(block + BLOCK_PREV_OFFSET); 971 int blockReportedSize = getShort(block); 972 long followingBlock = getFreeRecPtr(block + BLOCK_NEXT_OFFSET); 973 if (measuredLastBlock != lastBlock) { 974 throw describeProblem() 975 .addProblemAddress("last block", block + BLOCK_PREV_OFFSET, PTR_SIZE) //$NON-NLS-1$ 976 .addProblemAddress("incoming pointer", addressOfPrevBlockPointer, PTR_SIZE) //$NON-NLS-1$ 977 .build("The free space block (" + block //$NON-NLS-1$ 978 + ") of size " + correctSize + " had an incorrect prev pointer to " //$NON-NLS-1$//$NON-NLS-2$ 979 + measuredLastBlock + ", but it should have been pointing to " //$NON-NLS-1$ 980 + lastBlock); 981 } 982 if (blockReportedSize != correctSize) { 983 throw describeProblem() 984 .addProblemAddress("block size", block, SHORT_SIZE) //$NON-NLS-1$ 985 .addProblemAddress("incoming pointer", addressOfPrevBlockPointer, PTR_SIZE) //$NON-NLS-1$ 986 .build("A block (" + block + ") of size " + measuredLastBlock //$NON-NLS-1$ //$NON-NLS-2$ 987 + " was in the free space list for blocks of size " + correctSize); //$NON-NLS-1$ 988 } 989 addressOfPrevBlockPointer = block + BLOCK_NEXT_OFFSET; 990 lastBlock = block; 991 block = followingBlock; 992 } 993 } 994 995 /** 996 * Performs a self-test on the free space trie list (used by the large block allocator) to check for corruption 997 */ validateFreeSpaceTries()998 private void validateFreeSpaceTries() { 999 int currentChunkNum = getInt(FREE_BLOCK_OFFSET); 1000 1001 if (currentChunkNum == 0) { 1002 return; 1003 } 1004 1005 Set<Integer> visited = new HashSet<>(); 1006 validateFreeSpaceNode(visited, currentChunkNum, 0); 1007 } 1008 validateFreeSpaceNode(Set<Integer> visited, int chunkNum, int parent)1009 private void validateFreeSpaceNode(Set<Integer> visited, int chunkNum, int parent) { 1010 if (visited.contains(chunkNum)) { 1011 throw describeProblem().build("Chunk " + chunkNum + "(parent = " + parent //$NON-NLS-1$//$NON-NLS-2$ 1012 + " appeared twice in the free space tree"); //$NON-NLS-1$ 1013 } 1014 1015 long chunkStart = (long) chunkNum * CHUNK_SIZE; 1016 int parentChunk = getInt(chunkStart + LargeBlock.PARENT_OFFSET); 1017 if (parentChunk != parent) { 1018 throw describeProblem() 1019 .addProblemAddress("parent pointer", chunkStart + LargeBlock.PARENT_OFFSET, Database.INT_SIZE) //$NON-NLS-1$ 1020 .build("Chunk " + chunkNum + " has the wrong parent. Expected " + parent //$NON-NLS-1$//$NON-NLS-2$ 1021 + " but found " + parentChunk); //$NON-NLS-1$ 1022 } 1023 1024 visited.add(chunkNum); 1025 int numChunks = getBlockHeaderForChunkNum(chunkNum); 1026 for (int testPosition = 0; testPosition < LargeBlock.ENTRIES_IN_CHILD_TABLE; testPosition++) { 1027 long nextChildChunkNumAddress = chunkStart + LargeBlock.CHILD_TABLE_OFFSET + (testPosition * INT_SIZE); 1028 int nextChildChunkNum = getInt(nextChildChunkNumAddress); 1029 1030 if (nextChildChunkNum == 0) { 1031 continue; 1032 } 1033 1034 int nextSize = getBlockHeaderForChunkNum(nextChildChunkNum); 1035 int sizeDifference = nextSize ^ numChunks; 1036 int firstDifference = LargeBlock.SIZE_OF_SIZE_FIELD * 8 - Integer.numberOfLeadingZeros( 1037 Integer.highestOneBit(sizeDifference)) - 1; 1038 1039 if (firstDifference != testPosition) { 1040 IndexExceptionBuilder descriptor = describeProblem(); 1041 attachBlockHeaderForChunkNum(descriptor, chunkNum); 1042 attachBlockHeaderForChunkNum(descriptor, nextChildChunkNum); 1043 throw descriptor.build("Chunk " + nextChildChunkNum + " contained an incorrect size of " //$NON-NLS-1$//$NON-NLS-2$ 1044 + nextSize + ". It was at position " + testPosition + " in parent " + chunkNum //$NON-NLS-1$ //$NON-NLS-2$ 1045 + " which had size " + numChunks); //$NON-NLS-1$ 1046 } 1047 1048 try { 1049 validateFreeSpaceNode(visited, nextChildChunkNum, chunkNum); 1050 } catch (IndexException e) { 1051 describeProblem() 1052 .addProblemAddress("child pointer from parent " + chunkNum, nextChildChunkNumAddress, //$NON-NLS-1$ 1053 Database.INT_SIZE) 1054 .attachTo(e); 1055 throw e; 1056 } 1057 } 1058 } 1059 1060 /** 1061 * Adds the given child block to the given parent subtree of the free space trie. Any existing 1062 * subtree under the given child block will be retained. 1063 * 1064 * @param parentChunkNum root of the existing tree, or 0 if the child is going to be the new root 1065 * @param newChildChunkNum the new child to insert 1066 */ insertChild(int parentChunkNum, int newChildChunkNum)1067 private void insertChild(int parentChunkNum, int newChildChunkNum) { 1068 if (parentChunkNum == 0) { 1069 putInt((long) newChildChunkNum * CHUNK_SIZE + LargeBlock.PARENT_OFFSET, parentChunkNum); 1070 putInt(FREE_BLOCK_OFFSET, newChildChunkNum); 1071 return; 1072 } 1073 int numChunks = getBlockHeaderForChunkNum(newChildChunkNum); 1074 for (;;) { 1075 int currentSize = getBlockHeaderForChunkNum(parentChunkNum); 1076 int difference = currentSize ^ numChunks; 1077 if (difference == 0) { 1078 // The newly added item is exactly the same size as this trie node 1079 insertFreeBlockAfter(parentChunkNum, newChildChunkNum); 1080 return; 1081 } 1082 1083 int firstDifference = LargeBlock.SIZE_OF_SIZE_FIELD * 8 - Integer.numberOfLeadingZeros(difference) - 1; 1084 long locationOfChildPointer = (long) parentChunkNum * CHUNK_SIZE + LargeBlock.CHILD_TABLE_OFFSET 1085 + (firstDifference * INT_SIZE); 1086 int childChunkNum = getInt(locationOfChildPointer); 1087 if (childChunkNum == 0) { 1088 putInt(locationOfChildPointer, newChildChunkNum); 1089 putInt((long) newChildChunkNum * CHUNK_SIZE + LargeBlock.PARENT_OFFSET, parentChunkNum); 1090 return; 1091 } 1092 parentChunkNum = childChunkNum; 1093 } 1094 } 1095 1096 /** 1097 * Adds the given block to the linked list of equally-sized free chunks in the free space trie. 1098 * Both chunks must be unused, must be the same size, and the previous chunk must already 1099 * be linked into the free space trie. The newly-added chunk must not have any children. 1100 * 1101 * @param prevChunkNum chunk number of previous block in the existing list 1102 * @param newChunkNum new chunk to be added to the list 1103 */ insertFreeBlockAfter(int prevChunkNum, int newChunkNum)1104 private void insertFreeBlockAfter(int prevChunkNum, int newChunkNum) { 1105 long prevChunkAddress = (long) prevChunkNum * CHUNK_SIZE; 1106 int nextChunkNum = getInt(prevChunkAddress + LargeBlock.NEXT_BLOCK_OFFSET); 1107 long nextChunkAddress = (long) nextChunkNum * CHUNK_SIZE; 1108 long newLockAddress = (long) newChunkNum * CHUNK_SIZE; 1109 1110 putInt(prevChunkAddress + LargeBlock.NEXT_BLOCK_OFFSET, newChunkNum); 1111 if (nextChunkNum != 0) { 1112 putInt(nextChunkAddress + LargeBlock.PREV_BLOCK_OFFSET, newChunkNum); 1113 } 1114 putInt(newLockAddress + LargeBlock.PREV_BLOCK_OFFSET, prevChunkNum); 1115 putInt(newLockAddress + LargeBlock.NEXT_BLOCK_OFFSET, nextChunkNum); 1116 } 1117 1118 /** 1119 * Returns the chunk number of the chunk at the start of a block, given the 1120 * chunk number of the chunk at the start of the following block. 1121 * 1122 * @param chunkNum the chunk number of the chunk immediately following the 1123 * chunk being queried 1124 * @return the chunk number of the chunk at the start of the previous block 1125 */ getFirstChunkOfBlockBefore(int chunkNum)1126 private int getFirstChunkOfBlockBefore(int chunkNum) { 1127 int blockChunks = Math.abs(getBlockFooterForChunkBefore(chunkNum)); 1128 return chunkNum - blockChunks; 1129 } 1130 1131 /** 1132 * Sets the block header and footer for the given range of chunks which make 1133 * up a contiguous block. 1134 * 1135 * @param firstChunkNum chunk number of the first chunk in the block 1136 * @param headerContent the content of the header. Its magnitude is the number of 1137 * chunks in the block. It is positive if the chunk is free and negative if 1138 * the chunk is in use. 1139 */ setBlockHeader(int firstChunkNum, int headerContent)1140 private void setBlockHeader(int firstChunkNum, int headerContent) { 1141 assert headerContent != 0; 1142 assert firstChunkNum < this.fChunksUsed; 1143 int numBlocks = Math.abs(headerContent); 1144 long firstChunkAddress = (long) firstChunkNum * CHUNK_SIZE; 1145 putInt(firstChunkAddress, headerContent); 1146 putInt(firstChunkAddress + ((long) numBlocks * CHUNK_SIZE) - LargeBlock.FOOTER_SIZE, headerContent); 1147 } 1148 1149 /** 1150 * Returns the size of the block (in number of chunks) starting at the given address. The return value is positive 1151 * if the block is free and negative if the block is allocated. 1152 */ 1153 private int getBlockHeaderForChunkNum(int firstChunkNum) { 1154 if (firstChunkNum >= this.fChunksUsed) { 1155 return 0; 1156 } 1157 return getInt((long) firstChunkNum * CHUNK_SIZE); 1158 } 1159 attachBlockHeaderForChunkNum(IndexExceptionBuilder builder, int firstChunkNum)1160 private void attachBlockHeaderForChunkNum(IndexExceptionBuilder builder, int firstChunkNum) { 1161 if (firstChunkNum >= this.fChunksUsed) { 1162 return; 1163 } 1164 builder.addProblemAddress("block header for chunk " + firstChunkNum, ((long) firstChunkNum * CHUNK_SIZE), //$NON-NLS-1$ 1165 Database.INT_SIZE); 1166 } 1167 1168 /** 1169 * Returns the size of the block (in number of chunks), given the (non-inclusive) address that the block ends at. 1170 * The return value is positive if the block is free and negative if the block is allocated. 1171 */ getBlockFooterForChunkBefore(int chunkNum)1172 private int getBlockFooterForChunkBefore(int chunkNum) { 1173 if (chunkNum < 2) { 1174 // Don't report the database header as a normal chunk. 1175 return 0; 1176 } 1177 return getInt((long) chunkNum * CHUNK_SIZE - LargeBlock.FOOTER_SIZE); 1178 } 1179 createNewChunks(int numChunks)1180 private int createNewChunks(int numChunks) throws IndexException { 1181 assert this.fExclusiveLock; 1182 synchronized (this.fCache) { 1183 final int firstChunkIndex = this.fChunksUsed; 1184 final int lastChunkIndex = firstChunkIndex + numChunks - 1; 1185 1186 final Chunk lastChunk = new Chunk(this, lastChunkIndex); 1187 1188 if (lastChunkIndex >= this.fChunks.length) { 1189 int increment = Math.max(1024, this.fChunks.length / 20); 1190 int newNumChunks = Math.max(lastChunkIndex + 1, this.fChunks.length + increment); 1191 Chunk[] newChunks = new Chunk[newNumChunks]; 1192 System.arraycopy(this.fChunks, 0, newChunks, 0, this.fChunks.length); 1193 this.fChunks = newChunks; 1194 } 1195 1196 this.fChunksUsed = lastChunkIndex + 1; 1197 if (DEBUG_PAGE_CACHE) { 1198 System.out.println("CHUNK " + lastChunk.fSequenceNumber + ": inserted into vector - instance " //$NON-NLS-1$//$NON-NLS-2$ 1199 + System.identityHashCode(lastChunk)); 1200 } 1201 this.fChunks[lastChunkIndex] = lastChunk; 1202 this.fMostRecentlyFetchedChunk = lastChunk; 1203 lastChunk.makeDirty(); 1204 this.fCache.add(lastChunk); 1205 long result = (long) firstChunkIndex * CHUNK_SIZE; 1206 1207 /* 1208 * Non-dense pointers are at most 31 bits dense pointers are at most 35 bits Check the sizes here and throw 1209 * an exception if the address is too large. By throwing the IndexException with the special status, the 1210 * indexing operation should be stopped. This is desired since generally, once the max size is exceeded, 1211 * there are lots of errors. 1212 */ 1213 long endAddress = result + ((long) numChunks * CHUNK_SIZE); 1214 if (endAddress > MAX_DB_SIZE) { 1215 Object bindings[] = { this.getLocation().getAbsolutePath(), MAX_DB_SIZE }; 1216 throw new IndexException(new Status(IStatus.ERROR, Package.PLUGIN_ID, Package.STATUS_DATABASE_TOO_LARGE, 1217 NLS.bind("Database too large! Address = " + endAddress + ", max size = " + MAX_DB_SIZE, //$NON-NLS-1$ //$NON-NLS-2$ 1218 bindings), null)); 1219 } 1220 1221 return firstChunkIndex; 1222 } 1223 } 1224 getAddressOfFirstBlockPointer(int blockSize)1225 private long getAddressOfFirstBlockPointer(int blockSize) { 1226 return MALLOC_TABLE_OFFSET + (blockSize / BLOCK_SIZE_DELTA - MIN_BLOCK_DELTAS) * INT_SIZE; 1227 } 1228 1229 /** 1230 * @param blockSize (must be a multiple of BLOCK_SIZE_DELTA) 1231 */ getFirstBlock(int blockSize)1232 private long getFirstBlock(int blockSize) throws IndexException { 1233 assert this.fLocked; 1234 return this.fHeaderChunk.getFreeRecPtr(getAddressOfFirstBlockPointer(blockSize)); 1235 } 1236 setFirstBlock(int blockSize, long block)1237 private void setFirstBlock(int blockSize, long block) throws IndexException { 1238 assert this.fExclusiveLock; 1239 this.fHeaderChunk.putFreeRecPtr(getAddressOfFirstBlockPointer(blockSize), block); 1240 } 1241 removeBlock(Chunk chunk, int blocksize, long block)1242 private void removeBlock(Chunk chunk, int blocksize, long block) throws IndexException { 1243 assert this.fExclusiveLock; 1244 1245 long prevblock = chunk.getFreeRecPtr(block + BLOCK_PREV_OFFSET); 1246 long nextblock = chunk.getFreeRecPtr(block + BLOCK_NEXT_OFFSET); 1247 if (prevblock != 0) { 1248 putFreeRecPtr(prevblock + BLOCK_NEXT_OFFSET, nextblock); 1249 } else { // We were the head. 1250 setFirstBlock(blocksize, nextblock); 1251 } 1252 1253 if (nextblock != 0) 1254 putFreeRecPtr(nextblock + BLOCK_PREV_OFFSET, prevblock); 1255 } 1256 addBlock(Chunk chunk, int blocksize, long block)1257 private void addBlock(Chunk chunk, int blocksize, long block) throws IndexException { 1258 assert this.fExclusiveLock; 1259 // Mark our size 1260 chunk.putShort(block, (short) blocksize); 1261 1262 // Add us to the head of the list. 1263 long prevfirst = getFirstBlock(blocksize); 1264 chunk.putFreeRecPtr(block + BLOCK_PREV_OFFSET, 0); 1265 chunk.putFreeRecPtr(block + BLOCK_NEXT_OFFSET, prevfirst); 1266 if (prevfirst != 0) 1267 putFreeRecPtr(prevfirst + BLOCK_PREV_OFFSET, block); 1268 setFirstBlock(blocksize, block); 1269 } 1270 1271 /** 1272 * Free an allocated block. 1273 * 1274 * @param address 1275 * memory address to be freed 1276 * @param poolId 1277 * the same ID that was previously passed into malloc when allocating this memory address 1278 */ free(long address, short poolId)1279 public void free(long address, short poolId) throws IndexException { 1280 getLog().start(this.freeTag); 1281 try { 1282 assert this.fExclusiveLock; 1283 if (address == 0) { 1284 return; 1285 } 1286 long blockSize; 1287 long block = address - BLOCK_HEADER_SIZE; 1288 Chunk chunk = getChunk(block); 1289 blockSize = -chunk.getShort(block); 1290 // We use a block size of 0 to indicate a large block that fills a range of chunks 1291 if (blockSize == 0) { 1292 int offsetIntoChunk = (int) (address % CHUNK_SIZE); 1293 assert offsetIntoChunk == LargeBlock.HEADER_SIZE + BLOCK_HEADER_SIZE; 1294 // Deallocating a large block 1295 // This was a large block. It uses a sequence of full chunks. 1296 int chunkNum = (int) (address / CHUNK_SIZE); 1297 int numChunks = -getBlockHeaderForChunkNum(chunkNum); 1298 if (numChunks < 0) { 1299 IndexExceptionBuilder builder = describeProblem(); 1300 if (chunkNum < this.fChunksUsed) { 1301 builder.addProblemAddress("block header", (long) chunkNum * CHUNK_SIZE, INT_SIZE); //$NON-NLS-1$ 1302 } 1303 throw builder.build("Already freed large block " + address); //$NON-NLS-1$ 1304 } 1305 blockSize = (long) numChunks * CHUNK_SIZE; 1306 this.log.recordFree(address, (int)(blockSize - BLOCK_HEADER_SIZE)); 1307 freeLargeChunk(chunkNum, numChunks); 1308 } else { 1309 // Deallocating a normal block 1310 // TODO Look for opportunities to merge small blocks 1311 if (blockSize < 0) { 1312 throw describeProblem() 1313 .addProblemAddress("block size", block, SHORT_SIZE) //$NON-NLS-1$ 1314 .build("Already freed record " + address); //$NON-NLS-1$ 1315 } 1316 this.log.recordFree(address, (int)(blockSize - BLOCK_HEADER_SIZE)); 1317 int offset = Chunk.recPtrToIndex(address); 1318 if (offset + blockSize > CHUNK_SIZE) { 1319 throw describeProblem() 1320 .addProblemAddress("block size", block, SHORT_SIZE) //$NON-NLS-1$ 1321 .build("Attempting to free chunk of impossible size. The block at address " //$NON-NLS-1$ 1322 + address + " in chunk " + chunk.fSequenceNumber + " offset " + offset //$NON-NLS-1$//$NON-NLS-2$ 1323 + " can't be as large as " //$NON-NLS-1$ 1324 + blockSize + " bytes since that would make it extend beyond the end of the chunk"); //$NON-NLS-1$ 1325 } 1326 addBlock(chunk, (int) blockSize, block); 1327 } 1328 1329 if (DEBUG_FREE_SPACE) { 1330 periodicValidateFreeSpace(); 1331 } 1332 1333 this.freed += blockSize; 1334 this.memoryUsage.recordFree(poolId, blockSize); 1335 } finally { 1336 getLog().end(this.freeTag); 1337 } 1338 } 1339 1340 /** 1341 * Periodically performs validation of the free space in the database. Validation is very expensive, so the 1342 * validation period uses exponential falloff so validations happen less and less frequently over 1343 * time. Returns true iff validation happened on this iteration. 1344 */ periodicValidateFreeSpace()1345 private boolean periodicValidateFreeSpace() { 1346 this.validateCounter++; 1347 if (this.validateCounter > this.nextValidation) { 1348 validateFreeSpace(); 1349 this.nextValidation = this.validateCounter * 2; 1350 return true; 1351 } 1352 return false; 1353 } 1354 freeLargeChunk(int chunkNum, int numChunks)1355 private void freeLargeChunk(int chunkNum, int numChunks) { 1356 assert chunkNum > 0; 1357 assert numChunks > 0; 1358 int prevBlockHeader = getBlockFooterForChunkBefore(chunkNum); 1359 int nextBlockChunkNum = chunkNum + numChunks; 1360 int nextBlockHeader = getBlockHeaderForChunkNum(nextBlockChunkNum); 1361 1362 // If the previous block is unused, merge with it 1363 if (prevBlockHeader > 0) { 1364 int prevBlockChunkNum = getFirstChunkOfBlockBefore(chunkNum); 1365 1366 unlinkFreeBlock(prevBlockChunkNum); 1367 chunkNum = prevBlockChunkNum; 1368 numChunks += prevBlockHeader; 1369 } 1370 1371 // If the next block is unused, merge with it 1372 if (nextBlockHeader > 0) { 1373 unlinkFreeBlock(nextBlockChunkNum); 1374 numChunks += nextBlockHeader; 1375 } 1376 1377 // Block merging is done. Now reinsert the merged block into the free space trie 1378 linkFreeBlockToTrie(chunkNum, numChunks); 1379 } 1380 putByte(long offset, byte value)1381 public void putByte(long offset, byte value) throws IndexException { 1382 getChunk(offset).putByte(offset, value); 1383 } 1384 getByte(long offset)1385 public byte getByte(long offset) throws IndexException { 1386 return getChunk(offset).getByte(offset); 1387 } 1388 putInt(long offset, int value)1389 public void putInt(long offset, int value) throws IndexException { 1390 getChunk(offset).putInt(offset, value); 1391 } 1392 getInt(long offset)1393 public int getInt(long offset) throws IndexException { 1394 return getChunk(offset).getInt(offset); 1395 } 1396 putRecPtr(long offset, long value)1397 public void putRecPtr(long offset, long value) throws IndexException { 1398 getChunk(offset).putRecPtr(offset, value); 1399 } 1400 getRecPtr(long offset)1401 public long getRecPtr(long offset) throws IndexException { 1402 return getChunk(offset).getRecPtr(offset); 1403 } 1404 putFreeRecPtr(long offset, long value)1405 private void putFreeRecPtr(long offset, long value) throws IndexException { 1406 getChunk(offset).putFreeRecPtr(offset, value); 1407 } 1408 getFreeRecPtr(long offset)1409 private long getFreeRecPtr(long offset) throws IndexException { 1410 return getChunk(offset).getFreeRecPtr(offset); 1411 } 1412 put3ByteUnsignedInt(long offset, int value)1413 public void put3ByteUnsignedInt(long offset, int value) throws IndexException { 1414 getChunk(offset).put3ByteUnsignedInt(offset, value); 1415 } 1416 get3ByteUnsignedInt(long offset)1417 public int get3ByteUnsignedInt(long offset) throws IndexException { 1418 return getChunk(offset).get3ByteUnsignedInt(offset); 1419 } 1420 putShort(long offset, short value)1421 public void putShort(long offset, short value) throws IndexException { 1422 getChunk(offset).putShort(offset, value); 1423 } 1424 getShort(long offset)1425 public short getShort(long offset) throws IndexException { 1426 return getChunk(offset).getShort(offset); 1427 } 1428 putLong(long offset, long value)1429 public void putLong(long offset, long value) throws IndexException { 1430 getChunk(offset).putLong(offset, value); 1431 } 1432 putDouble(long offset, double value)1433 public void putDouble(long offset, double value) throws IndexException { 1434 getChunk(offset).putDouble(offset, value); 1435 } 1436 putFloat(long offset, float value)1437 public void putFloat(long offset, float value) throws IndexException { 1438 getChunk(offset).putFloat(offset, value); 1439 } 1440 getLong(long offset)1441 public long getLong(long offset) throws IndexException { 1442 return getChunk(offset).getLong(offset); 1443 } 1444 getDouble(long offset)1445 public double getDouble(long offset) throws IndexException { 1446 return getChunk(offset).getDouble(offset); 1447 } 1448 getFloat(long offset)1449 public float getFloat(long offset) throws IndexException { 1450 return getChunk(offset).getFloat(offset); 1451 } 1452 putChar(long offset, char value)1453 public void putChar(long offset, char value) throws IndexException { 1454 getChunk(offset).putChar(offset, value); 1455 } 1456 getChar(long offset)1457 public char getChar(long offset) throws IndexException { 1458 return getChunk(offset).getChar(offset); 1459 } 1460 clearBytes(long offset, int byteCount)1461 public void clearBytes(long offset, int byteCount) throws IndexException { 1462 getChunk(offset).clear(offset, byteCount); 1463 } 1464 putBytes(long offset, byte[] data, int len)1465 public void putBytes(long offset, byte[] data, int len) throws IndexException { 1466 getChunk(offset).put(offset, data, len); 1467 } 1468 putBytes(long offset, byte[] data, int dataPos, int len)1469 public void putBytes(long offset, byte[] data, int dataPos, int len) throws IndexException { 1470 getChunk(offset).put(offset, data, dataPos, len); 1471 } 1472 getBytes(long offset, byte[] data)1473 public void getBytes(long offset, byte[] data) throws IndexException { 1474 getChunk(offset).get(offset, data); 1475 } 1476 getBytes(long offset, byte[] data, int dataPos, int len)1477 public void getBytes(long offset, byte[] data, int dataPos, int len) throws IndexException { 1478 getChunk(offset).get(offset, data, dataPos, len); 1479 } 1480 newString(String string)1481 public IString newString(String string) throws IndexException { 1482 return newString(string.toCharArray()); 1483 } 1484 newString(char[] chars)1485 public IString newString(char[] chars) throws IndexException { 1486 int len= chars.length; 1487 int bytelen; 1488 final boolean useBytes = useBytes(chars); 1489 if (useBytes) { 1490 bytelen= len; 1491 } else { 1492 bytelen= 2 * len; 1493 } 1494 1495 if (bytelen > ShortString.MAX_BYTE_LENGTH) { 1496 return new LongString(this, chars, useBytes); 1497 } else { 1498 return new ShortString(this, chars, useBytes); 1499 } 1500 } 1501 useBytes(char[] chars)1502 private boolean useBytes(char[] chars) { 1503 for (char c : chars) { 1504 if ((c & 0xff00) != 0) 1505 return false; 1506 } 1507 return true; 1508 } 1509 getString(long offset)1510 public IString getString(long offset) throws IndexException { 1511 final int l = getInt(offset); 1512 int bytelen= l < 0 ? -l : 2 * l; 1513 if (bytelen > ShortString.MAX_BYTE_LENGTH) { 1514 return new LongString(this, offset); 1515 } 1516 return new ShortString(this, offset); 1517 } 1518 getDatabaseSize()1519 public long getDatabaseSize() { 1520 return (long) this.fChunksUsed * CHUNK_SIZE; 1521 } 1522 1523 /** 1524 * Returns the number of bytes freed by {@link #free(long, short)} since this {@link Database} instance was 1525 * instantiated. Intended for use in unit tests. 1526 */ getBytesFreed()1527 public long getBytesFreed() { 1528 return this.freed; 1529 } 1530 1531 /** 1532 * Returns the number of bytes allocated by {@link #malloc(long, short)} since this {@link Database} instance was 1533 * instantiated. Intended for use in unit tests. 1534 */ getBytesAllocated()1535 public long getBytesAllocated() { 1536 return this.malloced; 1537 } 1538 1539 /** 1540 * For debugging purposes, only. 1541 */ reportFreeBlocks()1542 public void reportFreeBlocks() throws IndexException { 1543 System.out.println("Allocated size: " + formatByteString(getDatabaseSize())); //$NON-NLS-1$ 1544 System.out.println("malloc'ed: " + formatByteString(this.malloced)); //$NON-NLS-1$ 1545 System.out.println("free'd: " + formatByteString(this.freed)); //$NON-NLS-1$ 1546 System.out.println("wasted: " + formatByteString((getDatabaseSize() - (this.malloced - this.freed)))); //$NON-NLS-1$ 1547 System.out.println("Free blocks"); //$NON-NLS-1$ 1548 for (int bs = MIN_BLOCK_DELTAS*BLOCK_SIZE_DELTA; bs <= CHUNK_SIZE; bs += BLOCK_SIZE_DELTA) { 1549 int count = 0; 1550 long block = getFirstBlock(bs); 1551 while (block != 0) { 1552 ++count; 1553 block = getFreeRecPtr(block + BLOCK_NEXT_OFFSET); 1554 } 1555 if (count != 0) 1556 System.out.println("Block size: " + bs + "=" + count); //$NON-NLS-1$ //$NON-NLS-2$ 1557 } 1558 } 1559 1560 /** 1561 * Closes the database. 1562 * <p> 1563 * The behavior of any further calls to the Database is undefined 1564 * @throws IndexException 1565 */ close()1566 public void close() throws IndexException { 1567 assert this.fExclusiveLock; 1568 flush(); 1569 removeChunksFromCache(); 1570 1571 this.log.clear(); 1572 // Chunks have been removed from the cache, so we are fine. 1573 this.fHeaderChunk.clear(0, CHUNK_SIZE); 1574 this.memoryUsage.refresh(); 1575 this.fHeaderChunk.fDirty= false; 1576 this.dirtyChunkSet.clear(); 1577 this.fChunks= new Chunk[] { null }; 1578 this.fChunksUsed = this.fChunks.length; 1579 try { 1580 this.fFile.close(); 1581 } catch (IOException e) { 1582 throw new IndexException(new DBStatus(e)); 1583 } 1584 } 1585 1586 /** 1587 * This method is public for testing purposes only. 1588 */ getLocation()1589 public File getLocation() { 1590 return this.fLocation; 1591 } 1592 1593 /** 1594 * Called from any thread via the cache, protected by {@link #fCache}. 1595 */ checkIfChunkReleased(final Chunk chunk)1596 void checkIfChunkReleased(final Chunk chunk) { 1597 if (!chunk.fDirty && chunk.fCacheIndex < 0) { 1598 if (DEBUG_PAGE_CACHE) { 1599 System.out.println("CHUNK " + chunk.fSequenceNumber //$NON-NLS-1$ 1600 + ": removing from vector in releaseChunk - instance " + System.identityHashCode(chunk)); //$NON-NLS-1$ 1601 } 1602 this.fChunks[chunk.fSequenceNumber]= null; 1603 } 1604 } 1605 chunkDirtied(final Chunk chunk)1606 void chunkDirtied(final Chunk chunk) { 1607 if (chunk.fSequenceNumber < NUM_HEADER_CHUNKS) { 1608 return; 1609 } 1610 this.dirtyChunkSet.add(chunk); 1611 } 1612 chunkCleaned(final Chunk chunk)1613 void chunkCleaned(final Chunk chunk) { 1614 if (chunk.fSequenceNumber < NUM_HEADER_CHUNKS) { 1615 return; 1616 } 1617 this.dirtyChunkSet.remove(chunk); 1618 checkIfChunkReleased(chunk); 1619 } 1620 1621 /** 1622 * Returns the cache used for this database. 1623 * @since 4.0 1624 */ getChunkCache()1625 public ChunkCache getChunkCache() { 1626 return this.fCache; 1627 } 1628 1629 /** 1630 * Asserts that database is used by one thread exclusively. This is necessary when doing 1631 * write operations. 1632 */ setExclusiveLock()1633 public void setExclusiveLock() { 1634 this.fExclusiveLock= true; 1635 this.fLocked= true; 1636 } 1637 setLocked(boolean val)1638 public void setLocked(boolean val) { 1639 this.fLocked= val; 1640 } 1641 giveUpExclusiveLock()1642 public void giveUpExclusiveLock() { 1643 this.fExclusiveLock = false; 1644 } 1645 flush()1646 public boolean flush() throws IndexException { 1647 boolean wasInterrupted = false; 1648 assert this.fLocked; 1649 ArrayList<Chunk> dirtyChunks= new ArrayList<>(); 1650 synchronized (this.fCache) { 1651 dirtyChunks.addAll(this.dirtyChunkSet); 1652 } 1653 sortBySequenceNumber(dirtyChunks); 1654 1655 long startTime = System.currentTimeMillis(); 1656 // Also handles header chunk. 1657 wasInterrupted = flushAndUnlockChunks(dirtyChunks, true) || wasInterrupted; 1658 long elapsedTime = System.currentTimeMillis() - startTime; 1659 this.totalFlushTime += elapsedTime; 1660 1661 return wasInterrupted; 1662 } 1663 sortBySequenceNumber(ArrayList<Chunk> dirtyChunks)1664 private void sortBySequenceNumber(ArrayList<Chunk> dirtyChunks) { 1665 dirtyChunks.sort((a, b) -> {return a.fSequenceNumber - b.fSequenceNumber;}); 1666 } 1667 1668 /** 1669 * Interrupting the thread with {@link Thread#interrupt()} won't interrupt the write. Returns true iff an attempt 1670 * was made to interrupt the thread with {@link Thread#interrupt()}. 1671 * 1672 * @throws IndexException 1673 */ flushAndUnlockChunks(final ArrayList<Chunk> dirtyChunks, boolean isComplete)1674 private boolean flushAndUnlockChunks(final ArrayList<Chunk> dirtyChunks, boolean isComplete) throws IndexException { 1675 boolean wasInterrupted = false; 1676 assert !Thread.holdsLock(this.fCache); 1677 final boolean haveDirtyChunks = !dirtyChunks.isEmpty(); 1678 if (haveDirtyChunks || this.fHeaderChunk.fDirty) { 1679 wasInterrupted = markFileIncomplete() || wasInterrupted; 1680 } 1681 if (haveDirtyChunks) { 1682 double desiredWriteBytesPerMs = Database.MIN_BYTES_PER_MILLISECOND; 1683 synchronized (this.fCache) { 1684 if (this.cacheMisses > 100) { 1685 double measuredReadBytesPerMs = getAverageReadBytesPerMs(); 1686 if (measuredReadBytesPerMs > 0) { 1687 desiredWriteBytesPerMs = measuredReadBytesPerMs / 2; 1688 } 1689 } 1690 } 1691 desiredWriteBytesPerMs = Math.max(desiredWriteBytesPerMs, Database.MIN_BYTES_PER_MILLISECOND); 1692 ChunkWriter writer = new ChunkWriter(WRITE_BUFFER_SIZE, desiredWriteBytesPerMs, this::write); 1693 try { 1694 for (Chunk chunk : dirtyChunks) { 1695 if (chunk.fDirty) { 1696 boolean wasCanceled = false; 1697 if (DEBUG_PAGE_CACHE) { 1698 System.out.println("CHUNK " + chunk.fSequenceNumber + ": flushing - instance " //$NON-NLS-1$//$NON-NLS-2$ 1699 + System.identityHashCode(chunk)); 1700 } 1701 byte[] nextBytes; 1702 synchronized (this.fCache) { 1703 nextBytes = chunk.getBytes(); 1704 chunk.fDirty = false; 1705 chunkCleaned(chunk); 1706 } 1707 wasCanceled = writer.write((long) chunk.fSequenceNumber * Database.CHUNK_SIZE, nextBytes); 1708 1709 wasInterrupted = wasCanceled || wasInterrupted; 1710 } 1711 } 1712 writer.flush(); 1713 synchronized (this.fCache) { 1714 this.pageWritesBytes += writer.getBytesWritten(); 1715 this.totalWriteTimeMs += writer.getTotalWriteTimeMs(); 1716 } 1717 } catch (IOException e) { 1718 throw new IndexException(new DBStatus(e)); 1719 } 1720 } 1721 1722 if (isComplete) { 1723 if (this.fHeaderChunk.fDirty || this.fIsMarkedIncomplete) { 1724 this.fHeaderChunk.putInt(VERSION_OFFSET, this.fVersion); 1725 wasInterrupted = this.fHeaderChunk.flush() || wasInterrupted; 1726 this.fIsMarkedIncomplete= false; 1727 } 1728 } 1729 return wasInterrupted; 1730 } 1731 markFileIncomplete()1732 private boolean markFileIncomplete() throws IndexException { 1733 boolean wasInterrupted = false; 1734 if (!this.fIsMarkedIncomplete) { 1735 this.fIsMarkedIncomplete= true; 1736 try { 1737 final ByteBuffer buf= ByteBuffer.wrap(new byte[4]); 1738 wasInterrupted = performUninterruptableWrite(() -> this.fFile.getChannel().write(buf, 0)); 1739 this.bytesWritten += 4; 1740 } catch (IOException e) { 1741 throw new IndexException(new DBStatus(e)); 1742 } 1743 } 1744 return wasInterrupted; 1745 } 1746 resetCacheCounters()1747 public void resetCacheCounters() { 1748 this.cacheHits = 0; 1749 this.cacheMisses = 0; 1750 this.bytesWritten = 0; 1751 this.totalFlushTime = 0; 1752 this.pageWritesBytes = 0; 1753 this.totalWriteTimeMs = 0; 1754 this.totalReadTimeMs = 0; 1755 } 1756 getBytesWritten()1757 public long getBytesWritten() { 1758 return this.bytesWritten; 1759 } 1760 getAverageReadBytesPerMs()1761 public double getAverageReadBytesPerMs() { 1762 long reads = this.cacheMisses; 1763 long time = this.totalReadTimeMs; 1764 1765 if (time == 0) { 1766 return 0; 1767 } 1768 1769 return (double)(reads * CHUNK_SIZE) / (double) time; 1770 } 1771 getAverageWriteBytesPerMs()1772 public double getAverageWriteBytesPerMs() { 1773 long time = this.totalWriteTimeMs; 1774 long writes = this.pageWritesBytes; 1775 1776 return ((double) writes / (double) time); 1777 } 1778 getBytesRead()1779 public long getBytesRead() { 1780 return this.cacheMisses * CHUNK_SIZE; 1781 } 1782 getCacheHits()1783 public long getCacheHits() { 1784 return this.cacheHits; 1785 } 1786 getCacheMisses()1787 public long getCacheMisses() { 1788 return this.cacheMisses; 1789 } 1790 getCumulativeFlushTimeMs()1791 public long getCumulativeFlushTimeMs() { 1792 return this.totalFlushTime; 1793 } 1794 getSizeBytes()1795 public long getSizeBytes() throws IOException { 1796 return this.fFile.length(); 1797 } 1798 getChunkCount()1799 public int getChunkCount() { 1800 return this.fChunksUsed; 1801 } 1802 1803 /** 1804 * A Record Pointer is a pointer as returned by Database.malloc(). 1805 * This is a pointer to a block + BLOCK_HEADER_SIZE. 1806 */ putRecPtr(final long value, byte[] buffer, int idx)1807 public static void putRecPtr(final long value, byte[] buffer, int idx) { 1808 final int denseValue = value == 0 ? 0 : Chunk.compressFreeRecPtr(value - BLOCK_HEADER_SIZE); 1809 Chunk.putInt(denseValue, buffer, idx); 1810 } 1811 1812 /** 1813 * A Record Pointer is a pointer as returned by Database.malloc(). 1814 * This is a pointer to a block + BLOCK_HEADER_SIZE. 1815 */ getRecPtr(byte[] buffer, final int idx)1816 public static long getRecPtr(byte[] buffer, final int idx) { 1817 int value = Chunk.getInt(buffer, idx); 1818 long address = Chunk.expandToFreeRecPtr(value); 1819 return address != 0 ? (address + BLOCK_HEADER_SIZE) : address; 1820 } 1821 getMemoryStats()1822 public MemoryStats getMemoryStats() { 1823 return this.memoryUsage; 1824 } 1825 1826 /** 1827 * Returns the number of bytes that can fit in the payload of the given number of chunks. 1828 */ getBytesThatFitInChunks(int numChunks)1829 public static long getBytesThatFitInChunks(int numChunks) { 1830 return CHUNK_SIZE * (long) numChunks - LargeBlock.HEADER_SIZE - LargeBlock.FOOTER_SIZE - BLOCK_HEADER_SIZE; 1831 } 1832 1833 /** 1834 * Returns the number of chunks needed to fit the given number of bytes of payload. 1835 */ getChunksNeededForBytes(long datasize)1836 public static int getChunksNeededForBytes(long datasize) { 1837 return divideRoundingUp(datasize + BLOCK_HEADER_SIZE + LargeBlock.HEADER_SIZE + LargeBlock.FOOTER_SIZE, 1838 CHUNK_SIZE); 1839 } 1840 getCache()1841 public ChunkCache getCache() { 1842 return this.fCache; 1843 } 1844 getDirtyChunkCount()1845 public int getDirtyChunkCount() { 1846 return this.dirtyChunkSet.size(); 1847 } 1848 formatByteString(long valueInBytes)1849 public static String formatByteString(long valueInBytes) { 1850 final double MB = 1024 * 1024; 1851 double value = valueInBytes; 1852 String suffix = "B"; //$NON-NLS-1$ 1853 1854 if (value > 1024) { 1855 suffix = "MiB"; //$NON-NLS-1$ 1856 value /= MB; 1857 } 1858 1859 DecimalFormat mbFormat = new DecimalFormat("#0.###"); //$NON-NLS-1$ 1860 return mbFormat.format(value) + suffix; 1861 } 1862 getChunkStats()1863 public ChunkStats getChunkStats() { 1864 synchronized (this.fCache) { 1865 int count = 0; 1866 int dirtyChunks = 0; 1867 int nonDirtyChunksNotInCache = 0; 1868 for (Chunk next : this.fChunks) { 1869 if (next != null) { 1870 count++; 1871 if (next.fDirty) { 1872 dirtyChunks++; 1873 } else if (next.fCacheIndex < 0) { 1874 nonDirtyChunksNotInCache++; 1875 } 1876 } 1877 } 1878 return new ChunkStats(this.fChunks.length, count, dirtyChunks, nonDirtyChunksNotInCache); 1879 } 1880 } 1881 describeProblem()1882 public IndexExceptionBuilder describeProblem() { 1883 return new IndexExceptionBuilder(this); 1884 } 1885 } 1886