package com.sleepycat.je.cleaner;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.dbi.DatabaseId;
import com.sleepycat.je.tree.LN;
import de.ovgu.cide.jakutil.*;
/**
* Keeps track of the status of files for which cleaning is in progres.
*/
class FileSelector {
private SortedSet toBeCleanedFiles;
private Set beingCleanedFiles;
private Set cleanedFiles;
private Set checkpointedFiles;
private Set fullyProcessedFiles;
private Set safeToDeleteFiles;
private Map pendingLNs;
private boolean anyPendingDuringCheckpoint;
private Set lowUtilizationFiles;
FileSelector(){
toBeCleanedFiles=new TreeSet();
cleanedFiles=new HashSet();
checkpointedFiles=new HashSet();
fullyProcessedFiles=new HashSet();
safeToDeleteFiles=new HashSet();
pendingLNs=new HashMap();
this.hook163();
lowUtilizationFiles=Collections.EMPTY_SET;
beingCleanedFiles=new HashSet();
}
/**
* Returns the best file that qualifies for cleaning, or null if no file
* qualifies. This method is not thread safe and should only be called
* from the cleaner thread.
* @param forceCleaning is true to always select a file, even if its
* utilization is above the minimum utilization threshold.
* @param calcLowUtilizationFiles whether to recalculate the set of files
* that are below the minimum utilization threshold.
* @param maxBatchFiles is the maximum number of files to be selected at
* one time, or zero if there is no limit.
* @return the next file to be cleaned, or null if no file needs cleaning.
*/
Long selectFileForCleaning( UtilizationProfile profile, boolean forceCleaning, boolean calcLowUtilizationFiles, int maxBatchFiles) throws DatabaseException {
Set newLowUtilizationFiles=calcLowUtilizationFiles ? (new HashSet()) : null;
while (true) {
if (maxBatchFiles > 0) {
synchronized (this) {
if (toBeCleanedFiles.size() >= maxBatchFiles) {
break;
}
}
}
Long fileNum=profile.getBestFileForCleaning(this,forceCleaning,newLowUtilizationFiles);
if (fileNum == null) {
break;
}
synchronized (this) {
toBeCleanedFiles.add(fileNum);
}
}
if (newLowUtilizationFiles != null) {
lowUtilizationFiles=newLowUtilizationFiles;
}
SortedSet availableFiles;
synchronized (this) {
availableFiles=new TreeSet(toBeCleanedFiles);
}
Long file=profile.getCheapestFileToClean(availableFiles);
synchronized (this) {
toBeCleanedFiles.remove(file);
beingCleanedFiles.add(file);
}
return file;
}
/**
* Returns whether the file is in any stage of the cleaning process.
*/
synchronized boolean isFileCleaningInProgress( Long file){
return toBeCleanedFiles.contains(file) || beingCleanedFiles.contains(file) || cleanedFiles.contains(file)|| checkpointedFiles.contains(file)|| fullyProcessedFiles.contains(file)|| safeToDeleteFiles.contains(file);
}
/**
* When file cleaning is aborted, move the file back from the being-cleaned
* set to the to-be-cleaned set.
*/
synchronized void putBackFileForCleaning( Long fileNum){
toBeCleanedFiles.add(fileNum);
beingCleanedFiles.remove(fileNum);
}
/**
* When cleaning is complete, move the file from the being-cleaned set to
* the cleaned set.
*/
synchronized void addCleanedFile( Long fileNum){
cleanedFiles.add(fileNum);
beingCleanedFiles.remove(fileNum);
}
/**
* Returns a read-only set of low utilization files that can be accessed
* without synchronization.
*/
Set getLowUtilizationFiles(){
return lowUtilizationFiles;
}
/**
* Returns a read-only copy of to-be-cleaned and being-cleaned files that
* can be accessed without synchronization.
*/
synchronized Set getMustBeCleanedFiles(){
Set set=new HashSet(toBeCleanedFiles);
set.addAll(beingCleanedFiles);
return set;
}
/**
* Returns the number of files waiting to-be-cleaned.
*/
synchronized int getBacklog(){
return toBeCleanedFiles.size();
}
/**
* Returns a copy of the cleaned and fully-processed files at the time a
* checkpoint starts.
*/
synchronized Set[] getFilesAtCheckpointStart(){
anyPendingDuringCheckpoint=!pendingLNs.isEmpty();
this.hook164();
Set[] files=new Set[2];
files[0]=(cleanedFiles.size() > 0) ? (new HashSet(cleanedFiles)) : null;
files[1]=(fullyProcessedFiles.size() > 0) ? (new HashSet(fullyProcessedFiles)) : null;
return (files[0] != null || files[1] != null) ? files : null;
}
/**
* When a checkpoint is complete, moves the previously cleaned and
* fully-processed files to the checkpointed and safe-to-delete sets.
*/
synchronized void updateFilesAtCheckpointEnd( Set[] files){
if (files != null) {
Set previouslyCleanedFiles=files[0];
if (previouslyCleanedFiles != null) {
if (anyPendingDuringCheckpoint) {
checkpointedFiles.addAll(previouslyCleanedFiles);
}
else {
safeToDeleteFiles.addAll(previouslyCleanedFiles);
}
cleanedFiles.removeAll(previouslyCleanedFiles);
}
Set previouslyProcessedFiles=files[1];
if (previouslyProcessedFiles != null) {
safeToDeleteFiles.addAll(previouslyProcessedFiles);
fullyProcessedFiles.removeAll(previouslyProcessedFiles);
}
updateProcessedFiles();
}
}
/**
* Adds the given LN info to the pending LN set.
*/
synchronized boolean addPendingLN( LN ln, DatabaseId dbId, byte[] key, byte[] dupKey){
assert ln != null;
boolean added=pendingLNs.put(new Long(ln.getNodeId()),new LNInfo(ln,dbId,key,dupKey)) != null;
anyPendingDuringCheckpoint=true;
return added;
}
/**
* Returns an array of LNInfo for LNs that could not be migrated in a
* prior cleaning attempt, or null if no LNs are pending.
*/
synchronized LNInfo[] getPendingLNs(){
if (pendingLNs.size() > 0) {
LNInfo[] lns=new LNInfo[pendingLNs.size()];
pendingLNs.values().toArray(lns);
return lns;
}
else {
return null;
}
}
/**
* Removes the LN for the given node ID from the pending LN set.
*/
synchronized void removePendingLN( long nodeId){
pendingLNs.remove(new Long(nodeId));
updateProcessedFiles();
}
/**
* Returns a copy of the safe-to-delete files.
*/
synchronized Set copySafeToDeleteFiles(){
if (safeToDeleteFiles.size() == 0) {
return null;
}
else {
return new HashSet(safeToDeleteFiles);
}
}
/**
* Removes file from the safe-to-delete set after the file itself has
* finally been deleted.
*/
synchronized void removeDeletedFile( Long fileNum){
safeToDeleteFiles.remove(fileNum);
}
/**
* If there are no pending LNs or DBs outstanding, move the checkpointed
* files to the fully-processed set. The check for pending LNs/DBs and the
* copying of the checkpointed files must be done atomically in a
* synchronized block. All methods that call this method are synchronized.
*/
private void updateProcessedFiles(){
boolean b=pendingLNs.isEmpty();
b=this.hook165(b);
if (b) {
fullyProcessedFiles.addAll(checkpointedFiles);
checkpointedFiles.clear();
}
}
protected void hook163(){
}
protected void hook164(){
}
protected boolean hook165( boolean b){
return b;
}
}