/* * Copyright 2008 the original author or authors. * Copyright 2005 Sun Microsystems, 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 org.rioproject.impl.persistence; import com.sun.jini.reliableLog.LogHandler; import com.sun.jini.reliableLog.ReliableLog; import com.sun.jini.thread.ReadersWriter; import java.io.File; import java.io.IOException; import java.util.List; /** * Class that stores a server's state to disk. Basically a wrapper around ReliableLog * with the addition of lock management. */ public class PersistentStore { /** Object we use to reliable and penitently log updates to our state */ 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 reader) 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<Long> lockState = new ThreadLocal<Long>(); /** Cache a <code>Long</code> object with a zero value */ final static private Long zero = (long) 0; /** Location of the persistent store */ final private File storeLocation; /** The server this PersistentStore serves */ final private SnapshotHandler snapshotHandler; /** Number of updates since last snapshot */ private int updateCount; /** A list of all of the substores */ private List<SubStore> subStores = new java.util.LinkedList<SubStore>(); /** * Construct a store that will persist its data to the specified * directory. * @param logDir Directory where the store should persist its data. * must exist. * @param logHandler Object that will process the log and last snapshot * to recover the server's state * @param snapshotHandler 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 */ public PersistentStore(String logDir, LogHandler logHandler, SnapshotHandler snapshotHandler) throws StoreException { this.snapshotHandler = snapshotHandler; 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 */ public void destroy() throws IOException { // Prep all the sub-stores to be destroyed for (SubStore subStore : subStores) { subStore.prepareDestroy(); } log.deletePersistentStore(); FileSystem.destroy(storeLocation, true); } /** * Get the absolute path location for the store location * * @return The absolute path location for the store location */ public String getStoreLocation(){ return(storeLocation.getAbsolutePath()); } /** * Inform the store of a sub-store * * @param subStore The SubStore to add * * @throws StoreException if the SubStore cannot be added */ public void addSubStore(SubStore subStore) throws StoreException { try { 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); } } ///////////////////////////////////////////////////////////////// // Methods for obtaining and releasing the locks on the store /** * Block until we can acquire a non-exclusive mutator lock on the * servers '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. */ public void acquireMutatorLock() { // Do we already hold a lock? Long lockStateVal = lockState.get(); if(lockStateVal == null) lockStateVal = zero; final long longVal = lockStateVal; 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(); } // Ether way, bump the lock count and update our thread state lockState.set(longVal + 1); } /** * Release one level of mutator locks if this thread holds at lease one. */ public void releaseMutatorLock() { Long lockStateVal = lockState.get(); if(lockStateVal == null) lockStateVal = zero; final long longVal = lockStateVal; 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 = 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. * * @param o Update argument * * @throws IllegalStateException if the current thread does not hold * a non-exclusive mutator lock * @throws IOException If errors accessing the file system occur */ public void update(Object o) throws IOException { final Long lockStateVal = lockState.get(); if(lockStateVal == null || lockStateVal == 0) throw new IllegalStateException("PersistentStrore.update:" + "Must acquire mutator lock before calling update()"); synchronized (this) { log.update(o, true); updateCount++; snapshotHandler.updatePerformed(updateCount); } } /** * 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 * @see com.sun.jini.reliableLog.ReliableLog#snapshot */ public void snapshot() throws IOException { 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(); } } /** * Close this PersistentStore without destroying its contents * * @throws IOException If file system access errors are encountered */ public void close() throws IOException { 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.close(); } finally { // Using write lock because we want an exclusive lock mutatorLock.writeUnlock(); } } }