/**
* diqube: Distributed Query Base.
*
* Copyright (C) 2015 Bastian Gloeckle
*
* This file is part of diqube.
*
* diqube is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diqube.consensus;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;
import javax.annotation.PostConstruct;
import org.apache.thrift.TBase;
import org.diqube.config.Config;
import org.diqube.config.DerivedConfigKey;
import org.diqube.context.AutoInstatiate;
import org.diqube.file.internaldb.InternalDbFileReader;
import org.diqube.file.internaldb.InternalDbFileReader.ReadException;
import org.diqube.file.internaldb.InternalDbFileWriter;
import org.diqube.file.internaldb.InternalDbFileWriter.WriteException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.atomix.copycat.server.Commit;
/**
* Abstract base class for consensus state machine implementations that have a
* {@link ConsensusStateMachineImplementation} annotation and want to provide storing any state on disk using
* "internalDb" files.
*
* <p>
* Note that the subclassing class should be {@link AutoInstatiate}d, which is the case for all
* {@link ConsensusStateMachineImplementation}s.
*
* @param <T>
* Thrift type used to serialize data to internalDb files.
* @author Bastian Gloeckle
*/
public abstract class AbstractConsensusStateMachine<T extends TBase<?, ?>> {
private static final Logger logger = LoggerFactory.getLogger(AbstractConsensusStateMachine.class);
private String internalDbFilePrefix;
private String internalDbDataType;
private Supplier<T> factory;
@Config(DerivedConfigKey.FINAL_INTERNAL_DB_DIR)
private String internalDbDir;
private InternalDbFileWriter<T> internalDbFileWriter;
public AbstractConsensusStateMachine(String internalDbFilePrefix, String internalDbDataType, Supplier<T> factory) {
this.internalDbFilePrefix = internalDbFilePrefix;
this.internalDbDataType = internalDbDataType;
this.factory = factory;
}
@PostConstruct
public void initialize() {
File internalDbDirFile = new File(internalDbDir);
if (!internalDbDirFile.exists())
if (!internalDbDirFile.mkdirs())
throw new RuntimeException("Could not create directory " + internalDbDir);
try {
InternalDbFileReader<T> internalDbFileReader =
new InternalDbFileReader<>(internalDbDataType, internalDbFilePrefix, internalDbDirFile, factory);
List<T> entries = internalDbFileReader.readNewest();
if (entries == null)
logger.info("No internaldb for {} available", internalDbDataType);
doInitialize(entries);
} catch (ReadException e) {
throw new RuntimeException("Could not load " + internalDbDataType + " file", e);
}
internalDbFileWriter = new InternalDbFileWriter<>(internalDbDataType, internalDbFilePrefix, internalDbDirFile);
}
/**
* Load the initial state of the state machine which was loaded from internalDb.
*
* @param entriesLoadedFromInternalDb
* The entries that were stored in internalDb. May be <code>null</code> if no data is available.
*/
protected abstract void doInitialize(List<T> entriesLoadedFromInternalDb);
/**
* Writes current state of the state machine to internalDb.
*
* @param consensusIndex
* The {@link Commit#index()} of the commit that was commited last.
* @param entries
* The entries to store (= the state of the state machine).
*/
protected void writeCurrentStateToInternalDb(long consensusIndex, Collection<T> entries) {
List<T> entryList;
if (entries instanceof List)
entryList = (List<T>) entries;
else
entryList = new ArrayList<>(entries);
try {
internalDbFileWriter.write(consensusIndex, entryList);
} catch (WriteException e1) {
logger.error("Could not write {} internaldb file!", internalDbDataType, e1);
// this is an error, but we try to continue anyway. When the file is missing, the node might not be able to
// recover correctly, but for now we can keep working. The admin might want to copy a internaldb file from a
// different node.
}
}
}