/*
* 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 java.io.IOException;
import java.nio.channels.ClosedByInterruptException;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
class ConsolidationTask implements Runnable
{
// Object interface
@Override
public String toString()
{
return String.format("ConsolidationTask#%s(%s)", taskId, planner);
}
// Runnable interface
public void run()
{
consolidator.noteConsolidationStart();
try {
consolidateIfNecessary();
} catch (InterruptedException e) {
LOG.log(Level.WARNING, "{0} terminated by interruption", this);
} catch (IOException e) {
termination = e;
LOG.log(Level.SEVERE, "{0} terminated by IOException", this);
LOG.log(Level.SEVERE, "stack", e);
} catch (Throwable e) {
termination = e;
LOG.log(Level.SEVERE, "Something has gone very wrong", e);
} finally {
consolidator.noteConsolidationEnd(termination);
}
}
public boolean inputDurable()
{
return planner.inputDurable();
}
public boolean outputDurable()
{
return planner.outputDurable();
}
// ConsolidationTask interface
public ConsolidationTask(Consolidator consolidator, Consolidation.Element newElement)
{
this.consolidator = consolidator;
this.planner = consolidator.planner();
this.container = planner.consolidationSet().container();
this.newElement = newElement;
}
// For use by this class
// package private so that tests can get to it
/* private */ void consolidateIfNecessary() throws InterruptedException, IOException
{
ConsolidationSet consolidationSet = planner.consolidationSet();
boolean needToConsolidate;
synchronized (container) {
needToConsolidate = planner.planConsolidation(newElement);
if (needToConsolidate) {
consolidationSet.markForConsolidation(this, planner.elementsToConsolidate());
}
}
if (needToConsolidate) {
consolidate();
}
}
private void consolidate() throws InterruptedException
{
ConsolidationSet consolidationSet = planner.consolidationSet();
consolidationSet.consolidationStarting();
Consolidation.Element replacement = null;
List<Consolidation.Element> obsolete = planner.elementsToConsolidate();
try {
List<Consolidation.Element> destroy;
long start = System.currentTimeMillis();
try {
replacement = container.consolidate(obsolete,
planner.inputDurable(),
planner.outputDurable());
} catch (ClosedByInterruptException | InterruptedException e) {
// cleanup is below
}
if (replacement == null) {
// Interrupted, either by InterruptedException, or ClosedByInterruptException.
// Clean up the ConsolidationSet
consolidationSet.consolidationFailed(obsolete, planner.inputDurable());
LOG.log(Level.WARNING, "{0} failed consolidation complete", this);
} else {
long stop = System.currentTimeMillis();
logConsolidation(obsolete, replacement, stop - start);
synchronized (container) {
destroy = consolidationSet.replaceObsolete(this, obsolete, replacement);
// TODO: Move this call, and synchronization, inside ConsolidationSet
container.replaceObsolete(obsolete, replacement);
}
// Transactions in obsolete are now durable and public.
// Destroy elements that were unused when they became obsolete.
consolidationSet.deleteElements(destroy);
LOG.log(Level.FINE, "{0} consolidation complete", this);
}
} catch (IOException e) {
// But this is not normal, even on shutdown
LOG.log(Level.WARNING,
"{0} failed. replacement: {1}, obsolete: {2}",
new Object[]{this, replacement, obsolete});
LOG.log(Level.SEVERE, e.toString(), e);
} catch (Throwable e) {
LOG.log(Level.SEVERE, "Well this is unexpected", e);
throw new Error(e);
} finally {
consolidationSet.consolidationEnding();
}
}
private void logConsolidation(List<Consolidation.Element> obsolete, Consolidation.Element replacement, long timeMsec)
{
if (LOG.isLoggable(Level.INFO)) {
SortedMap<String, Integer> sizeDistribution = new TreeMap<>();
for (Consolidation.Element element : obsolete) {
String classname = element.getClass().getSimpleName();
long size = element.count();
String key = String.format("%s:%s", classname, size);
Integer count = sizeDistribution.get(key);
if (count == null) {
count = 0;
}
sizeDistribution.put(key, count + 1);
}
StringBuilder obsoleteSizes = new StringBuilder();
for (Map.Entry<String, Integer> entry : sizeDistribution.entrySet()) {
if (obsoleteSizes.length() > 0) {
obsoleteSizes.append(", ");
}
obsoleteSizes.append(String.format("%s x %s", entry.getValue(), entry.getKey()));
}
LOG.log(Level.INFO,
"{0} {1} replaced by {2}, consolidation took {3} msec",
new Object[]{this, obsoleteSizes, replacement, timeMsec});
}
}
// Class state
private static final Logger LOG = Logger.getLogger(ConsolidationTask.class.getName());
private static final AtomicLong taskIdCounter = new AtomicLong(0);
// Object state
private final Consolidator consolidator;
private final ConsolidationPlanner planner;
private final long taskId = taskIdCounter.getAndIncrement();
private final Consolidation.Container container;
private final Consolidation.Element newElement;
private Throwable termination;
}