/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.sun.jini.norm;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jini.config.ConfigurationException;
import com.sun.jini.norm.lookup.SubStore;
import com.sun.jini.reliableLog.LogHandler;
import com.sun.jini.reliableLog.ReliableLog;
import com.sun.jini.system.FileSystem;
import com.sun.jini.thread.ReadersWriter;
/**
* Class that actually stores a Norm server's state to disk. Basically
* a wrapper around ReliableLog with the addition of lock management.
*
* @author Sun Microsystems, Inc.
*/
class PersistentStore {
/** Logger for logging messages for this class */
private static final Logger logger = Logger.getLogger("com.sun.jini.norm");
/**
* Object we use to reliably and persistently log updates to our
* state, or null if not persistent.
*/
private ReliableLog log;
/**
* No mutation of persistent state can occur during a snapshot,
* however, we can have multiple mutators, use
* <code>ReadersWriter</code> object to manage this invariant. Note
* as far as the lock is concerned the mutators are the readers and
* snapshot thread the writer since for us mutation is the
* non-exclusive operation.
*/
final private ReadersWriter mutatorLock = new ReadersWriter();
/**
* Thread local that tracks if (and how many times) the current thread
* has acquired a non-exclusive mutator lock.
*/
final static private ThreadLocal lockState = new ThreadLocal();
/** Cache a <code>Long</code> object with a zero value */
final static private Long zero = new Long(0);
/** Location of the persistent store, or null if not persistent */
final private File storeLocation;
/** Object that handles the recovery of logs, or null if not persistent */
final private LogHandler logHandler;
/** The NormServer we are part of */
final private NormServerBaseImpl server;
/** Number of updates since last snapshot */
private int updateCount;
/** A list of all of the sub-stores */
private List subStores = new LinkedList();
/**
* Construct a store that will persist its data to the specified
* directory.
*
* @param logDir directory where the store should persist its data,
* which must exist, unless it is <code>null</code>, in which case
* there is no persistence
* @param logHandler object that will process the log and last
* snapshot to recover the server's state
* @param server the server is called back after an update so it can
* decide whether or not to do a snapshot
* @throws StoreException if there is a problem setting up the store
*/
PersistentStore(String logDir, LogHandler logHandler,
NormServerBaseImpl server)
throws StoreException
{
this.logHandler = logHandler;
this.server = server;
if (logDir == null) {
storeLocation = null;
} else {
storeLocation = new File(logDir);
try {
log = new ReliableLog(
storeLocation.getCanonicalPath(), logHandler);
} catch (IOException e) {
throw new CorruptedStoreException(
"Failure creating reliable log", e);
}
try {
log.recover();
} catch (IOException e) {
throw new CorruptedStoreException(
"Failure recovering reliable log", e);
}
}
}
/**
* Destroy the store.
*
* @throws IOException if it has difficulty removing the log files
*/
void destroy() throws IOException {
// Prep all the sub-stores to be destroyed
for (Iterator i = subStores.iterator(); i.hasNext(); ) {
SubStore subStore = (SubStore) i.next();
subStore.prepareDestroy();
}
if (log != null) {
log.deletePersistentStore();
FileSystem.destroy(storeLocation, true);
}
}
/**
* Inform the store of a sub-store
*/
void addSubStore(SubStore subStore) throws StoreException {
try {
if (log == null) {
subStore.setDirectory(null);
} else {
final String subDir = subStore.subDirectory();
if (subDir == null) {
subStore.setDirectory(storeLocation);
} else {
subStore.setDirectory(new File(storeLocation, subDir));
}
}
subStores.add(subStore);
} catch (IOException e) {
throw new StoreException("Failure adding substore " + subStore,
e);
} catch (ConfigurationException e) {
throw new StoreException("Failure adding substore " + subStore,
e);
}
}
/////////////////////////////////////////////////////////////////
// Methods for obtaining and releasing the locks on the store
/**
* Block until we can acquire a non-exclusive mutator lock on the
* server's persistent state. This lock should be acquired in a
* <code>try</code> block and a <code>releaseMutatorLock</code> call
* should be placed in a <code>finally</code> block.
*/
void acquireMutatorLock() {
// Do we already hold a lock?
Long lockStateVal = (Long) lockState.get();
if (lockStateVal == null)
lockStateVal = zero;
final long longVal = lockStateVal.longValue();
if (longVal == 0) {
// No, this thread currently does not hold a lock,
// grab non-exclusive lock (which for mutatorLock is a
// read lock)
mutatorLock.readLock();
}
// Either way, bump the lock count and update our thread state
lockState.set(new Long(longVal + 1));
}
/**
* Release one level of mutator locks if this thread holds at least one.
*/
void releaseMutatorLock() {
Long lockStateVal = (Long) lockState.get();
if (lockStateVal == null)
lockStateVal = zero;
final long longVal = lockStateVal.longValue();
if (longVal == 0) {
// No lock to release, return
return;
}
if (longVal == 1) {
// Last one on stack release lock
// Using read lock because we want a non-exclusive lock
mutatorLock.readUnlock();
lockStateVal = zero;
} else {
lockStateVal = new Long(longVal - 1);
}
lockState.set(lockStateVal);
}
//////////////////////////////////////////////////////////////////
// Methods for writing records to the log and taking and
// coordinating snapshots
/**
* Log an update. Will flush to disk before returning.
*
* @throws IllegalStateException if the current thread does not hold
* a non-exclusive mutator lock
* @throws IOException
* @see ReliableLog#update
*/
void update(Object o) {
if (log == null) {
return;
}
final Long lockStateVal = (Long) lockState.get();
if (lockStateVal == null || lockStateVal.longValue() == 0) {
throw new IllegalStateException("PersistentStore.update:" +
"Must acquire mutator lock before calling update()");
}
synchronized (this) {
try {
log.update(o, true);
updateCount++;
server.updatePerformed(updateCount);
} catch (IOException e) {
// $$$ should probably be propagating this exception
logger.log(Level.WARNING, "IOException while updating log", e);
}
}
}
/**
* Generate a snapshot, will perform the necessary locking to ensure no
* threads are mutating the state of the server before creating the
* snapshot.
*
* @throws IOException
* @see ReliableLog#snapshot
*/
void snapshot() throws IOException {
if (log == null) {
return;
}
try {
// Using write lock because we want an exclusive lock
mutatorLock.writeLock();
updateCount = 0;
// Don't need to sync on this because
// mutatorLock.writeLock() gives us an exclusive lock
log.snapshot();
} finally {
// Using write lock because we want an exclusive lock
mutatorLock.writeUnlock();
}
}
}