blob: acb3a088b52fc83ddea316a1e35df3d27d76a691 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* @author max
*/
package com.intellij.util.io;
import com.intellij.util.containers.hash.LinkedHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
@SuppressWarnings({"AssignmentToStaticFieldFromInstanceMethod"})
public class PagePool {
private final Map<PoolPageKey, Page> myProtectedQueue;
private final Map<PoolPageKey, Page> myProbationalQueue;
private int finalizationId = 0;
private final TreeMap<PoolPageKey, FinalizationRequest> myFinalizationQueue = new TreeMap<PoolPageKey, FinalizationRequest>();
private final Object lock = new Object();
private final Object finalizationMonitor = new Object();
private final PoolPageKey keyInstance = new PoolPageKey(null, -1);
private PoolPageKey lastFinalizedKey = null;
public PagePool(final int protectedPagesLimit, final int probationalPagesLimit) {
myProbationalQueue = new LinkedHashMap<PoolPageKey,Page>(probationalPagesLimit * 2, 0.6f, true) {
@Override
protected boolean removeEldestEntry(final Map.Entry<PoolPageKey, Page> eldest) {
if (size() > probationalPagesLimit) {
scheduleFinalization(eldest.getValue());
return true;
}
return false;
}
};
myProtectedQueue = new LinkedHashMap<PoolPageKey, Page>(protectedPagesLimit, 0.6f, true) {
@Override
protected boolean removeEldestEntry(final Map.Entry<PoolPageKey, Page> eldest) {
if (size() > protectedPagesLimit) {
myProbationalQueue.put(eldest.getKey(), eldest.getValue());
return true;
}
return false;
}
};
}
@SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) private static int hits = 0;
@SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) private static int cache_misses = 0;
@SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) private static int same_page_hits = 0;
@SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) private static int protected_queue_hits = 0;
@SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) private static int probational_queue_hits = 0;
@SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) private static int finalization_queue_hits = 0;
public static final PagePool SHARED = new PagePool(500, 500);
private RandomAccessDataFile lastOwner = null;
private long lastOffset = 0;
private Page lastHit = null;
@SuppressWarnings({"AssignmentToStaticFieldFromInstanceMethod"})
@NotNull
public Page alloc(RandomAccessDataFile owner, long offset) {
synchronized (lock) {
offset -= offset % Page.PAGE_SIZE;
hits++;
if (owner == lastOwner && offset == lastOffset) {
same_page_hits++;
return lastHit;
}
lastOffset = offset;
lastOwner = owner;
lastHit = hitQueues(owner, offset);
flushFinalizationQueue(Integer.MAX_VALUE);
return lastHit;
}
}
private Page hitQueues(final RandomAccessDataFile owner, final long offset) {
PoolPageKey key = setupKey(owner, offset);
Page page = myProtectedQueue.get(key);
if (page != null) {
protected_queue_hits++;
return page;
}
page = myProbationalQueue.remove(key);
if (page != null) {
probational_queue_hits++;
toProtectedQueue(page);
return page;
}
final FinalizationRequest request = myFinalizationQueue.remove(key);
if (request != null) {
page = request.page;
finalization_queue_hits++;
toProtectedQueue(page);
return page;
}
cache_misses++;
page = new Page(owner, offset);
myProbationalQueue.put(keyForPage(page), page);
return page;
}
//private long lastFlushTime = 0;
private static double percent(int part, int whole) {
return ((double)part * 1000 / whole) / 10;
}
@SuppressWarnings({"ALL"})
public static void printStatistics() {
System.out.println("Total requests: " + hits);
System.out.println("Same page hits: " + same_page_hits + " (" + percent(same_page_hits, hits) + "%)");
System.out.println("Protected queue hits: " + protected_queue_hits + " (" + percent(protected_queue_hits, hits) + "%)");
System.out.println("Probatinonal queue hits: " + probational_queue_hits + " (" + percent(probational_queue_hits, hits) + "%)");
System.out.println("Finalization queue hits: " + finalization_queue_hits + " (" + percent(finalization_queue_hits, hits) + "%)");
System.out.println("Cache misses: " + cache_misses + " (" + percent(cache_misses, hits) + "%)");
System.out.println("Total reads: " + RandomAccessDataFile.totalReads + ". Bytes read: " + RandomAccessDataFile.totalReadBytes);
System.out.println("Total writes: " + RandomAccessDataFile.totalWrites + ". Bytes written: " + RandomAccessDataFile.totalWriteBytes);
}
private static PoolPageKey keyForPage(final Page page) {
return page.getKey();
}
private void toProtectedQueue(final Page page) {
myProtectedQueue.put(keyForPage(page), page);
}
private PoolPageKey setupKey(RandomAccessDataFile owner, long offset) {
keyInstance.setup(owner, offset);
return keyInstance;
}
public void flushPages(final RandomAccessDataFile owner) {
flushPages(owner, Integer.MAX_VALUE);
}
/**
*
* @param owner
* @param maxPagesToFlush
* @return true if all the dirty pages where flushed.
*/
public boolean flushPages(final RandomAccessDataFile owner, final int maxPagesToFlush) {
boolean hasFlushes;
synchronized (lock) {
if (lastOwner == owner) {
scheduleFinalization(lastHit);
lastHit = null;
lastOwner = null;
}
hasFlushes = scanQueue(owner, myProtectedQueue);
hasFlushes |= scanQueue(owner, myProbationalQueue);
}
return !hasFlushes || flushFinalizationQueue(maxPagesToFlush);
}
private boolean flushFinalizationQueue(final int maxPagesToFlush) {
int count = 0;
while (count < maxPagesToFlush) {
FinalizationRequest request = retrieveFinalizationRequest();
if (request == null) {
return true;
}
processFinalizationRequest(request);
count++;
}
return false;
}
private boolean scanQueue(final RandomAccessDataFile owner, final Map<?, Page> queue) {
Iterator<Page> iterator = queue.values().iterator();
boolean hasFlushes = false;
while (iterator.hasNext()) {
Page page = iterator.next();
if (page.getOwner() == owner) {
scheduleFinalization(page);
iterator.remove();
hasFlushes = true;
}
}
return hasFlushes;
}
private boolean scheduleFinalization(final Page page) {
final int curFinalizationId;
synchronized (lock) {
curFinalizationId = ++finalizationId;
}
final FinalizationRequest request = page.prepareForFinalization(curFinalizationId);
if (request == null) return false;
synchronized (lock) {
/*
if (myFinalizerThread == null) {
myFinalizerThread = new Thread(new FinalizationThreadWorker(), FINALIZER_THREAD_NAME);
myFinalizerThread.start();
}
*/
myFinalizationQueue.put(keyForPage(page), request);
if (myFinalizationQueue.size() > 5000) {
return true;
}
}
synchronized (finalizationMonitor) {
finalizationMonitor.notifyAll();
}
return false;
}
private void processFinalizationRequest(final FinalizationRequest request) {
final Page page = request.page;
try {
page.flushIfFinalizationIdIsEqualTo(request.finalizationId);
}
finally {
synchronized (lock) {
myFinalizationQueue.remove(page.getKey());
}
page.recycleIfFinalizationIdIsEqualTo(request.finalizationId);
}
}
@Nullable
private FinalizationRequest retrieveFinalizationRequest() {
FinalizationRequest request = null;
synchronized (lock) {
if (!myFinalizationQueue.isEmpty()) {
final PoolPageKey key;
if (lastFinalizedKey == null) {
key = myFinalizationQueue.firstKey();
}
else {
PoolPageKey k = lastFinalizedKey;
PoolPageKey kk = new PoolPageKey(k.getOwner(), k.getOwner().physicalLength());
SortedMap<PoolPageKey, FinalizationRequest> tail = myFinalizationQueue.tailMap(kk);
if (tail == null || tail.isEmpty()) {
tail = myFinalizationQueue.tailMap(k);
}
key = tail.isEmpty() ? myFinalizationQueue.firstKey() : tail.firstKey();
}
lastFinalizedKey = key;
request = myFinalizationQueue.get(key);
}
else {
lastFinalizedKey = null;
}
}
return request;
}
}