/** * Allocates the requested space in the file. * * @param len requested space * @return allocated file position and length as FileEntry object */ private FileEntry allocate(int len) { synchronized (freeList) { // lookup a free entry of sufficient size SortedSet<FileEntry> candidates = freeList.tailSet(new FileEntry(0, len)); for (Iterator<FileEntry> it = candidates.iterator(); it.hasNext(); ) { FileEntry free = it.next(); // ignore entries that are still in use by concurrent readers if (free.isLocked()) continue; // There's no race condition risk between locking the entry on // loading and checking whether it's locked (or store allocation), // because for the entry to be lockable, it needs to be in the // entries collection, in which case it's not in the free list. // The only way an entry can be found in the free list is if it's // been removed, and to remove it, lock on "entries" needs to be // acquired, which is also a pre-requisite for loading data. // found one, remove from freeList it.remove(); return allocateExistingEntry(free, len); } // no appropriate free section available, append at end of file FileEntry fe = new FileEntry(filePos, len); filePos += len; if (trace) log.tracef( "New entry allocated at %d:%d, %d free entries, file size is %d", fe.offset, fe.size, freeList.size(), filePos); return fe; } }
/** * Coalesces adjacent free entries to create larger free entries (so that the probability of * finding a free entry during allocation increases) */ private void mergeFreeEntries(List<FileEntry> entries) { long startTime = 0; if (trace) startTime = timeService.wallClockTime(); FileEntry lastEntry = null; FileEntry newEntry = null; int mergeCounter = 0; for (Iterator<FileEntry> it = entries.iterator(); it.hasNext(); ) { FileEntry fe = it.next(); if (fe.isLocked()) { continue; } // Merge any holes created (consecutive free entries) in the file if ((lastEntry != null) && (lastEntry.offset == (fe.offset + fe.size))) { if (newEntry == null) { newEntry = new FileEntry(fe.offset, fe.size + lastEntry.size); freeList.remove(lastEntry); mergeCounter++; } else { newEntry = new FileEntry(fe.offset, fe.size + newEntry.size); } freeList.remove(fe); mergeCounter++; } else { if (newEntry != null) { try { addNewFreeEntry(newEntry); if (trace) log.tracef( "Merged %d entries at %d:%d, %d free entries", mergeCounter, newEntry.offset, newEntry.size, freeList.size()); } catch (IOException e) { throw new PersistenceException("Could not add new merged entry", e); } newEntry = null; mergeCounter = 0; } } lastEntry = fe; } if (newEntry != null) { try { addNewFreeEntry(newEntry); if (trace) log.tracef( "Merged %d entries at %d:%d, %d free entries", mergeCounter, newEntry.offset, newEntry.size, freeList.size()); } catch (IOException e) { throw new PersistenceException("Could not add new merged entry", e); } } if (trace) log.tracef( "Total time taken for mergeFreeEntries: " + (timeService.wallClockTime() - startTime) + " (ms)"); }
/** Removes free entries towards the end of the file and truncates the file. */ private void truncateFile(List<FileEntry> entries) { long startTime = 0; if (trace) startTime = timeService.wallClockTime(); int reclaimedSpace = 0; int removedEntries = 0; long truncateOffset = -1; for (Iterator<FileEntry> it = entries.iterator(); it.hasNext(); ) { FileEntry fe = it.next(); // Till we have free entries at the end of the file, // we can remove them and contract the file to release disk // space. if (!fe.isLocked() && ((fe.offset + fe.size) == filePos)) { truncateOffset = fe.offset; filePos = fe.offset; freeList.remove(fe); it.remove(); reclaimedSpace += fe.size; removedEntries++; } else { break; } } if (truncateOffset > 0) { try { channel.truncate(truncateOffset); } catch (IOException e) { throw new PersistenceException("Error while truncating file", e); } } if (trace) { log.tracef("Removed entries: " + removedEntries + ", Reclaimed Space: " + reclaimedSpace); log.tracef( "Time taken for truncateFile: " + (timeService.wallClockTime() - startTime) + " (ms)"); } }