Bug 510461 - Database.giveUpExclusiveLock is a severe bottleneck

Change-Id: I24f57186dea252309e7b199fdf67ff44cd55578a
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Database.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Database.java
index 64c7da0..8847cc4 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Database.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Database.java
@@ -23,6 +23,8 @@
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.FileChannel;
 import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
 
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.OperationCanceledException;
@@ -109,6 +111,7 @@
 	public static final int WRITE_NUMBER_OFFSET = FREE_BLOCK_OFFSET + PTR_SIZE;
 	public static final int MALLOC_STATS_OFFSET = WRITE_NUMBER_OFFSET + LONG_SIZE;
 	public static final int DATA_AREA_OFFSET = MALLOC_STATS_OFFSET + MemoryStats.SIZE;
+	private static final int NUM_HEADER_CHUNKS = 1;
 
 	// Malloc pool IDs (used for classifying memory allocations and recording statistics about them)
 	/** Misc pool -- may be used for any purpose that doesn't fit the IDs below. */
@@ -132,7 +135,16 @@
 
 	private int fVersion;
 	private final Chunk fHeaderChunk;
+	/**
+	 * Stores the {@link Chunk} associated with each page number or null if the chunk isn't loaded. Synchronize on
+	 * {@link #fCache} before accessing.
+	 */
 	private Chunk[] fChunks;
+
+	/**
+	 * Holds all the non-null entries from {@link #fChunks}. Synchronize on {@link #fCache} before accessing.
+	 */
+	private HashSet<Chunk> allocatedChunks = new HashSet<>();
 	private int fChunksUsed;
 	private ChunkCache fCache;
 
@@ -328,11 +340,12 @@
 
 	private void removeChunksFromCache() {
 		synchronized (this.fCache) {
-			for (int i= 1; i < this.fChunks.length; i++) {
-				Chunk chunk= this.fChunks[i];
-				if (chunk != null) {
+			for (Iterator<Chunk> next = this.allocatedChunks.iterator(); next.hasNext();) {
+				Chunk chunk = next.next();
+				if (chunk.fSequenceNumber >= NUM_HEADER_CHUNKS) {
 					this.fCache.remove(chunk);
-					this.fChunks[i]= null;
+					this.fChunks[chunk.fSequenceNumber] = null;
+					next.remove();
 				}
 			}
 		}
@@ -362,6 +375,7 @@
 				chunk = new Chunk(this, index);
 				chunk.read();
 				this.fChunks[index] = chunk;
+				this.allocatedChunks.add(chunk);
 			} else {
 				this.cacheHits++;
 			}
@@ -831,6 +845,7 @@
 
 			this.fChunksUsed = lastChunkIndex + 1;
 			this.fChunks[lastChunkIndex] = lastChunk;
+			this.allocatedChunks.add(lastChunk);
 			this.fCache.add(lastChunk, true);
 			long result = (long) firstChunkIndex * CHUNK_SIZE;
 
@@ -1182,6 +1197,7 @@
 	void releaseChunk(final Chunk chunk) {
 		if (!chunk.fLocked) {
 			this.fChunks[chunk.fSequenceNumber]= null;
+			this.allocatedChunks.remove(chunk);
 		}
 	}
 
@@ -1212,16 +1228,17 @@
 			try {
 				ArrayList<Chunk> dirtyChunks= new ArrayList<>();
 				synchronized (this.fCache) {
-					for (int i= 1; i < this.fChunksUsed; i++) {
-						Chunk chunk= this.fChunks[i];
-						if (chunk != null) {
+					for (Iterator<Chunk> iter = this.allocatedChunks.iterator(); iter.hasNext();) {
+						Chunk chunk = iter.next();
+						if (chunk.fSequenceNumber >= NUM_HEADER_CHUNKS) {
 							if (chunk.fCacheIndex < 0) {
 								// Locked chunk that has been removed from cache.
 								if (chunk.fDirty) {
 									dirtyChunks.add(chunk); // Keep in fChunks until it is flushed.
 								} else {
 									chunk.fLocked= false;
-									this.fChunks[i]= null;
+									this.fChunks[chunk.fSequenceNumber]= null;
+									iter.remove();
 								}
 							} else if (chunk.fLocked) {
 								// Locked chunk, still in cache.
@@ -1238,6 +1255,7 @@
 						}
 					}
 				}
+				sortBySequenceNumber(dirtyChunks);
 				// Also handles header chunk.
 				wasInterrupted = flushAndUnlockChunks(dirtyChunks, flush) || wasInterrupted;
 			} finally {
@@ -1247,6 +1265,10 @@
 		return wasInterrupted;
 	}
 
+	private void sortBySequenceNumber(ArrayList<Chunk> dirtyChunks) {
+		dirtyChunks.sort((a, b) -> {return a.fSequenceNumber - b.fSequenceNumber;});
+	}
+
 	public boolean flush() throws IndexException {
 		boolean wasInterrupted = false;
 		assert this.fLocked;
@@ -1262,14 +1284,15 @@
 		// Be careful as other readers may access chunks concurrently.
 		ArrayList<Chunk> dirtyChunks= new ArrayList<>();
 		synchronized (this.fCache) {
-			for (int i= 1; i < this.fChunksUsed ; i++) {
-				Chunk chunk= this.fChunks[i];
-				if (chunk != null && chunk.fDirty) {
+			for (Chunk chunk : this.allocatedChunks) {
+				if (chunk.fSequenceNumber >= 1 && chunk.fDirty) {
 					dirtyChunks.add(chunk);
 				}
 			}
 		}
 
+		sortBySequenceNumber(dirtyChunks);
+
 		// Also handles header chunk.
 		return flushAndUnlockChunks(dirtyChunks, true) || wasInterrupted;
 	}
@@ -1301,6 +1324,7 @@
 						chunk.fLocked= false;
 						if (chunk.fCacheIndex < 0) {
 							this.fChunks[chunk.fSequenceNumber]= null;
+							this.allocatedChunks.remove(chunk);
 						}
 					}
 				}