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