/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.github.geophile.erdo.consolidate;
import com.github.geophile.erdo.Configuration;
import com.github.geophile.erdo.map.MapCursor;
import com.github.geophile.erdo.map.diskmap.DiskMap;
import com.github.geophile.erdo.util.IdentitySet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.github.geophile.erdo.consolidate.Consolidation.Element;
/*
* A ConsolidationSet manages a set of Elements that are periodically consolidated.
* Consolidation selects a number of Elements, merges them to create a replacement, and then
* removes the obsolete, consolidated elements from the ConsolidationSet. From the outside,
* (to a user of the ConsolidationSet), the replacement is atomic. An Element
* manages resources, and since the switch from obsolete elements to the replacement is
* instantaneous, the set of resources being managed doesn't change.
*
* A ConsolidationSet is occasionally copied, to create a ConsolidationSetSnapshot. The copy
* contains a set of Elements at a point in time, and does not reflect later changes
* to the ConsolidationSet.
*
* An Element can only be destroyed when it is obsolete, (i.e., has been consolidated and replaced),
* and it is unused (i.e., not present in any ConsolidationSetSnapshot). If an Element is unused when
* it becomes obsolete, it is immediately destroyed. If it is in use (present in a ConsolidationSetSnapshot)
* when it becomes obsolete, then it is kept around and destroyed when the usage count drops to zero.
*
* Elements are managed in two ConsolidationElementTrackers:
* - nonDurable: Elements whose state exists only in memory.
* - durable: Elements whose state exists on disk.
* ConsolidationElementTracker notes which elements are currently being consolidated.
*
* ConsolidationSet state is operated on under synchronization. The code synchronizes on container, not this
* because the state managed by the container resides both in the container's ConsolidationSet (this) and
* possibly in the container. Synchronizing on this misses the container's state. Synchronization protects access
* to a ConsolidationSet and its container. Actual consolidation, and destruction of elements are expensive, and
* done outside of synchronized blocks.
*/
public class ConsolidationSet
{
// ConsolidationSet interface
public Consolidation.Container container()
{
return container;
}
public void add(Element element, boolean makeDurableImmediately)
throws InterruptedException, IOException
{
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE,
"Adding {0} to consolidation set, inProgress: {1}, nonDurable records: {2}, complexity: {3}",
new Object[]{element, inProgress, nonDurable.totalRecords(), nonDurable.complexity()});
}
synchronized (container) {
long start = -1L;
while (maxPendingBytes > 0 &&
nonDurable.totalBytes() >= maxPendingBytes) {
start = System.currentTimeMillis();
container.wait();
}
if (LOG.isLoggable(Level.INFO) && start != -1L) {
long stop = System.currentTimeMillis();
LOG.log(Level.INFO,
"Waited {0} msec to add {1}",
new Object[]{(stop - start), element});
}
nonDurable.add(element);
}
if (makeDurableImmediately) {
makeDurableImmediatelyConsolidator.consolidate(element);
} else {
inMemoryConsolidator.consolidate(null);
}
if (element.count() > 0) {
updateLastActivityTime();
}
}
public ConsolidationSetSnapshot snapshot()
{
synchronized (container) {
List<Element> snapshot = new ArrayList<>(nonDurable.elements());
snapshot.addAll(durable.elements());
for (Element element : snapshot) {
usageCounts.register(element.id());
}
return new ConsolidationSetSnapshot(this, snapshot);
}
}
public void consolidateAll() throws IOException, InterruptedException
{
allDurableConsolidator.consolidate(null);
}
public void consolidateOnRecovery()
{
List<Element> consolidateOnRecovery;
synchronized (container) {
long keysInMemoryMapLimit = container.configuration().keysInMemoryMapLimit();
consolidateOnRecovery = new ArrayList<>();
for (Element element : durable.availableForConsolidation()) {
if (element.count() <= keysInMemoryMapLimit) {
consolidateOnRecovery.add(element);
}
}
}
if (consolidateOnRecovery.size() > 1) {
Consolidator recoveryConsolidator =
ImmediateConsolidator.newConsolidator
(this, RecoveryConsolidationPlanner.newPlanner(this, consolidateOnRecovery));
try {
recoveryConsolidator.consolidate(null);
} catch (IOException | InterruptedException e) {
assert false;
}
// Read the new map (highest-numbered) into memory
DiskMap newMap = null;
synchronized (container) {
List<Element> availableForConsolidation = durable.availableForConsolidation();
assert !availableForConsolidation.isEmpty();
for (Element element : availableForConsolidation) {
DiskMap map = (DiskMap) element;
if (newMap == null || map.id() > newMap.id()) {
newMap = map;
}
}
}
try {
assert newMap != null;
MapCursor mapScan = newMap.cursor(null, false);
while (mapScan.next() == null);
} catch (IOException | InterruptedException e) {
assert false : e;
}
}
}
public void flush() throws IOException, InterruptedException
{
flushConsolidator.consolidate(null);
}
public void shutdown() throws InterruptedException, IOException
{
synchronized (shutdown) {
if (!shutdown.get()) {
makeDurableImmediatelyConsolidator.stopThreads();
firstDurableConsolidator.stopThreads();
laterDurableConsolidator.stopThreads();
inMemoryConsolidator.stopThreads();
LOG.log(Level.INFO, "Running final consolidation to make last commits durable.");
flushConsolidator.consolidate(null);
flushConsolidator.stopThreads();
treeDeleter.delete(new ArrayList<>(destroyWhenUnused.keySet())); // No elements are in use at shutdown.
treeDeleter.shutdown();
}
shutdown.set(true);
}
}
public Configuration configuration()
{
return container.configuration();
}
public void describe(Logger log, Level level, String label)
{
synchronized (container) {
durable.describe(log, level, label);
nonDurable.describe(log, level, label);
}
}
public static ConsolidationSet newConsolidationSet(Consolidation.Container owner, List<? extends Element> elements)
{
ConsolidationSet consolidationSet = new ConsolidationSet(owner, elements);
consolidationSet.createSynchronousConsolidators();
consolidationSet.consolidateOnRecovery();
consolidationSet.createAsynchronousConsolidators();
return consolidationSet;
}
// For use by this package
ConsolidationElementTracker durable()
{
return durable;
}
ConsolidationElementTracker nonDurable()
{
return nonDurable;
}
void consolidationStarting()
{
synchronized (container) {
inProgress++;
}
}
void consolidationFailed(List<Element> elements, boolean inputDurable)
{
synchronized (container) {
if (inputDurable) {
durable.consolidationFailed(elements);
} else {
nonDurable.consolidationFailed(elements);
}
}
updateLastActivityTime();
}
void consolidationEnding()
{
synchronized (container) {
inProgress--;
assert inProgress >= 0 : inProgress;
container.notifyAll();
}
updateLastActivityTime();
}
void unregister(List<Element> elements)
{
List<Element> destroy = new ArrayList<>();
synchronized (container) {
for (Element element : elements) {
long id = element.id();
boolean unused = usageCounts.unregister(id);
if (unused && destroyWhenUnused.contains(element)) {
destroy.add(element);
destroyWhenUnused.remove(element);
}
}
}
treeDeleter.delete(destroy);
}
List<Element> replaceObsolete(ConsolidationTask consolidationTask,
List<Element> obsolete,
Element replacement)
{
assert Thread.holdsLock(container);
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "{0} obsoletes {1}", new Object[]{replacement, obsolete});
}
List<Element> destroy = new ArrayList<>();
ConsolidationElementTracker obsoleteElementTracker =
consolidationTask.inputDurable() ? durable : nonDurable;
for (Element element : obsolete) {
obsoleteElementTracker.removeElementBeingConsolidated(element);
if (usageCounts.unused(element.id())) {
// Not used in any snapshot
LOG.log(Level.FINE, "Destroy {0} now", element);
destroy.add(element);
} else {
LOG.log(Level.FINE, "Destroy {0} when unused", element);
destroyWhenUnused.add(element);
}
}
if (replacement.durable()) {
durable.add(replacement);
laterDurableConsolidator.noteNewElement();
} else {
nonDurable.add(replacement);
inMemoryConsolidator.noteNewElement();
}
return destroy;
}
void markForConsolidation(ConsolidationTask consolidationTask, List<Element> toConsolidate)
{
assert Thread.holdsLock(container);
ConsolidationElementTracker elementTracker =
consolidationTask.inputDurable() ? durable : nonDurable;
elementTracker.beingConsolidated(toConsolidate);
}
boolean idle()
{
return
inProgress == 0 &&
System.currentTimeMillis() - lastActivityTime.get() >= idleTimeMsec;
}
void deleteElements(List<Element> elements)
{
treeDeleter.delete(elements);
}
// For use by this class
private void updateLastActivityTime()
{
lastActivityTime.set(System.currentTimeMillis());
}
private ConsolidationSet(Consolidation.Container container, List<? extends Element> elements)
{
// A consolidation set is created on recovery, so all of the elements must be durable.
nonDurable = ConsolidationElementTracker.nonDurable(container);
durable = ConsolidationElementTracker.durable(container);
this.container = container;
// Synchronization is not really necessary. But durable.add asserts synchronization on the container.
synchronized (this.container) {
for (Element element : elements) {
assert element.durable();
durable.add(element);
}
}
this.maxPendingBytes = configuration().consolidationMaxPendingCommittedSizeBytes();
this.idleTimeMsec = configuration().consolidationIdleTimeSec() * 1000;
this.treeDeleter = TreeDeleter.create();
}
private void createSynchronousConsolidators()
{
int minConsolidationMaps = configuration().consolidationMinMapsToConsolidate();
this.makeDurableImmediatelyConsolidator =
ImmediateConsolidator.newConsolidator
(this, ImmediateConsolidationPlanner.newPlanner(this));
this.inMemoryConsolidator =
ImmediateConsolidator.newConsolidator
(this, FractalConsolidationPlanner.nonDurable(this,
minConsolidationMaps,
0));
this.flushConsolidator =
ImmediateConsolidator.newConsolidator
(this, AllNonDurableConsolidationPlanner.newPlanner(this));
this.allDurableConsolidator =
ImmediateConsolidator.newConsolidator
(this, AllDurableConsolidationPlanner.newPlanner(this));
}
private void createAsynchronousConsolidators()
{
int minConsolidationSize = configuration().consolidationMinSizeBytes();
int minConsolidationMaps = configuration().consolidationMinMapsToConsolidate();
this.firstDurableConsolidator =
DurableConsolidator.runPeriodically
(this, MakeDurableConsolidationPlanner.newPlanner(this));
this.laterDurableConsolidator =
DurableConsolidator.usingThreadPool
(this, FractalConsolidationPlanner.durable(this,
minConsolidationMaps,
minConsolidationSize));
this.idleConsolidator =
DurableConsolidator.runPeriodically
(this, IdleConsolidationPlanner.newPlanner(this,
minConsolidationMaps,
minConsolidationSize));
}
// Class state
private static final Logger LOG = Logger.getLogger(ConsolidationSet.class.getName());
// Object state
private final Consolidation.Container container;
private final ConsolidationElementTracker nonDurable;
private final ConsolidationElementTracker durable;
private final IdentitySet<Element> destroyWhenUnused = new IdentitySet<>();
private final long maxPendingBytes;
private final AtomicBoolean shutdown = new AtomicBoolean(false);
private final TreeDeleter treeDeleter;
private final int idleTimeMsec;
private volatile int inProgress = 0;
private volatile AtomicLong lastActivityTime = new AtomicLong(0);
// Tracks usage of elements by snapshots
private final UsageCounts usageCounts = new UsageCounts();
private Consolidator makeDurableImmediatelyConsolidator;
private Consolidator inMemoryConsolidator;
// Consolidate everything
private Consolidator allDurableConsolidator;
// Consolidate in response to Database.flush()
private Consolidator flushConsolidator;
// Makes non-durable elements durable
private Consolidator firstDurableConsolidator;
// Consolidates durable elements, triggered by end of a consolidation
private Consolidator laterDurableConsolidator;
// Consolidates when there aren't any being triggered by updates
private Consolidator idleConsolidator;
}