Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/rmi/log/ReliableLog.java
38919 views
/*1* Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.rmi.log;2627import java.io.*;28import java.lang.reflect.Constructor;29import java.rmi.server.RMIClassLoader;30import java.security.AccessController;31import java.security.PrivilegedAction;32import sun.security.action.GetBooleanAction;33import sun.security.action.GetPropertyAction;3435/**36* This class is a simple implementation of a reliable Log. The37* client of a ReliableLog must provide a set of callbacks (via a38* LogHandler) that enables a ReliableLog to read and write39* checkpoints and log records. This implementation ensures that the40* current value of the data stored (via a ReliableLog) is recoverable41* after a system crash. <p>42*43* The secondary storage strategy is to record values in files using a44* representation of the caller's choosing. Two sorts of files are45* kept: snapshots and logs. At any instant, one snapshot is current.46* The log consists of a sequence of updates that have occurred since47* the current snapshot was taken. The current stable state is the48* value of the snapshot, as modified by the sequence of updates in49* the log. From time to time, the client of a ReliableLog instructs50* the package to make a new snapshot and clear the log. A ReliableLog51* arranges disk writes such that updates are stable (as long as the52* changes are force-written to disk) and atomic : no update is lost,53* and each update either is recorded completely in the log or not at54* all. Making a new snapshot is also atomic. <p>55*56* Normal use for maintaining the recoverable store is as follows: The57* client maintains the relevant data structure in virtual memory. As58* updates happen to the structure, the client informs the ReliableLog59* (all it "log") by calling log.update. Periodically, the client60* calls log.snapshot to provide the current value of the data61* structure. On restart, the client calls log.recover to obtain the62* latest snapshot and the following sequences of updates; the client63* applies the updates to the snapshot to obtain the state that64* existed before the crash. <p>65*66* The current logfile format is: <ol>67* <li> a format version number (two 4-octet integers, major and68* minor), followed by69* <li> a sequence of log records. Each log record contains, in70* order, <ol>71* <li> a 4-octet integer representing the length of the following log72* data,73* <li> the log data (variable length). </ol> </ol> <p>74*75* @see LogHandler76*77* @author Ann Wollrath78*79*/80public class ReliableLog {8182public final static int PreferredMajorVersion = 0;83public final static int PreferredMinorVersion = 2;8485// sun.rmi.log.debug=false86private boolean Debug = false;8788private static String snapshotPrefix = "Snapshot.";89private static String logfilePrefix = "Logfile.";90private static String versionFile = "Version_Number";91private static String newVersionFile = "New_Version_Number";92private static int intBytes = 4;93private static long diskPageSize = 512;9495private File dir; // base directory96private int version = 0; // current snapshot and log version97private String logName = null;98private LogFile log = null;99private long snapshotBytes = 0;100private long logBytes = 0;101private int logEntries = 0;102private long lastSnapshot = 0;103private long lastLog = 0;104//private long padBoundary = intBytes;105private LogHandler handler;106private final byte[] intBuf = new byte[4];107108// format version numbers read from/written to this.log109private int majorFormatVersion = 0;110private int minorFormatVersion = 0;111112113/**114* Constructor for the log file. If the system property115* sun.rmi.log.class is non-null and the class specified by this116* property a) can be loaded, b) is a subclass of LogFile, and c) has a117* public two-arg constructor (String, String), ReliableLog uses the118* constructor to construct the LogFile.119**/120private static final Constructor<? extends LogFile>121logClassConstructor = getLogClassConstructor();122123/**124* Creates a ReliableLog to handle checkpoints and logging in a125* stable storage directory.126*127* @param dirPath path to the stable storage directory128* @param logCl the closure object containing callbacks for logging and129* recovery130* @param pad ignored131* @exception IOException If a directory creation error has132* occurred or if initialSnapshot callback raises an exception or133* if an exception occurs during invocation of the handler's134* snapshot method or if other IOException occurs.135*/136public ReliableLog(String dirPath,137LogHandler handler,138boolean pad)139throws IOException140{141super();142this.Debug = AccessController.doPrivileged(143new GetBooleanAction("sun.rmi.log.debug")).booleanValue();144dir = new File(dirPath);145if (!(dir.exists() && dir.isDirectory())) {146// create directory147if (!dir.mkdir()) {148throw new IOException("could not create directory for log: " +149dirPath);150}151}152//padBoundary = (pad ? diskPageSize : intBytes);153this.handler = handler;154lastSnapshot = 0;155lastLog = 0;156getVersion();157if (version == 0) {158try {159snapshot(handler.initialSnapshot());160} catch (IOException e) {161throw e;162} catch (Exception e) {163throw new IOException("initial snapshot failed with " +164"exception: " + e);165}166}167}168169/**170* Creates a ReliableLog to handle checkpoints and logging in a171* stable storage directory.172*173* @param dirPath path to the stable storage directory174* @param logCl the closure object containing callbacks for logging and175* recovery176* @exception IOException If a directory creation error has177* occurred or if initialSnapshot callback raises an exception178*/179public ReliableLog(String dirPath,180LogHandler handler)181throws IOException182{183this(dirPath, handler, false);184}185186/* public methods */187188/**189* Returns an object which is the value recorded in the current190* snapshot. This snapshot is recovered by calling the client191* supplied callback "recover" and then subsequently invoking192* the "readUpdate" callback to apply any logged updates to the state.193*194* @exception IOException If recovery fails due to serious log195* corruption, read update failure, or if an exception occurs196* during the recover callback197*/198public synchronized Object recover()199throws IOException200{201if (Debug)202System.err.println("log.debug: recover()");203204if (version == 0)205return null;206207Object snapshot;208String fname = versionName(snapshotPrefix);209File snapshotFile = new File(fname);210InputStream in =211new BufferedInputStream(new FileInputStream(snapshotFile));212213if (Debug)214System.err.println("log.debug: recovering from " + fname);215216try {217try {218snapshot = handler.recover(in);219220} catch (IOException e) {221throw e;222} catch (Exception e) {223if (Debug)224System.err.println("log.debug: recovery failed: " + e);225throw new IOException("log recover failed with " +226"exception: " + e);227}228snapshotBytes = snapshotFile.length();229} finally {230in.close();231}232233return recoverUpdates(snapshot);234}235236/**237* Records this update in the log file (does not force update to disk).238* The update is recorded by calling the client's "writeUpdate" callback.239* This method must not be called until this log's recover method has240* been invoked (and completed).241*242* @param value the object representing the update243* @exception IOException If an exception occurred during a244* writeUpdate callback or if other I/O error has occurred.245*/246public synchronized void update(Object value) throws IOException {247update(value, true);248}249250/**251* Records this update in the log file. The update is recorded by252* calling the client's writeUpdate callback. This method must not be253* called until this log's recover method has been invoked254* (and completed).255*256* @param value the object representing the update257* @param forceToDisk ignored; changes are always forced to disk258* @exception IOException If force-write to log failed or an259* exception occurred during the writeUpdate callback or if other260* I/O error occurs while updating the log.261*/262public synchronized void update(Object value, boolean forceToDisk)263throws IOException264{265// avoid accessing a null log field.266if (log == null) {267throw new IOException("log is inaccessible, " +268"it may have been corrupted or closed");269}270271/*272* If the entry length field spans a sector boundary, write273* the high order bit of the entry length, otherwise write zero for274* the entry length.275*/276long entryStart = log.getFilePointer();277boolean spansBoundary = log.checkSpansBoundary(entryStart);278writeInt(log, spansBoundary? 1<<31 : 0);279280/*281* Write update, and sync.282*/283try {284handler.writeUpdate(new LogOutputStream(log), value);285} catch (IOException e) {286throw e;287} catch (Exception e) {288throw (IOException)289new IOException("write update failed").initCause(e);290}291log.sync();292293long entryEnd = log.getFilePointer();294int updateLen = (int) ((entryEnd - entryStart) - intBytes);295log.seek(entryStart);296297if (spansBoundary) {298/*299* If length field spans a sector boundary, then300* the next two steps are required (see 4652922):301*302* 1) Write actual length with high order bit set; sync.303* 2) Then clear high order bit of length; sync.304*/305writeInt(log, updateLen | 1<<31);306log.sync();307308log.seek(entryStart);309log.writeByte(updateLen >> 24);310log.sync();311312} else {313/*314* Write actual length; sync.315*/316writeInt(log, updateLen);317log.sync();318}319320log.seek(entryEnd);321logBytes = entryEnd;322lastLog = System.currentTimeMillis();323logEntries++;324}325326/**327* Returns the constructor for the log file if the system property328* sun.rmi.log.class is non-null and the class specified by the329* property a) can be loaded, b) is a subclass of LogFile, and c) has a330* public two-arg constructor (String, String); otherwise returns null.331**/332private static Constructor<? extends LogFile>333getLogClassConstructor() {334335String logClassName = AccessController.doPrivileged(336new GetPropertyAction("sun.rmi.log.class"));337if (logClassName != null) {338try {339ClassLoader loader =340AccessController.doPrivileged(341new PrivilegedAction<ClassLoader>() {342public ClassLoader run() {343return ClassLoader.getSystemClassLoader();344}345});346Class<? extends LogFile> cl =347loader.loadClass(logClassName).asSubclass(LogFile.class);348return cl.getConstructor(String.class, String.class);349} catch (Exception e) {350System.err.println("Exception occurred:");351e.printStackTrace();352}353}354return null;355}356357/**358* Records this value as the current snapshot by invoking the client359* supplied "snapshot" callback and then empties the log.360*361* @param value the object representing the new snapshot362* @exception IOException If an exception occurred during the363* snapshot callback or if other I/O error has occurred during the364* snapshot process365*/366public synchronized void snapshot(Object value)367throws IOException368{369int oldVersion = version;370incrVersion();371372String fname = versionName(snapshotPrefix);373File snapshotFile = new File(fname);374FileOutputStream out = new FileOutputStream(snapshotFile);375try {376try {377handler.snapshot(out, value);378} catch (IOException e) {379throw e;380} catch (Exception e) {381throw new IOException("snapshot failed", e);382}383lastSnapshot = System.currentTimeMillis();384} finally {385out.close();386snapshotBytes = snapshotFile.length();387}388389openLogFile(true);390writeVersionFile(true);391commitToNewVersion();392deleteSnapshot(oldVersion);393deleteLogFile(oldVersion);394}395396/**397* Close the stable storage directory in an orderly manner.398*399* @exception IOException If an I/O error occurs when the log is400* closed401*/402public synchronized void close() throws IOException {403if (log == null) return;404try {405log.close();406} finally {407log = null;408}409}410411/**412* Returns the size of the snapshot file in bytes;413*/414public long snapshotSize() {415return snapshotBytes;416}417418/**419* Returns the size of the log file in bytes;420*/421public long logSize() {422return logBytes;423}424425/* private methods */426427/**428* Write an int value in single write operation. This method429* assumes that the caller is synchronized on the log file.430*431* @param out output stream432* @param val int value433* @throws IOException if any other I/O error occurs434*/435private void writeInt(DataOutput out, int val)436throws IOException437{438intBuf[0] = (byte) (val >> 24);439intBuf[1] = (byte) (val >> 16);440intBuf[2] = (byte) (val >> 8);441intBuf[3] = (byte) val;442out.write(intBuf);443}444445/**446* Generates a filename prepended with the stable storage directory path.447*448* @param name the leaf name of the file449*/450private String fName(String name) {451return dir.getPath() + File.separator + name;452}453454/**455* Generates a version 0 filename prepended with the stable storage456* directory path457*458* @param name version file name459*/460private String versionName(String name) {461return versionName(name, 0);462}463464/**465* Generates a version filename prepended with the stable storage466* directory path with the version number as a suffix.467*468* @param name version file name469* @thisversion a version number470*/471private String versionName(String prefix, int ver) {472ver = (ver == 0) ? version : ver;473return fName(prefix) + String.valueOf(ver);474}475476/**477* Increments the directory version number.478*/479private void incrVersion() {480do { version++; } while (version==0);481}482483/**484* Delete a file.485*486* @param name the name of the file487* @exception IOException If new version file couldn't be removed488*/489private void deleteFile(String name) throws IOException {490491File f = new File(name);492if (!f.delete())493throw new IOException("couldn't remove file: " + name);494}495496/**497* Removes the new version number file.498*499* @exception IOException If an I/O error has occurred.500*/501private void deleteNewVersionFile() throws IOException {502deleteFile(fName(newVersionFile));503}504505/**506* Removes the snapshot file.507*508* @param ver the version to remove509* @exception IOException If an I/O error has occurred.510*/511private void deleteSnapshot(int ver) throws IOException {512if (ver == 0) return;513deleteFile(versionName(snapshotPrefix, ver));514}515516/**517* Removes the log file.518*519* @param ver the version to remove520* @exception IOException If an I/O error has occurred.521*/522private void deleteLogFile(int ver) throws IOException {523if (ver == 0) return;524deleteFile(versionName(logfilePrefix, ver));525}526527/**528* Opens the log file in read/write mode. If file does not exist, it is529* created.530*531* @param truncate if true and file exists, file is truncated to zero532* length533* @exception IOException If an I/O error has occurred.534*/535private void openLogFile(boolean truncate) throws IOException {536try {537close();538} catch (IOException e) { /* assume this is okay */539}540541logName = versionName(logfilePrefix);542543try {544log = (logClassConstructor == null ?545new LogFile(logName, "rw") :546logClassConstructor.newInstance(logName, "rw"));547} catch (Exception e) {548throw (IOException) new IOException(549"unable to construct LogFile instance").initCause(e);550}551552if (truncate) {553initializeLogFile();554}555}556557/**558* Creates a new log file, truncated and initialized with the format559* version number preferred by this implementation.560* <p>Environment: inited, synchronized561* <p>Precondition: valid: log, log contains nothing useful562* <p>Postcondition: if successful, log is initialised with the format563* version number (Preferred{Major,Minor}Version), and logBytes is564* set to the resulting size of the updatelog, and logEntries is set to565* zero. Otherwise, log is in an indeterminate state, and logBytes566* is unchanged, and logEntries is unchanged.567*568* @exception IOException If an I/O error has occurred.569*/570private void initializeLogFile()571throws IOException572{573log.setLength(0);574majorFormatVersion = PreferredMajorVersion;575writeInt(log, PreferredMajorVersion);576minorFormatVersion = PreferredMinorVersion;577writeInt(log, PreferredMinorVersion);578logBytes = intBytes * 2;579logEntries = 0;580}581582583/**584* Writes out version number to file.585*586* @param newVersion if true, writes to a new version file587* @exception IOException If an I/O error has occurred.588*/589private void writeVersionFile(boolean newVersion) throws IOException {590String name;591if (newVersion) {592name = newVersionFile;593} else {594name = versionFile;595}596try (FileOutputStream fos = new FileOutputStream(fName(name));597DataOutputStream out = new DataOutputStream(fos)) {598writeInt(out, version);599}600}601602/**603* Creates the initial version file604*605* @exception IOException If an I/O error has occurred.606*/607private void createFirstVersion() throws IOException {608version = 0;609writeVersionFile(false);610}611612/**613* Commits (atomically) the new version.614*615* @exception IOException If an I/O error has occurred.616*/617private void commitToNewVersion() throws IOException {618writeVersionFile(false);619deleteNewVersionFile();620}621622/**623* Reads version number from a file.624*625* @param name the name of the version file626* @return the version627* @exception IOException If an I/O error has occurred.628*/629private int readVersion(String name) throws IOException {630try (DataInputStream in = new DataInputStream631(new FileInputStream(name))) {632return in.readInt();633}634}635636/**637* Sets the version. If version file does not exist, the initial638* version file is created.639*640* @exception IOException If an I/O error has occurred.641*/642private void getVersion() throws IOException {643try {644version = readVersion(fName(newVersionFile));645commitToNewVersion();646} catch (IOException e) {647try {648deleteNewVersionFile();649}650catch (IOException ex) {651}652653try {654version = readVersion(fName(versionFile));655}656catch (IOException ex) {657createFirstVersion();658}659}660}661662/**663* Applies outstanding updates to the snapshot.664*665* @param state the most recent snapshot666* @exception IOException If serious log corruption is detected or667* if an exception occurred during a readUpdate callback or if668* other I/O error has occurred.669* @return the resulting state of the object after all updates670*/671private Object recoverUpdates(Object state)672throws IOException673{674logBytes = 0;675logEntries = 0;676677if (version == 0) return state;678679String fname = versionName(logfilePrefix);680InputStream in =681new BufferedInputStream(new FileInputStream(fname));682DataInputStream dataIn = new DataInputStream(in);683684if (Debug)685System.err.println("log.debug: reading updates from " + fname);686687try {688majorFormatVersion = dataIn.readInt(); logBytes += intBytes;689minorFormatVersion = dataIn.readInt(); logBytes += intBytes;690} catch (EOFException e) {691/* This is a log which was corrupted and/or cleared (by692* fsck or equivalent). This is not an error.693*/694openLogFile(true); // create and truncate695in = null;696}697/* A new major version number is a catastrophe (it means698* that the file format is incompatible with older699* clients, and we'll only be breaking things by trying to700* use the log). A new minor version is no big deal for701* upward compatibility.702*/703if (majorFormatVersion != PreferredMajorVersion) {704if (Debug) {705System.err.println("log.debug: major version mismatch: " +706majorFormatVersion + "." + minorFormatVersion);707}708throw new IOException("Log file " + logName + " has a " +709"version " + majorFormatVersion +710"." + minorFormatVersion +711" format, and this implementation " +712" understands only version " +713PreferredMajorVersion + "." +714PreferredMinorVersion);715}716717try {718while (in != null) {719int updateLen = 0;720721try {722updateLen = dataIn.readInt();723} catch (EOFException e) {724if (Debug)725System.err.println("log.debug: log was sync'd cleanly");726break;727}728if (updateLen <= 0) {/* crashed while writing last log entry */729if (Debug) {730System.err.println(731"log.debug: last update incomplete, " +732"updateLen = 0x" +733Integer.toHexString(updateLen));734}735break;736}737738// this is a fragile use of available() which relies on the739// twin facts that BufferedInputStream correctly consults740// the underlying stream, and that FileInputStream returns741// the number of bytes remaining in the file (via FIONREAD).742if (in.available() < updateLen) {743/* corrupted record at end of log (can happen since we744* do only one fsync)745*/746if (Debug)747System.err.println("log.debug: log was truncated");748break;749}750751if (Debug)752System.err.println("log.debug: rdUpdate size " + updateLen);753try {754state = handler.readUpdate(new LogInputStream(in, updateLen),755state);756} catch (IOException e) {757throw e;758} catch (Exception e) {759e.printStackTrace();760throw new IOException("read update failed with " +761"exception: " + e);762}763logBytes += (intBytes + updateLen);764logEntries++;765} /* while */766} finally {767if (in != null)768in.close();769}770771if (Debug)772System.err.println("log.debug: recovered updates: " + logEntries);773774/* reopen log file at end */775openLogFile(false);776777// avoid accessing a null log field778if (log == null) {779throw new IOException("rmid's log is inaccessible, " +780"it may have been corrupted or closed");781}782783log.seek(logBytes);784log.setLength(logBytes);785786return state;787}788789/**790* ReliableLog's log file implementation. This implementation791* is subclassable for testing purposes.792*/793public static class LogFile extends RandomAccessFile {794795private final FileDescriptor fd;796797/**798* Constructs a LogFile and initializes the file descriptor.799**/800public LogFile(String name, String mode)801throws FileNotFoundException, IOException802{803super(name, mode);804this.fd = getFD();805}806807/**808* Invokes sync on the file descriptor for this log file.809*/810protected void sync() throws IOException {811fd.sync();812}813814/**815* Returns true if writing 4 bytes starting at the specified file816* position, would span a 512 byte sector boundary; otherwise returns817* false.818**/819protected boolean checkSpansBoundary(long fp) {820return fp % 512 > 508;821}822}823}824825826