Crash Recovery Schemes
I assume that a basic crash recovery system looks something
like this. It works superficially, although I have not gone crazy
with a debugger. Is there anything obviously stupid about
this scheme, or is it more or less usable?
import java.io.*;
public class CrashRecovery
implements ITicking, Serializable
{
// User Constant
private static final String DEFAULT_FILE_NAME = "recovery.ser";
private static final String DEFAULT_MIRROR_NAME =
"recovery_mirror.ser";
private static final int DEFAULT_TIMEOUT = 60 * 60 * 5; //
60fps * 60s/m * 5m
// System Constant
private static final int DEFAULT_TICK = 1;
private static final int ONE_SECOND = 1000; // milliseconds
// State
private Serializable mHost;
private String mFileName;
private String mMirrorName;
private int mTimer;
private int mTimeout;
private int mVersion;
public CrashRecovery()
{
init(null, DEFAULT_FILE_NAME, DEFAULT_MIRROR_NAME);
}
public CrashRecovery(Serializable pHost, String pFileName, String
pMirrorName)
{
init(pHost, pFileName, pMirrorName);
}
public CrashRecovery init(Serializable pHost, String pFileName,
String pMirrorName)
{
setHost(pHost);
setFile(pFileName, pMirrorName);
resetTimer();
resetVersion();
return this;
}
public CrashRecovery setHost(Serializable pHost)
{
mHost = pHost;
return this;
}
public CrashRecovery setFile(String pFileName, String pMirrorName)
{
mFileName = pFileName;
mMirrorName = pMirrorName;
return this;
}
// tick every frame
public void tick()
{
tick(DEFAULT_TICK);
}
// ITicking supports multiticking
public void tick(int pTick)
{
mTimer += pTick;
if (mTimeout < mTimer)
{
resetTimer();
save();
}
}
public void setTimeout(int pTimeout)
{
mTimeout = pTimeout;
}
// because splitting timeout time and FPS is probably a good thing
public void setTimeout(int pSeconds, int pFps)
{
mTimeout = pSeconds * pFps;
}
public void resetTimer()
{
mTimer = 0;
}
private void resetVersion()
{
mVersion = 0;
}
public boolean save()
{
mVersion++;
resetTimer();
boolean success = saveState(mFileName);
success &= saveState(mMirrorName);
return success;
}
public Serializable load()
{
Serializable result;
SerializableShell data = loadShell(mFileName);
SerializableShell mirror = loadShell(mMirrorName);
// null is bad
if ((null == data) && (null == mirror))
{
result = null;
}
else if (null == data)
{
result = mirror.host;
}
else if (null == mirror)
{
result = data.host;
}
// lower version is more reliable
else if (mirror.version < data.version)
{
result = mirror.host;
}
else
{
result = data.host;
}
if (null != result)
{
setHost(result);
resetVersion();
save();
}
return mHost;
}
public boolean saveState(String pFilename)
{
boolean success;
try
{
SerializableShell data = new SerializableShell(mHost,
mVersion);
FileOutputStream fos = new
FileOutputStream(pFilename);
ObjectOutputStream out = new ObjectOutputStream(fos);
out.writeObject(data);
out.close();
success = true;
}
catch (IOException e)
{
success = false;
}
return success;
}
public Serializable loadState(String pFilename)
{
SerializableShell data = loadShell(pFilename);
if (null == data)
{
return null;
}
return data.host;
}
private SerializableShell loadShell(String pFilename)
{
SerializableShell data = null;
try
{
FileInputStream fis = new
FileInputStream(pFilename);
ObjectInputStream in = new ObjectInputStream(fis);
data = (SerializableShell)in.readObject();
in.close();
}
catch (Throwable e)
{
data = null;
}
return data;
}
public boolean deleteFiles()
{
boolean success = (new File(mFileName)).delete();
success &= (new File(mMirrorName)).delete();
return success;
}
class SerializableShell
implements Serializable
{
public Serializable host;
public int version;
public SerializableShell(Serializable pHost, int pVersion)
{
host = pHost;
version = pVersion;
}
}
}
-Brendan