package com.limegroup.gnutella.downloader;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.limewire.collection.ByteArrayCache;
import org.limewire.collection.PowerOf2ByteArrayCache;
import org.limewire.concurrent.ExecutorsHelper;
import org.limewire.concurrent.ManagedThread;
import org.limewire.inject.EagerSingleton;
import org.limewire.lifecycle.ServiceScheduler;
import com.google.inject.Inject;
import com.google.inject.name.Named;
/** Manages writing / reading from / to disk. */
@EagerSingleton
public class DiskController {
private static final Log LOG = LogFactory.getLog(DiskController.class);
/** The thread that does the actual verification & writing */
private final ThreadPoolExecutor QUEUE = ExecutorsHelper.newSingleThreadExecutor(
new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = new ManagedThread(r, "BlockingVF");
t.setDaemon(true);
t.setPriority(Thread.NORM_PRIORITY+1);
return t;
}
});
/**
* A list of DelayedWrites that will write when space becomes available in the cache.
* LOCKING: Lock on the below CACHE.
*/
private final List<DelayedWrite> DELAYED = new LinkedList<DelayedWrite>();
/** A cache for byte[]s. */
private final ByteArrayCache CACHE = new ByteArrayCache(512, HTTPDownloader.BUF_LENGTH);
/** a bunch of cached byte[]s for verifiable chunks */
private final PowerOf2ByteArrayCache CHUNK_CACHE = new PowerOf2ByteArrayCache();
/** The number of chunks scheduled to be written. */
private int chunksScheduled = 0;
/** A lock to use for the queue size + chunksScheduled. */
private final Object SCHEDULE_LOCK = new Object();
@Inject
public DiskController() {
}
@Inject
public void register(ServiceScheduler serviceScheduler, @Named("backgroundExecutor") ScheduledExecutorService backgroundExecutor) {
serviceScheduler.scheduleWithFixedDelay("DiskContrller.CacheCleaner", new CacheCleaner(), 10, 10, TimeUnit.MINUTES, backgroundExecutor);
}
/** Adds a DelayedWrite to the queue of writers. */
public void addDelayedWrite(DelayedWrite dw) {
synchronized(CACHE) {
DELAYED.add(dw);
}
}
/** Returns true if no delayed writes are pending. */
public boolean canWriteNow() {
synchronized(CACHE) {
return DELAYED.isEmpty();
}
}
/** Returns a chunk for writing. Will return false if no chunks are available for writing. */
public byte[] getWriteChunk() {
return CACHE.getQuick();
}
/** Adds a job to be performed on the disk. */
public void addDiskJob(final ChunkDiskJob job) {
synchronized(SCHEDULE_LOCK) {
chunksScheduled++;
QUEUE.execute(new Runnable() {
public void run() {
try {
job.runChunkJob(job.getChunk());
} finally {
synchronized(SCHEDULE_LOCK) {
chunksScheduled--;
}
releaseChunk(job.getChunk(), true);
job.finish();
}
}
});
}
}
/** Adds a job to be performed on the disk that doesn't involve chunks. */
public void addDiskJobWithoutChunk(Runnable job) {
QUEUE.execute(job);
}
/** Gets a byte[] to the closest power of 2. */
public byte[] getPowerOf2Chunk(int size) {
return CHUNK_CACHE.get(size);
}
/**
* A Runnable that clears the cache used for storing byte[]s used for
* writing data read from network to disk, and schedules a ChunkCacheCleaner.
*/
private class CacheCleaner implements Runnable {
public void run() {
LOG.info("clearing cache");
CACHE.clear();
QUEUE.execute(new ChunkCacheCleaner());
}
}
/** A Runnable that clears the cache storing byte[]s used for verifying. */
private class ChunkCacheCleaner implements Runnable {
public void run() {
CHUNK_CACHE.clear();
}
}
private void releaseChunk(byte[] buf, boolean runDelayed) {
CACHE.release(buf);
if(runDelayed)
runDelayedWrites();
}
private void runDelayedWrites() {
synchronized(SCHEDULE_LOCK) {
if(chunksScheduled > 0)
return;
}
while(CACHE.isBufferAvailable()) {
DelayedWrite dw;
synchronized(CACHE) {
if(DELAYED.isEmpty()) {
LOG.debug("Nothing delayed to run.");
return;
}
dw = DELAYED.get(0);
}
// write & notify outside of lock
if(dw.write()) {
// if we wrote succesfully, remove the item from the cache.
synchronized(CACHE) {
DELAYED.remove(0);
}
} else {
// otherwise, something went wrong, so reschedule another
// delayed write later on.
// NOTE: this should be impossible to happen, but it's happening,
// and its no huge deal, so we're preparing for it.
QUEUE.execute(new Runnable() {
public void run() {
runDelayedWrites();
}
});
}
}
}
/** Cleans the caches. */
public void clearCaches() {
Runnable runner = new CacheCleaner();
runner.run();
}
/** Returns the number of bytes cached in the byte cache. */
public int getSizeOfByteCache() {
return CACHE.getCacheSize();
}
/** Returns the number of bytes cached in the verifying cache. */
public int getSizeOfVerifyingCache() {
return CHUNK_CACHE.getCacheSize();
}
public int getNumPendingItems() {
return QUEUE.getQueue().size();
}
}