/**
* Copyright 2005-2012 Akiban Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.persistit.stress.unit;
import java.util.Random;
import com.persistit.Accumulator.MaxAccumulator;
import com.persistit.Accumulator.MinAccumulator;
import com.persistit.Accumulator.SeqAccumulator;
import com.persistit.Accumulator.SumAccumulator;
import com.persistit.Configuration;
import com.persistit.Exchange;
import com.persistit.Persistit;
import com.persistit.Transaction;
import com.persistit.exception.PersistitException;
import com.persistit.util.ArgParser;
import com.persistit.util.Util;
/**
* Tests Accumulator values after restart. This test modifies accumulators and
* then restarts Persistit, both gracefully and after failure, to verify that
* the recovered Accumulator values are always correct. The test specifically
* attempts to test the juxtaposition of a Checkpoint and the final Accumulator
* update to look for bugs similar to 1064565.
*
* @author peter
*
*/
public class AccumulatorRestart extends StressBase {
final static Random RANDOM = new Random(1);
private final static String[] ARGS_TEMPLATE = { "repeat|int:1:0:1000000000|Repetitions" };
public final static long CHECKPOINT_INTERVAL = 30;
long _checkpointTimestamp = 0;
long _checkpointTime = 0;
public AccumulatorRestart(final String argsString) {
super(argsString);
}
@Override
public void setUp() throws Exception {
super.setUp();
_ap = new ArgParser("com.persistit.Stress1", _args, ARGS_TEMPLATE).strict();
_repeatTotal = _ap.getIntValue("repeat");
}
@Override
public void executeTest() {
/*
* Local copies, managed by this thread for comparison
*/
long minValue = 0;
long maxValue = 0;
long sumValue = 0;
long seqValue = 0;
long seqCommit = 0;
final Configuration config = getPersistit().getConfiguration();
try {
for (_count = 1;; _count++) {
final boolean a = RANDOM.nextInt(10) == 0;
System.out.println(_threadName + " starting cycle " + _count + " abort=" + a);
_ex = getPersistit().getExchange("persistit", _rootName + _threadIndex, true);
final SumAccumulator sum = _ex.getTree().getSumAccumulator(17);
final MaxAccumulator max = _ex.getTree().getMaxAccumulator(22);
final MinAccumulator min = _ex.getTree().getMinAccumulator(23);
final SeqAccumulator seq = _ex.getTree().getSeqAccumulator(47);
final Transaction txn = _ex.getTransaction();
if (_count > 1) {
txn.begin();
try {
final long minv = min.getSnapshotValue();
final long maxv = max.getSnapshotValue();
final long seqv = seq.getLiveValue();
final long sumv = sum.getSnapshotValue();
if (minv != minValue || maxv != maxValue || seqv != seqValue || sumv != sumValue) {
fail(String.format("Values don't match: (min/max/seq/sum) "
+ "expected=(%,d/%,d/%,d/%,d) actual=(%,d/%,d/%,d/%,d)", minValue, maxValue,
seqValue, sumValue, minv, maxv, seqv, sumv));
}
txn.commit();
} finally {
txn.end();
}
}
txn.begin();
try {
final long timeOffset = RANDOM.nextInt(1000) - 500;
while (!isTriggered(timeOffset)) {
final long r = RANDOM.nextInt(1000) - 500;
min.minimum(bsum(minValue, r));
max.maximum(bsum(maxValue, r));
seq.allocate();
sum.add(r);
seqValue++;
final long minWas = getLong(_ex.to("min"), Long.MAX_VALUE);
_ex.getValue().put(Math.min(bsum(minValue, r), minWas));
_ex.store();
final long maxWas = getLong(_ex.to("max"), Long.MIN_VALUE);
_ex.getValue().put(Math.max(bsum(maxValue, r), maxWas));
_ex.store();
final long seqWas = getLong(_ex.to("seq"), 0);
_ex.getValue().put(Math.max(seqValue, seqWas));
_ex.store();
final long sumWas = getLong(_ex.to("sum"), 0);
_ex.getValue().put(Math.min(sumValue + r, sumWas));
_ex.store();
if (!a) {
minValue = Math.min(bsum(minValue, r), minValue);
maxValue = Math.max(bsum(maxValue, r), maxValue);
sumValue = sumValue + r;
}
}
if (a) {
txn.rollback();
} else {
seqCommit = seqValue;
txn.commit();
}
} finally {
txn.end();
}
if (isStopped() || _count >= _repeatTotal) {
break;
}
if ((_count % 3) == 0) {
Persistit db = getPersistit();
db.close();
db = new Persistit();
db.initialize(config);
setPersistit(db);
seqValue = seqCommit;
}
}
} catch (final Exception ex) {
handleThrowable(ex);
} finally {
final Persistit db = getPersistit();
try {
db.close();
} catch (final PersistitException pe) {
handleThrowable(pe);
}
}
}
private boolean isTriggered(final long timeOffset) {
final long cp = getPersistit().getCurrentCheckpoint().getTimestamp();
final long now = System.nanoTime();
if (_checkpointTime == 0) {
if (cp > _checkpointTimestamp) {
_checkpointTime = now;
_checkpointTimestamp = cp;
}
}
if (_checkpointTime != 0) {
if (timeOffset >= 0 && now > _checkpointTime + timeOffset) {
_checkpointTime = 0;
return true;
} else if (timeOffset < 0 && now > _checkpointTime + timeOffset + CHECKPOINT_INTERVAL * Util.MS_PER_S) {
_checkpointTime = 0;
return true;
}
}
return false;
}
private long bsum(final long a, final long b) {
if (a < 0) {
return a + b > 0 ? a : a + b;
} else {
return a + b < 0 ? a : a + b;
}
}
private long getLong(final Exchange ex, final long dflt) throws PersistitException {
if (ex.getValue().isDefined()) {
return ex.getValue().getLong();
} else {
return dflt;
}
}
}