Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.tangosol.persistence.AbstractPersistenceManager Maven / Gradle / Ivy
/*
* Copyright (c) 2000, 2022, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl.
*/
package com.tangosol.persistence;
import com.oracle.coherence.common.base.Collector;
import com.oracle.coherence.common.base.Blocking;
import com.oracle.coherence.common.base.Logger;
import com.oracle.coherence.common.base.Timeout;
import com.oracle.coherence.persistence.AsyncPersistenceException;
import com.oracle.coherence.persistence.ConcurrentAccessException;
import com.oracle.coherence.persistence.FatalAccessException;
import com.oracle.coherence.persistence.OfflinePersistenceInfo;
import com.oracle.coherence.persistence.PersistenceException;
import com.oracle.coherence.persistence.PersistenceManager;
import com.oracle.coherence.persistence.PersistenceStatistics;
import com.oracle.coherence.persistence.PersistenceTools;
import com.oracle.coherence.persistence.PersistentStore;
import com.tangosol.internal.util.DaemonPool;
import com.tangosol.io.ByteArrayReadBuffer;
import com.tangosol.io.FileHelper;
import com.tangosol.io.ReadBuffer;
import com.tangosol.io.ReadBuffer.BufferInput;
import com.tangosol.io.WrapperBufferOutput;
import com.tangosol.io.WriteBuffer.BufferOutput;
import com.tangosol.net.cache.KeyAssociation;
import com.tangosol.persistence.AbstractPersistenceManager.AbstractPersistentStore;
import com.tangosol.net.GuardSupport;
import com.tangosol.util.Base;
import com.tangosol.util.ClassHelper;
import com.tangosol.util.NullImplementation;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StreamCorruptedException;
import java.nio.channels.FileLock;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
/**
* Abstract implementation of a ReadBuffer-based PersistentManager.
*
* @param the type of AbstractPersistentStore
*
* @author jh 2012.10.04
*/
public abstract class AbstractPersistenceManager
extends Base
implements PersistenceManager
{
// ----- constructors ---------------------------------------------------
/**
* Create a new AbstractPersistenceManager.
*
* @param fileData the directory used to store persisted data
* @param fileTrash an optional trash directory
* @param sName an optional name to give the new manager
*
* @throws IOException on error creating the data or trash directory
*/
public AbstractPersistenceManager(File fileData, File fileTrash, String sName)
throws IOException
{
f_dirData = FileHelper.ensureDir(fileData);
f_dirTrash = fileTrash;
f_dirLock = new File(f_dirData, CachePersistenceHelper.DEFAULT_LOCK_DIR);
f_sName = sName == null ? fileData.toString() : sName;
}
// ----- PersistenceManager interface -----------------------------------
/**
* {@inheritDoc}
*/
@Override
public String getName()
{
return f_sName;
}
/**
* {@inheritDoc}
*/
@Override
public PS createStore(String sId)
{
// validate the store ID
sId = validatePersistentStoreId(sId);
// create the requested store if necessary
return f_mapStores.computeIfAbsent(sId, s ->
{
ensureActive();
return instantiatePersistentStore(s);
});
}
/**
* {@inheritDoc}
*/
@Override
public PersistentStore open(String sId, PersistentStore storeFrom)
{
return open(sId, storeFrom, null);
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public PersistentStore open(String sId, PersistentStore storeFrom, Collector collector)
{
// validate the store ID
sId = validatePersistentStoreId(sId);
// create the requested store if necessary; if the store was created
// it will be opened (either sync or async) outside of the
// ConcurrentHashMap synchronization
PersistentStore[] aStore = new PersistentStore[1];
PS store = f_mapStores.computeIfAbsent(sId, s ->
{
ensureActive();
PS storeNew = instantiatePersistentStore(s);
aStore[0] = storeNew;
return storeNew;
});
if (store == aStore[0])
{
store.submitOpen(storeFrom, collector);
}
// Note: an unopened store can be returned in both the sync (collector == null)
// and async cases; any operation that requires an opened store
// will block the calling thread until the store is either opened
// or fails to open - see #ensureReady()
return store;
}
/**
* {@inheritDoc}
*/
@Override
public void close(String sId)
{
ensureActive();
// validate the store ID
sId = validatePersistentStoreId(sId);
AbstractPersistentStore store = f_mapStores.remove(sId);
if (store != null)
{
if (!store.f_setDeletedIds.isEmpty())
{
synchronized (store.f_setDeletedIds)
{
long cMillis = 10_000L;
GuardSupport.heartbeat(cMillis << 1);
// wait for deleteExtent tasks to finish before release
try (Timeout t = Timeout.after(cMillis))
{
while (!store.f_setDeletedIds.isEmpty())
{
Blocking.wait(store.f_setDeletedIds, 100L);
}
}
catch (InterruptedException e)
{
Thread.interrupted();
Logger.finest("Store close interrupted while waiting for delete extent tasks to finish: " +
store.getId());
}
}
// reset the guardian timeout to the default
GuardSupport.heartbeat();
}
store.release();
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean delete(String sId, boolean fSafe)
{
ensureActive();
// validate the store ID
sId = validatePersistentStoreId(sId);
AbstractPersistentStore store = f_mapStores.remove(sId);
if (store == null)
{
// create a new store, but don't bother opening it
store = instantiatePersistentStore(sId);
}
return store.delete(fSafe);
}
/**
* {@inheritDoc}
*/
@Override
public String[] list()
{
// Scan for persistence storages in the data directory. Commonly, the
// persistence storages are structured as individual directories, each
// holding instance-specific files. We can ensure that a given
// directory has a correct format by checking for a properly
// configured metadata.
String[] asNames = f_dirData.list((dir, name) ->
{
File fileEnv = new File(dir, name);
if (fileEnv.isDirectory() &&
!f_dirLock.getName().equals(fileEnv.getName()))
{
try
{
Properties prop = readMetadata(fileEnv);
if (isMetadataComplete(prop))
{
if (isMetadataCompatible(prop))
{
return true;
}
Logger.warn("Skipping incompatible persistent store directory \"" + fileEnv + "\"");
}
else
{
// we return true if the metadata is incomplete
// so that the incomplete store will be cleaned
// up (see {@link #open})
return true;
}
}
catch (IOException e)
{
// we return true if the metadata cannot be read
// so that the incomplete store will be cleaned
// up (see AbstractPersistenceStore#open)
return true;
}
}
return false;
});
return asNames == null ? NO_STRINGS : asNames;
}
@Override
public boolean contains(String sGUID)
{
// TODO: optimizie this impl to not require a full directory listing
return PersistenceManager.super.contains(sGUID);
}
/**
* {@inheritDoc}
*/
@Override
public String[] listOpen()
{
if (f_mapStores.isEmpty())
{
return NO_STRINGS;
}
Set setIds = f_mapStores.keySet();
return setIds.toArray(new String[setIds.size()]);
}
/**
* {@inheritDoc}
*/
@Override
public void read(String sId, BufferInput in)
throws IOException
{
ensureActive();
// the store being materialized into must be new/empty
AbstractPersistentStore store;
synchronized (this)
{
if (f_mapStores.containsKey(sId))
{
throw new ConcurrentAccessException("the store \"" + sId
+ "\" is currently open");
}
store = (AbstractPersistentStore) open(sId, null);
store.lockWrite();
}
// read magic, metadata, and contents of the store from the stream
try
{
// read and validate magic
if (in.readInt() != MAGIC)
{
throw new StreamCorruptedException("the data stream is unrecognized");
}
// read and validate version
int nVersion = in.readByte();
if (nVersion > VERSION)
{
throw new IOException("the data stream is a newer version ("
+ nVersion + ") than is supported by this manager ("
+ VERSION + ")");
}
// read contents of the store
while (true)
{
// read key and value lengths
int cbKey = in.readInt();
if (cbKey < 0) // see #write()
{
break;
}
int cbValue = in.readInt();
if (cbValue < 0)
{
throw new StreamCorruptedException();
}
// read extent identifier
long lExtentId = in.readLong();
// read key and value
byte[] ab = new byte[cbKey + cbValue];
try
{
in.readFully(ab, 0, cbKey);
}
catch (EOFException e)
{
throw new EOFException("Expected " + cbKey + " bytes for key but reached end of stream");
}
try
{
in.readFully(ab, cbKey, cbValue);
}
catch (EOFException e)
{
throw new EOFException("Expected " + cbValue + " bytes for value but reached end of stream");
}
ReadBuffer bufKey = new ByteArrayReadBuffer(ab, 0, cbKey);
ReadBuffer bufValue = new ByteArrayReadBuffer(ab, cbKey, cbValue);
store.ensureExtent(lExtentId);
store.store(lExtentId, bufKey, bufValue, null);
}
}
catch (IOException e)
{
delete(sId, false);
throw e;
}
finally
{
store.unlockWrite();
}
}
/**
* {@inheritDoc}
*/
@Override
public void write(String sId, BufferOutput out)
throws IOException
{
ensureActive();
// open the store
AbstractPersistentStore store = (AbstractPersistentStore) open(sId, null);
// write magic, metadata, and contents of the store to the stream
store.lockRead();
try
{
// write magic
out.writeInt(MAGIC);
// write version
out.writeByte(VERSION);
// write contents of the store
final IOException[] ae = new IOException[1];
store.iterate((lExtentId, bufKey, bufValue) ->
{
try
{
// write key and value lengths
out.writeInt(bufKey.length());
out.writeInt(bufValue.length());
// write extent identifier
out.writeLong(lExtentId);
// write key and value
bufKey.writeTo((DataOutput) out);
bufValue.writeTo((DataOutput) out);
}
catch (IOException e)
{
ae[0] = e;
return false;
}
return true;
});
if (ae[0] == null)
{
// terminate the stream
out.writeInt(-1);
}
else
{
throw ae[0];
}
}
finally
{
store.unlockRead();
try
{
out.flush();
}
catch (IOException e)
{
// ignore
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void release()
{
m_fReleased = true;
boolean fInterrupted = false;
// give outstanding tasks a chance to execute
Set set = NullImplementation.getSet();
synchronized (f_setTasks)
{
// give outstanding tasks a chance to complete
try
{
long ldtEnd = getSafeTimeMillis() + 5000L;
while (!f_setTasks.isEmpty())
{
Blocking.wait(f_setTasks, 1000L);
if (getSafeTimeMillis() >= ldtEnd)
{
break;
}
}
}
catch (InterruptedException e)
{
// ignore
fInterrupted = true;
}
if (!f_setTasks.isEmpty())
{
set = new HashSet<>(f_setTasks);
}
}
// cancel all tasks that weren't executed
for (Task task : set)
{
try
{
task.cancel(null /*eCause*/);
}
catch (Throwable e)
{
// ignore
}
}
// close all open stores
for (AbstractPersistentStore store : f_mapStores.values())
{
store.release();
}
// Note: we mark the PM as released and then clear the stores, while
// open checks the released flag under synchronization of the
// ConcurrentHashMap$Node; therefore an open will not insert data
// into mapStores after the clear operation below
f_mapStores.clear();
// notify the environment that this manager has been released
AbstractPersistenceEnvironment env = m_env;
if (env != null)
{
env.onReleased(this);
}
// reset the interrupt flag, if necessary
if (fInterrupted)
{
Thread.currentThread().interrupt();
}
}
@Override
public PersistenceTools getPersistenceTools()
{
// open the first snapshot store to get the partition count
String[] asGUIDs = list();
if (asGUIDs.length == 0)
{
throw new IllegalArgumentException("snapshot must have at least one GUID");
}
String sGUID = asGUIDs[0];
int nVersion;
int cPartitions;
PersistentStore store = null;
try
{
store = open(sGUID, null);
cPartitions = CachePersistenceHelper.getPartitionCount(store);
nVersion = CachePersistenceHelper.getPersistenceVersion(store);
}
finally
{
if (store != null)
{
close(sGUID);
}
}
OfflinePersistenceInfo info = new OfflinePersistenceInfo(cPartitions, getStorageFormat(),
false, asGUIDs, getStorageVersion(), getImplVersion(), nVersion);
return instantiatePersistenceTools(info);
}
/**
* {@inheritDoc}
*/
@Override
public void writeSafe(String sId)
{
ensureActive();
// validate the store ID
sId = validatePersistentStoreId(sId);
AbstractPersistentStore store = f_mapStores.get(sId);
if (store != null)
{
store.copyToTrash();
}
}
// ----- Object methods -------------------------------------------------
/**
* Return a human readable description of this AbstractPersistenceManager.
*
* @return a human readable description
*/
@Override
public String toString()
{
return ClassHelper.getSimpleName(getClass()) +
'(' +
(f_sName == null ? "" : f_sName + ", ") +
(m_fReleased ? "in" : "") + "active)";
}
// ----- versioning support ---------------------------------------------
/**
* Return metadata for this manager.
*
* @return the metadata for this manager
*/
protected Properties getMetadata()
{
Properties props = new Properties();
props.setProperty(CachePersistenceHelper.META_IMPL_VERSION, String.valueOf(getImplVersion()));
props.setProperty(CachePersistenceHelper.META_STORAGE_FORMAT, getStorageFormat());
props.setProperty(CachePersistenceHelper.META_STORAGE_VERSION, String.valueOf(getStorageVersion()));
return props;
}
/**
* Read persistence metadata from the specified directory.
*
* @param fileDir the directory to read metadata from
*
* @return the metadata
*
* @throws IOException on error reading the metadata file
*/
protected Properties readMetadata(File fileDir)
throws IOException
{
return CachePersistenceHelper.readMetadata(fileDir);
}
/**
* Write persistence metadata for this manager to the specified directory.
*
* @param fileDir the directory to write metadata to
*
* @throws IOException on error writing the metadata file
*/
protected void writeMetadata(File fileDir)
throws IOException
{
CachePersistenceHelper.writeMetadata(fileDir, getMetadata());
}
/**
* Determine if the given metadata in the {@link Properties} is complete.
*
* @param prop the metadata to analyze
*
* @return true if the given metadata is complete; false otherwise
*/
protected boolean isMetadataComplete(Properties prop)
{
return CachePersistenceHelper.isMetadataComplete(prop);
}
/**
* Determine if the given metadata is compatible with this manager.
*
* @param prop the metadata to analyze
*
* @return true if the given metadata is compatible with this manager;
* false otherwise
*/
protected boolean isMetadataCompatible(Properties prop)
{
return CachePersistenceHelper.isMetadataCompatible(
prop,
getImplVersion(),
getStorageFormat(),
getStorageVersion());
}
/**
* Return the implementation version of this manager.
*
* @return the implementation version of this manager
*/
protected abstract int getImplVersion();
/**
* Return the storage format used by this manager.
*
* @return the storage format used by this manager
*/
protected abstract String getStorageFormat();
/**
* Return the version of the storage format used by this manager.
*
* @return the version of the storage format used by this manager
*/
protected abstract int getStorageVersion();
// ----- helper methods -------------------------------------------------
/**
* Return a PersistenceException with the given cause. The returned
* exception is also initialized with this manager and its environment
* (if available).
*
* @param eCause an optional cause
*
* @return a PersistenceException with the given cause
*/
protected PersistenceException ensurePersistenceException(Throwable eCause)
{
return ensurePersistenceException(eCause, null /*sMessage*/);
}
/**
* Return a PersistenceException with the given cause and detail message.
* The returned exception is also initialized with this manager and its
* environment (if available).
*
* @param eCause an optional cause
* @param sMessage an optional detail message
*
* @return a PersistenceException with the given cause and detail message
*/
protected PersistenceException ensurePersistenceException(Throwable eCause, String sMessage)
{
AbstractPersistenceEnvironment env = m_env;
PersistenceException e = env == null
? CachePersistenceHelper.ensurePersistenceException(eCause, sMessage)
: env.ensurePersistenceException(eCause, sMessage);
e.initPersistenceManager(this);
return e;
}
/**
* Return control if this PersistenceManager is still active, otherwise
* throw an exception.
*
* @throws IllegalStateException if this PersistenceManager has been released
*/
protected void ensureActive()
{
if (m_fReleased)
{
throw new IllegalStateException(getClass().getSimpleName() + " has been released.");
}
}
/**
* Ensure trash directory is created.
*
* @return the configured trash directory
*/
protected File ensureTrashDir()
throws IOException
{
if (!f_dirTrash.exists())
{
Logger.info("Creating persistence trash directory \""
+ f_dirTrash.getAbsolutePath() + '"');
FileHelper.ensureDir(f_dirTrash);
}
return f_dirTrash;
}
/**
* Submit the provided list of tasks for execution to the pool or execute
* directly.
*
* @param listTasks the tasks to execute
*/
protected void submitTasks(List extends Task> listTasks)
{
boolean fPool = m_pool == null;
for (Task task : listTasks)
{
if (fPool)
{
task.register();
}
else
{
task.execute();
}
}
if (fPool)
{
m_pool.add(new BatchTasks((List) listTasks));
}
}
/**
* Validate that the given identifier can be used for a persistent store.
*
* @param sId the identifier to check
*
* @return the validated identifier
*/
public String validatePersistentStoreId(String sId)
{
if (sId == null)
{
throw new IllegalArgumentException("null identifier");
}
sId = sId.trim();
if (sId.length() == 0)
{
throw new IllegalArgumentException("empty identifier");
}
return sId;
}
// ----- inner interface: Task ------------------------------------------
/**
* Runnable extension that adds the ability to notify the task that it
* has been canceled.
*/
public abstract class Task
extends Base
implements Runnable
{
// ----- Task methods -----------------------------------------------
/**
* Execute the task.
*/
public abstract void execute();
/**
* Cancel execution of the task.
*
* @param eCause the optional cause of the cancellation
*/
public final synchronized void cancel(Throwable eCause)
{
if (f_canceled)
{
return;
}
try
{
notifyCanceled(eCause);
}
finally
{
f_canceled = true;
notifyCompleted();
}
}
/**
* Register this task with the manager.
*/
protected void register()
{
AbstractPersistenceManager.this.addTask(this);
}
/**
* Notify the task that it has been canceled.
*
* @param eCause the optional cause of the cancellation
*/
protected void notifyCanceled(Throwable eCause)
{
// no-op
}
/**
* Notify the task that is has been completed.
*/
private void notifyCompleted()
{
// see AbstractPersistenceManager#release()
Set set = AbstractPersistenceManager.this.f_setTasks;
synchronized (set)
{
set.remove(this);
if (set.isEmpty())
{
set.notifyAll();
}
}
}
// ----- Runnable interface -----------------------------------------
/**
* {@inheritDoc}
*/
@Override
public final synchronized void run()
{
if (f_canceled)
{
return;
}
try
{
execute();
}
finally
{
notifyCompleted();
}
}
// ----- data members -----------------------------------------------
/**
* Canceled flag.
*/
protected boolean f_canceled;
}
/**
* Submit the given task for execution by the daemon pool.
*
* @param task the task to submit
*/
protected void submitTask(Task task)
{
DaemonPool pool = getDaemonPool();
if (pool == null)
{
executeTask(task);
}
else
{
addTask(task);
// add to the pool outside of synhronization
pool.add(task);
}
}
/**
* Add the provided task to the set of tasks.
*
* @param task the task to add
*/
protected void addTask(Task task)
{
synchronized (f_setTasks)
{
f_setTasks.add(task);
}
}
/**
* Execute the specified task with the calling thread.
*
* @param task the task to execute
*/
protected void executeTask(Task task)
{
task.execute();
}
/**
* Execute the specified task with the calling thread. No other access to
* this manager or any of its persistent stores is guaranteed to occur
* while the task is being executed.
*
* @param task the task to execute
*/
protected synchronized void executeTaskExclusive(Task task)
{
List list = new ArrayList<>(f_mapStores.size());
try
{
// lock all open stores for write
for (AbstractPersistentStore store : f_mapStores.values())
{
store.lockWrite();
list.add(store);
}
executeTask(task);
}
finally
{
// release all write locks
for (AbstractPersistentStore store : list)
{
store.unlockWrite();
}
}
}
// ----- inner class: AbstractPersistentStore ---------------------------
/**
* Factory method for PersistentStore implementations managed by this
* PersistenceManager.
*
* @param sId the identifier of the store to create
*
* @return a new AbstractPersistentStore with the given identifier
*/
protected abstract PS instantiatePersistentStore(String sId);
/**
* Factory method to create a {@link PersistenceTools} implementation.
*
* @param info the {@link OfflinePersistenceInfo} relevant to the PersistenceTools
*
* @return a new PersistenceTools implementation
*/
protected abstract PersistenceTools instantiatePersistenceTools(OfflinePersistenceInfo info);
/**
* Abstract implementation of a ReadBuffer-based PersistentStore.
*
* @author jh 2012.10.04
*/
public abstract class AbstractPersistentStore
extends Base
implements PersistentStore
{
// ----- constructors -----------------------------------------------
/**
* Create a new AbstractPersistentStore with the given identifier.
*
* @param sId the identifier for the new store
*
* @throws IllegalArgumentException if the identifier is invalid
*/
public AbstractPersistentStore(String sId)
{
if (sId == null)
{
throw new IllegalArgumentException("null identifier");
}
f_sId = sId;
f_dirStore = new File(f_dirData, sId);
f_fileLock = new File(getLockDirectory(), sId + ".lck");
}
// ----- PersistentStore interface ----------------------------------
/**
* {@inheritDoc}
*/
@Override
public String getId()
{
return f_sId;
}
/**
* {@inheritDoc}
*/
@Override
public boolean ensureExtent(long lExtentId)
{
if (!f_setExtentIds.contains(lExtentId))
{
ensureReady();
// flush any pending deleteExtent tasks
if (f_setDeletedIds.contains(lExtentId))
{
synchronized (f_setDeletedIds)
{
long cMillis = 10_000L;
GuardSupport.heartbeat(cMillis << 1);
// wait for deleteExtent tasks to finish
try (Timeout t = Timeout.after(cMillis))
{
while (f_setDeletedIds.contains(lExtentId))
{
Blocking.wait(f_setDeletedIds, 100L);
}
}
catch (InterruptedException e)
{
Thread.interrupted();
// regardless of the interrupt attempt to create
// the extent; the creation will throw if the extent
// deletion remains pending
}
}
// reset the guardian timeout to the default
GuardSupport.heartbeat();
}
lockWrite();
try
{
return ensureExtentInternal(lExtentId);
}
finally
{
unlockWrite();
}
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public void deleteExtent(long lExtentId)
{
Long LId = Long.valueOf(lExtentId);
if (f_setExtentIds.contains(LId))
{
ensureReady();
lockWrite();
try
{
// remove the identifier from the set of known extents
if (f_setExtentIds.remove(LId))
{
// add the identifier to the set of deleted
if (f_setDeletedIds.add(LId))
{
// schedule a deletion of this extent
submitTask(new DeleteExtentTask(LId));
}
}
}
finally
{
unlockWrite();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void moveExtent(long lOldExtentId, long lNewExtentId)
{
Long LId = Long.valueOf(lOldExtentId);
boolean fLock = f_setExtentIds.contains(LId);
try
{
ensureReady();
if (fLock)
{
lockWrite();
moveExtentInternal(lOldExtentId, lNewExtentId);
f_setExtentIds.remove(LId);
}
}
finally
{
ensureExtent(lNewExtentId);
if (fLock)
{
unlockWrite();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void truncateExtent(long lExtentId)
{
Long LId = Long.valueOf(lExtentId);
if (f_setExtentIds.contains(LId))
{
ensureReady();
lockWrite();
try
{
truncateExtentInternal(lExtentId);
}
finally
{
unlockWrite();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public long[] extents()
{
ensureReady();
if (f_setExtentIds.isEmpty())
{
return NO_LONGS;
}
Object[] aL = f_setExtentIds.toArray();
int cL = aL.length;
long[] al = new long[cL];
for (int i = 0; i < cL; ++i)
{
al[i] = ((Long) aL[i]).longValue();
}
return al;
}
@Override
public AutoCloseable exclusively()
{
ensureReady();
lockWrite();
return instantiateExclusiveClosable();
}
/**
* {@inheritDoc}
*/
@Override
public ReadBuffer load(long lExtentId, ReadBuffer bufKey)
{
if (bufKey == null)
{
throw new IllegalArgumentException("null key");
}
ensureReady();
lockRead();
try
{
validateExtentId(lExtentId);
return loadInternal(lExtentId, bufKey);
}
finally
{
unlockRead();
}
}
/**
* {@inheritDoc}
*/
@Override
public void store(long lExtentId, ReadBuffer bufKey, ReadBuffer bufValue, Object oToken)
{
if (bufKey == null)
{
throw new IllegalArgumentException("null key");
}
if (bufValue == null)
{
throw new IllegalArgumentException("null value");
}
ensureReady();
lockRead();
try
{
validateExtentId(lExtentId);
if (oToken instanceof AbstractPersistenceManager.AbstractPersistentStore.BatchTask)
{
((BatchTask) oToken).store(lExtentId, bufKey, bufValue);
}
else
{
// perform the store either by adding the operation to
// the supplied batch or to a newly created batch if one
// hasn't been supplied
boolean fAbort = oToken == null;
boolean fCommit = fAbort;
if (fCommit)
{
oToken = begin();
}
try
{
storeInternal(lExtentId, bufKey, bufValue, oToken);
// commit the change, if necessary
if (fCommit)
{
commit(oToken);
fAbort = false;
}
}
finally
{
// abort the change, if necessary
if (fAbort)
{
abort(oToken);
}
}
}
}
finally
{
unlockRead();
}
}
/**
* {@inheritDoc}
*/
@Override
public void erase(long lExtentId, ReadBuffer bufKey, Object oToken)
{
if (bufKey == null)
{
throw new IllegalArgumentException("null key");
}
ensureReady();
lockRead();
try
{
validateExtentId(lExtentId);
if (oToken instanceof AbstractPersistenceManager.AbstractPersistentStore.BatchTask)
{
((BatchTask) oToken).erase(lExtentId, bufKey);
}
else
{
// perform the erase either by adding the operation to
// the supplied batch or to a newly created batch if one
// hasn't been supplied
boolean fAbort = oToken == null;
boolean fCommit = fAbort;
if (fCommit)
{
oToken = begin();
}
try
{
eraseInternal(lExtentId, bufKey, oToken);
// commit the change, if necessary
if (fCommit)
{
commit(oToken);
fAbort = false;
}
}
finally
{
// abort the change, if necessary
if (fAbort)
{
abort(oToken);
}
}
}
}
finally
{
unlockRead();
}
}
/**
* {@inheritDoc}
*/
@Override
public void iterate(Visitor visitor)
{
ensureReady();
lockRead();
try
{
iterateInternal(visitor);
}
finally
{
unlockRead();
}
}
/**
* {@inheritDoc}
*/
@Override
public Object begin()
{
ensureReady();
lockRead();
try
{
return beginInternal();
}
finally
{
unlockRead();
}
}
/**
* {@inheritDoc}
*/
@Override
public Object begin(Collector collector, Object oReceipt)
{
return new BatchTask(begin(), collector, oReceipt);
}
/**
* {@inheritDoc}
*/
@Override
public void commit(Object oToken)
{
if (oToken instanceof List)
{
submitTasks((List) oToken);
}
else if (oToken instanceof AbstractPersistenceManager.AbstractPersistentStore.BatchTask)
{
AbstractPersistenceManager.this.submitTask((BatchTask) oToken);
}
else
{
ensureReady();
lockRead();
try
{
commitInternal(oToken);
}
finally
{
unlockRead();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void abort(Object oToken)
{
if (oToken instanceof AbstractPersistenceManager.AbstractPersistentStore.BatchTask)
{
((BatchTask) oToken).abort(null /*eCause*/);
}
else
{
try
{
ensureReady();
lockRead();
try
{
abortInternal(oToken);
}
finally
{
unlockRead();
}
}
catch (Throwable e)
{
// guard against any unexpected throwable
Logger.fine("Caught an exception while aborting transaction for token \"" + oToken + "\":", e);
}
}
}
/**
* Copy the store to trash if the meta.properties file exist.
*/
public void copyToTrash()
{
lockWrite();
try
{
File dirTrash = AbstractPersistenceManager.this.f_dirTrash;
if (dirTrash != null)
{
dirTrash = ensureTrashDir();
File dirStore = new File(dirTrash, f_sId);
if (!dirStore.exists() && isReady())
{
FileHelper.copyDir(f_dirStore, dirStore);
}
}
}
catch (Throwable e)
{
// fall through
}
finally
{
unlockWrite();
}
}
// ----- lifecycle methods ------------------------------------------
/**
* Open this store either asynchronously, iff both a store to open
* from and a collector have been provided, or synchronously.
*
* @param storeFrom a {@link PersistentStore} to copy from
* @param collector a {@link Collector collector} to notify when the
* open completes
*/
protected void submitOpen(PersistentStore storeFrom, Collector collector)
{
setState(STORE_STATE_OPENING);
Task task = new OpenTask(storeFrom, collector);
if (collector == null || storeFrom == null)
{
AbstractPersistenceManager.this.executeTask(task);
}
else
{
AbstractPersistenceManager.this.submitTask(task);
}
}
/**
* Open this persistent store.
*
* @param storeFrom the PersistenceStore the new store should be based upon
*
* @return true if the store was created
*/
protected boolean open(PersistentStore storeFrom)
{
boolean fClosed = true;
boolean fNew = false;
lockWrite();
try
{
// create the data directory
if (!f_dirStore.exists())
{
if (!f_dirStore.mkdir() && !f_dirStore.exists())
{
throw ensurePersistenceException(new FatalAccessException(
"unable to create data directory \"" + f_dirStore + '"'));
}
fNew = true;
}
// lock the data directory
if (!lockStorage())
{
throw ensurePersistenceException(new ConcurrentAccessException(
"unable to lock data directory \"" + f_dirStore + '"'));
}
// read and assert metadata
if (!fNew)
{
validateMetadata();
}
// copy data from the old store (if provided)
// Note: the copy is performed prior to writing metadata thus
// the store is only considered valid after a successful copy
copyAndOpenInternal(storeFrom);
try
{
loadExtentIdsInternal(f_setExtentIds);
}
catch (Throwable t)
{
if (storeFrom != null)
{
delete(false);
}
throw ensurePersistenceException(t, "Error loading database for extend identifiers in directory \"" + f_dirStore + "\"");
}
// write metadata
try
{
AbstractPersistenceManager.this.writeMetadata(f_dirStore);
}
catch (IOException e)
{
throw ensurePersistenceException(new FatalAccessException(
"error writing metadata in directory \"" + f_dirStore + '"', e));
}
// the persistent store is now opened
fClosed = false;
setState(STORE_STATE_READY);
}
finally
{
if (fClosed)
{
// cleanup
try
{
releaseInternal();
}
catch (Throwable e)
{
// ignore
}
setState(STORE_STATE_CLOSED);
}
unlockStorage();
unlockWrite();
}
return fNew;
}
/**
* Release any resources held by this persistent store.
*/
protected void release()
{
lockWrite();
try
{
// cleanup
try
{
releaseInternal();
}
catch (Throwable e)
{
// ignore
}
setState(STORE_STATE_CLOSED);
f_setExtentIds.clear();
f_setDeletedIds.clear();
}
finally
{
unlockWrite();
}
}
/**
* Release any resources held by this persistent store and delete any
* underlying persistent storage.
*
* @param fSafe if true, remove the store by moving it to a restorable
* location (if possible) rather than deleting it
*
* @return true if the store was successfully deleted, false otherwise
*/
protected boolean delete(boolean fSafe)
{
boolean fDeleted = false;
lockWrite();
try
{
release();
if (lockStorage())
{
try
{
File fileTrash = AbstractPersistenceManager.this.f_dirTrash;
if (fSafe && fileTrash != null)
{
// create the trash directory
fileTrash = ensureTrashDir();
// move the data directory to the trash iff the meta.properties
// file exists - a sign of birth
File fileMeta = new File(f_dirStore, CachePersistenceHelper.META_FILENAME);
if (fileMeta.exists())
{
FileHelper.moveDir(f_dirStore, new File(fileTrash, f_sId));
}
}
deleteInternal();
fDeleted = true;
}
catch (Throwable e)
{
// fall through
}
finally
{
unlockStorage();
// delete the lock file followed by the store
fDeleted = fDeleted && f_fileLock.delete();
try
{
FileHelper.deleteDir(f_dirStore);
}
catch (IOException ignore) {}
}
}
}
finally
{
unlockWrite();
}
return fDeleted;
}
/**
* Block the calling thread until the store is either ready to accept
* requests or the store has been closed.
*
* @throws PersistenceException if the store has been closed or the
* thread was interrupted
*/
protected void ensureReady()
{
while (!isReady())
{
if (isClosed())
{
throw ensurePersistenceException(null, "Store (" + toString() + ") has been closed");
}
synchronized (this)
{
try
{
Blocking.wait(this, 10L);
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
throw ensurePersistenceException(e, "Interrupted while waiting for store to be opened");
}
}
}
}
/**
* Return true if the store is ready to accept requests.
*
* @return true if the store is ready to accept requests
*/
protected boolean isReady()
{
return (m_nState & STORE_STATE_READY) != 0;
}
/**
* Return true if the store has been closed.
*
* @return true if the store has been closed
*/
protected boolean isClosed()
{
return (m_nState & STORE_STATE_CLOSED) != 0;
}
/**
* Set the state of this store.
*
* @param nState the state the store should be transitioned to
*/
protected void setState(int nState)
{
if (nState != m_nState)
{
synchronized (this)
{
m_nState = nState;
notifyAll();
}
}
}
/**
* Return true if this store is in exclusive mode.
*
* @return true if this store is in exclusive mode
*/
protected boolean isExclusive()
{
return ((WriteLock) f_lock.writeLock()).isHeldByCurrentThread();
}
/**
* Return an {@link AutoCloseable} that will transition this PersistentStore
* out of exclusive mode.
*
* @return an AutoCloseable that will transition this PersistentStore
* out of exclusive mode
*/
protected AutoCloseable instantiateExclusiveClosable()
{
return this::unlockWrite;
}
// ----- Object methods ---------------------------------------------
/**
* Return a human readable description of this AbstractPersistentStore.
*
* @return a human readable description
*/
@Override
public String toString()
{
return ClassHelper.getSimpleName(getClass()) + '(' + f_sId + ", "
+ f_dirStore + ")";
}
// ----- helper methods ---------------------------------------------
/**
* Return a PersistenceException with the given cause. The returned
* exception is also initialized with this store, its manager, and
* its environment (if available).
*
* @param eCause an optional cause
*
* @return a PersistenceException with the given cause
*/
protected PersistenceException ensurePersistenceException(Throwable eCause)
{
return ensurePersistenceException(eCause, null /*sMessage*/);
}
/**
* Return a PersistenceException with the given cause and detail
* message. The returned exception is also initialized with this
* store, its manager, and its environment (if available).
*
* @param eCause an optional cause
* @param sMessage an optional detail message
*
* @return a PersistenceException with the given cause and detail message
*/
protected PersistenceException ensurePersistenceException(Throwable eCause, String sMessage)
{
PersistenceException e = AbstractPersistenceManager.this.ensurePersistenceException(eCause, sMessage);
e.initPersistentStore(this);
return e;
}
/**
* Acquire an exclusive lock on the data directory underlying this
* persistent store.
*
* @return true if an exclusive lock was obtained, false otherwise
*/
protected final boolean lockStorage()
{
FileLock lock = m_lockFile;
if (lock == null)
{
m_lockFile = lock = FileHelper.lockFile(f_fileLock);
}
return lock != null;
}
/**
* Release an exclusive lock on the data directory underlying this
* persistent store.
*/
protected final void unlockStorage()
{
FileLock lock = m_lockFile;
if (lock != null)
{
FileHelper.unlockFile(lock);
m_lockFile = null;
}
}
/**
* Acquire a read lock on this persistent store.
*/
protected final void lockRead()
{
f_lock.readLock().lock();
}
/**
* Release a read lock on this persistent store.
*/
protected final void unlockRead()
{
f_lock.readLock().unlock();
}
/**
* Acquire a write lock on this persistent store.
*/
protected final void lockWrite()
{
f_lock.writeLock().lock();
}
/**
* Release a write lock on this persistent store.
*/
protected final void unlockWrite()
{
f_lock.writeLock().unlock();
}
/**
* Validate the given extent identifier.
*
* @param lExtentId the extent identifier
*/
protected void validateExtentId(long lExtentId)
{
Long LId = Long.valueOf(lExtentId);
// validate that the given extent identifier is known
if (!f_setExtentIds.contains(LId))
{
throw new IllegalArgumentException("unknown extent identifier: " + lExtentId + " for store: " + getId());
}
}
/**
* Validate the metadata
*/
protected void validateMetadata()
{
try
{
Properties prop = AbstractPersistenceManager.this.readMetadata(f_dirStore);
if (!AbstractPersistenceManager.this.isMetadataComplete(prop))
{
throw ensurePersistenceException(new FatalAccessException(
"the data in directory \"" + f_dirStore + "\" appears to be incomplete"));
}
if (!AbstractPersistenceManager.this.isMetadataCompatible(prop))
{
throw ensurePersistenceException(new FatalAccessException(
"the data in directory \"" + f_dirStore + "\" is incompatible with this manager"));
}
}
catch (IOException e)
{
throw ensurePersistenceException(new FatalAccessException(
"error reading metadata in directory \"" + f_dirStore + '"', e));
}
}
/**
* Ensure the provided extent id has been registered and created, thus
* allowing subsequent load and store operations against the same extent
* id.
*
* Note: the caller is assumed to have exclusive access to this store.
*
* @param lExtentId the extent id to register and create
*
* @return true if the extent id was not previously registered and was
* successfully created
*/
protected boolean ensureExtentInternal(long lExtentId)
{
Long LId = Long.valueOf(lExtentId);
if (!f_setExtentIds.contains(LId))
{
// make sure the extent isn't in the process of being
// deleted
if (f_setDeletedIds.contains(LId))
{
throw new IllegalArgumentException("deleted extent identifier: " + lExtentId);
}
// add the identifier to the set of known extents
if (f_setExtentIds.add(LId))
{
// create the extent
createExtentInternal(lExtentId);
}
return true;
}
return false;
}
/**
* Copy the provided store to ensure both the contents are available
* in the new store and it is open thus ready to receive requests.
*
* Note: overriders of this method must guarantee {@link #openInternal()}
* is called by either delegating to super or calling it directly.
*
* @param storeFrom the store to copy from
*/
protected void copyAndOpenInternal(PersistentStore storeFrom)
{
openInternal();
if (storeFrom != null)
{
((AbstractPersistentStore) storeFrom).validateMetadata();
final Object oToken = beginInternal();
try
{
for (long lExtentId : storeFrom.extents())
{
ensureExtentInternal(lExtentId);
}
storeFrom.iterate((lExtentId, bufKey, bufValue) ->
{
storeInternal(lExtentId, bufKey, bufValue, oToken);
return true;
});
}
catch (PersistenceException e)
{
abortInternal(oToken);
delete(false);
throw e;
}
commitInternal(oToken);
}
}
/**
* Open the underlying persistent storage.
*
* Note: this method is guaranteed to only be called by a thread that
* holds a write lock on this persistent store.
*
* @throws PersistenceException if a general persistence error occurs
*/
protected abstract void openInternal();
/**
* Release the underlying persistent storage.
*
* Note: this method is guaranteed to only be called by a thread that
* holds a write lock on this persistent store.
*
* @throws PersistenceException if a general persistence error occurs
*/
protected abstract void releaseInternal();
/**
* Remove the underlying persistent storage.
*
* Note: this method is guaranteed to only be called by a thread that
* holds a write lock on this persistent store.
*
* @return {@code true} on successful removal
*
* @throws PersistenceException if a general persistence error occurs
*/
protected abstract boolean deleteInternal();
/**
* Populate the given set with the identifiers of extents in the
* underlying persistent storage.
*
* Note: this method is guaranteed to only be called by a thread that
* holds a write lock on this persistent store.
*
* @param setIds a set of ids
*
* @throws PersistenceException if a general persistence error occurs
*/
protected abstract void loadExtentIdsInternal(Set setIds);
/**
* Create the extent with the given identifier in the persistent
* store.
*
* Note: this method is guaranteed to only be called by a thread that
* holds a write lock on this persistent store.
*
* @param lExtentId the identifier of the extent to create
*
* @throws PersistenceException if a general persistence error occurs
*/
protected abstract void createExtentInternal(long lExtentId);
/**
* Delete the specified extent from the persistent store.
*
* Note: this method is guaranteed to only be called by a thread that
* holds a write lock on this persistent store.
*
* @param lExtentId the identifier of the extent to delete
*
* @throws PersistenceException if a general persistence error occurs
*/
protected abstract void deleteExtentInternal(long lExtentId);
/**
* Move the specified extent from the old extent id to the new extent id.
*
* Note: this method is guaranteed to only be called by a thread that
* holds a write lock on this persistent store.
*
* @param lOldExtentId the old extent identifier
* @param lNewExtentId the new extent identifier
*
* @throws PersistenceException if a general persistence error occurs
*/
protected void moveExtentInternal(long lOldExtentId, long lNewExtentId)
{
final Object oToken = begin();
try
{
iterate((lExtentId, bufKey, bufValue) ->
{
if (lExtentId == lOldExtentId)
{
store(lNewExtentId, bufKey, bufValue, oToken);
}
return true;
});
}
catch (PersistenceException e)
{
abort(oToken);
throw e;
}
commit(oToken);
}
/**
* Truncate the specified extent from the persistent store.
*
* Note: this method is guaranteed to only be called by a thread that
* holds a write lock on this persistent store.
*
* @param lExtentId the identifier of the extent to truncate
*
* @throws PersistenceException if a general persistence error occurs
*/
protected abstract void truncateExtentInternal(long lExtentId);
/**
* Load and return the value associated with the specified key from
* the underlying persistent storage.
*
* Note: this method is guaranteed to only be called by a thread that
* holds a read lock on this persistent store.
*
* @param lExtentId the extent identifier for the key
* @param bufKey key whose associated value is to be returned
*
* @return the value associated with the specified key, or null
* if no value is available for that key
*
* @throws PersistenceException if a general persistence error occurs
*/
protected abstract ReadBuffer loadInternal(long lExtentId, ReadBuffer bufKey);
/**
* Store the specified value under the specific key in the underlying
* persistent storage.
*
* Note: this method is guaranteed to only be called by a thread that
* holds a read lock on this persistent store.
*
* @param lExtentId the extent identifier for the key
* @param bufKey key to store the value under
* @param bufValue value to be stored
* @param oToken a token that represents an atomic unit to commit
*
* @throws PersistenceException if a general persistence error occurs
*
* @throws IllegalArgumentException if the token is invalid
*/
protected abstract void storeInternal(long lExtentId, ReadBuffer bufKey, ReadBuffer bufValue, Object oToken);
/**
* Remove the specified key from the underlying persistent storage
* if present.
*
* Note: this method is guaranteed to only be called by a thread that
* holds a read lock on this persistent store.
*
* @param lExtentId the extent identifier for the key
* @param bufKey key whose mapping is to be removed from the map
* @param oToken a token that represents an atomic unit to commit
*
* @throws PersistenceException if a general persistence error occurs
*
* @throws IllegalArgumentException if the token is invalid
*/
protected abstract void eraseInternal(long lExtentId, ReadBuffer bufKey, Object oToken);
/**
* Iterate the key-value pairs in the underlying persistent storage,
* applying the specified visitor to each key-value pair.
*
* Note: this method is guaranteed to only be called by a thread that
* holds a read lock on this persistent store.
*
* @param visitor the visitor to apply
*
* @throws PersistenceException if a general persistence error occurs
*/
protected abstract void iterateInternal(Visitor visitor);
/**
* Begin a sequence of mutating operations that should be committed
* atomically and return a token that represents the atomic unit.
*
* Note: this method is guaranteed to only be called by a thread that
* holds a read lock on this persistent store.
*
* @return a token that represents the atomic unit to commit
*
* @throws PersistenceException if a general persistence error occurs
*/
protected abstract Object beginInternal();
/**
* Commit a sequence of mutating operations represented by the given
* token as an atomic unit.
*
* Note: this method is guaranteed to only be called by a thread that
* holds a read lock on this persistent store.
*
* @param oToken a token that represents the atomic unit to commit
*
* @throws PersistenceException if a general persistence error occurs
*
* @throws IllegalArgumentException if the token is invalid
*/
protected abstract void commitInternal(Object oToken);
/**
* Abort an atomic sequence of mutating operations.
*
* Note: this method is guaranteed to only be called by a thread that
* holds a read lock on this persistent store.
*
* @param oToken a token that represents the atomic unit to abort
*
* @throws PersistenceException if a general persistence error occurs
*
* @throws IllegalArgumentException if the token is invalid
*/
protected abstract void abortInternal(Object oToken);
// ----- inner class: OpenTask --------------------------------------
/**
* An OpenTask opens the store (parent of this inner class) with the
* provided store and notifies the Collector when complete.
*/
protected class OpenTask
extends Task
implements KeyAssociation
{
// ----- constructors -------------------------------------------
/**
* Construct an OpenTask.
*
* @param storeFrom store to open from
* @param collector collector to notify when the open completes
*/
public OpenTask(PersistentStore storeFrom, Collector collector)
{
f_storeFrom = storeFrom;
f_collector = collector;
}
// ----- KeyAssociation methods ---------------------------------
@Override
public Object getAssociatedKey()
{
// the intent is to run under the same association as the store
// we are copying from; this allows the open implementation to
// assume there is no concurrent access to the store - see copyAndOpenInternal
return f_storeFrom == null
? AbstractPersistentStore.this.getId() : f_storeFrom.getId();
}
// ----- Task methods -------------------------------------------
@Override
public void execute()
{
AbstractPersistentStore store = AbstractPersistentStore.this;
PersistenceException eFailure = null;
try
{
boolean fNewStore = store.open(f_storeFrom);
if (fNewStore)
{
Logger.info("Created persistent store " + FileHelper.getPath(store.f_dirStore)
+ (f_storeFrom == null ? "" : " from " + f_storeFrom));
}
}
catch (PersistenceException e)
{
if (f_collector == null)
{
close(store.getId());
throw e;
}
eFailure = store.ensurePersistenceException(new AsyncPersistenceException("Error in opening store", e)
.initReceipt(store.getId()));
}
finally
{
if (f_collector != null)
{
if (eFailure == null)
{
f_collector.add(store.getId());
}
else
{
close(store.getId());
f_collector.add(eFailure);
}
}
}
}
// ----- data members -------------------------------------------
/**
* The {@link PersistentStore} to open with. The contents of this
* store are copied to the store being opened.
*/
protected final PersistentStore f_storeFrom;
/**
* The {@link Collector} to notify upon completion of opening the
* store.
*/
protected final Collector f_collector;
}
// ----- inner class: DeleteExtentTask ------------------------------
/**
* A Task implementation that deletes an extent from the associated
* store.
*/
protected class DeleteExtentTask
extends Task
implements KeyAssociation
{
// ----- constructors -------------------------------------------
/**
* Construct a DeleteExtentTask with the provided extent id.
*
* @param LExtentId the extent to delete
*/
public DeleteExtentTask(Long LExtentId)
{
f_LExtentId = LExtentId;
}
// ----- KeyAssociation methods ---------------------------------
@Override
public Object getAssociatedKey()
{
return AbstractPersistentStore.this.getId();
}
// ----- Task methods -------------------------------------------
@Override
public void execute()
{
AbstractPersistentStore store = AbstractPersistentStore.this;
store.lockWrite();
try
{
if (store.f_setDeletedIds.remove(f_LExtentId))
{
store.deleteExtentInternal(f_LExtentId.longValue());
synchronized (store.f_setDeletedIds)
{
store.f_setDeletedIds.notifyAll();
}
}
}
finally
{
store.unlockWrite();
}
}
// ----- data members -------------------------------------------
/**
* The extent to delete.
*/
protected final Long f_LExtentId;
}
// ----- inner class: BatchTask -------------------------------------
/**
* Runnable implementation that is used to perform and commit a
* sequence of mutating persistent store operations asynchronously.
*/
protected class BatchTask
extends Task
implements KeyAssociation
{
// ----- constructors -------------------------------------------
/**
* Create a new BatchTask.
*
* @param oToken a token that represents the atomic unit to commit
* @param collector an optional Collector to notify
* @param oReceipt the receipt to add to the Collector after the
* unit is committed
*/
public BatchTask(Object oToken, Collector collector, Object oReceipt)
{
f_oToken = oToken;
f_collector = collector;
f_oReceipt = oReceipt;
}
// ----- BatchTask methods --------------------------------------
/**
* Queue a store operation.
*
* @param lExtentId the extent identifier for the key
* @param bufKey key to store the value under
* @param bufValue value to be stored
*/
public void store(long lExtentId, ReadBuffer bufKey, ReadBuffer bufValue)
{
f_listOps.add(new StoreOperation(lExtentId, bufKey, bufValue));
}
/**
* Queue an erase operation.
*
* @param lExtentId the extent identifier for the key
* @param bufKey key whose mapping is to be removed
*/
public void erase(long lExtentId, ReadBuffer bufKey)
{
f_listOps.add(new EraseOperation(lExtentId, bufKey));
}
/**
* Abort all changes that have been made to the persistent store
* by this BatchTask.
*
* @param eCause optional cause for the abort
*/
public void abort(Throwable eCause)
{
Object oReceipt = f_oReceipt;
try
{
AbstractPersistentStore.this.abort(f_oToken);
// notify the collector with an AsyncPersistenceException
AsyncPersistenceException eAsync = new AsyncPersistenceException(
"\"transaction aborted: \"" + f_oToken, eCause)
.initReceipt(f_oReceipt);
oReceipt = ensurePersistenceException(eAsync);
}
finally
{
notifyCollector(oReceipt, true);
}
}
// ----- Task interface -----------------------------------------
/**
* Execute all queued operations and commit changes.
*/
public void execute()
{
AbstractPersistentStore store = AbstractPersistentStore.this;
try
{
// BatchTask execution will exclusively run either store or erase
// operations (both require a read lock), therefore a read lock
// is acquired upfront and not released until all ops have completed;
// this will prevent other threads from acquiring a read or
// write lock (partition transfer) in-between ops
store.lockRead();
try
{
// execute queued operations
for (Operation op : f_listOps)
{
op.run();
}
// commit the changes to the persistent store
AbstractPersistentStore.this.commit(f_oToken);
}
finally
{
store.unlockRead();
}
// Note: notify the Collector without holding any locks
notifyCollector(f_oReceipt, true);
}
catch (Throwable e)
{
// abort changes
abort(e);
}
}
/**
* {@inheritDoc}
*/
@Override
public void notifyCanceled(Throwable eCause)
{
abort(eCause);
}
// ----- KeyAssociation interface -------------------------------
/**
* {@inheritDoc}
*/
@Override
public Object getAssociatedKey()
{
return AbstractPersistentStore.this.getId();
}
// ----- inner class: Operation ---------------------------------
/**
* Base class for Runnable implementations that encapsulate a
* persistent store operation.
*/
protected abstract class Operation
extends Base
implements Runnable
{
// ----- constructors ---------------------------------------
/**
* Create a new Operation.
*
* @param lExtentId extent identifier for the target key
* @param bufKey target key of the operation
*/
public Operation(long lExtentId, ReadBuffer bufKey)
{
f_lExtentId = lExtentId;
f_bufKey = bufKey;
}
// ----- data members ---------------------------------------
/**
* The extent identifier for the target key.
*/
protected final long f_lExtentId;
/**
* The target key of the operation.
*/
protected final ReadBuffer f_bufKey;
}
// ----- inner class: EraseOperation ----------------------------
/**
* An erase() Operation.
*/
protected class EraseOperation
extends Operation
{
// ----- constructors ---------------------------------------
/**
* Create a new EraseOperation.
*
* @param lExtentId extent identifier for the target key
* @param bufKey key to erase
*/
public EraseOperation(long lExtentId, ReadBuffer bufKey)
{
super(lExtentId, bufKey);
}
// ----- Runnable interface ---------------------------------
/**
* Perform the erase operation.
*/
public void run()
{
AbstractPersistentStore.this.erase(f_lExtentId, f_bufKey,
BatchTask.this.f_oToken);
}
}
// ----- inner class: StoreOperation ----------------------------
/**
* A store() Operation.
*/
protected class StoreOperation
extends Operation
{
// ----- constructors ---------------------------------------
/**
* Create a new StoreOperation.
*
* @param lExtentId extent identifier for the target key
* @param bufKey target key
* @param bufValue value to store
*/
public StoreOperation(long lExtentId, ReadBuffer bufKey, ReadBuffer bufValue)
{
super(lExtentId, bufKey);
f_bufValue = bufValue;
}
// ----- Runnable interface ---------------------------------
/**
* Perform the erase operation.
*/
public void run()
{
AbstractPersistentStore.this.store(f_lExtentId, f_bufKey,
f_bufValue, BatchTask.this.f_oToken);
}
// ----- data members ---------------------------------------
/**
* The value to store.
*/
protected final ReadBuffer f_bufValue;
}
// ----- helper methods -----------------------------------------
/**
* Add the given object to the configured collector (if any). If
* the add operation throws an exception, it will be caught and
* logged.
*
* @param oItem the item to add
* @param fFlush if true, the collector will be flushed after
* adding the item
*/
protected void notifyCollector(Object oItem, boolean fFlush)
{
if (f_collector != null)
{
try
{
f_collector.add(oItem);
if (fFlush)
{
f_collector.flush();
}
}
catch (Throwable e)
{
Logger.err("Error adding an item to collector \"" + f_collector + "\":", e);
}
}
}
// ----- data members -------------------------------------------
/**
* A token representing the atomic unit that will be committed
* asynchronously.
*/
protected final Object f_oToken;
/**
* An optional Collector to add notifications to.
*/
protected final Collector f_collector;
/**
* The receipt to add to the Collector after the unit is committed.
*/
protected final Object f_oReceipt;
/**
* The sequence of operations to commit atomically.
*/
protected final List f_listOps = new ArrayList<>();
}
// ----- accessors --------------------------------------------------
/**
* The directory used to store persisted data.
*
* @return the underlying data storage directory
*/
public File getDataDirectory()
{
return f_dirStore;
}
// ----- data members -----------------------------------------------
/**
* The identifier of this persistent store.
*/
protected final String f_sId;
/**
* The directory used to store persisted data.
*/
protected final File f_dirStore;
/**
* The file used to prevent concurrent access to the data directory
* underlying this persistent store.
*/
protected final File f_fileLock;
/**
* The state of the PersistenceStore.
*/
protected volatile int m_nState;
/**
* The FileLock used to prevent concurrent access to the data
* directory underlying this persistent store.
*/
protected FileLock m_lockFile;
/**
* The ReadWriteLock used to protect against concurrent read/write
* operations.
*/
protected final ReadWriteLock f_lock = new ReentrantReadWriteLock();
/**
* The set of valid extent identifiers known to this persistent store.
*/
protected final Set f_setExtentIds = new CopyOnWriteArraySet<>();
/**
* The set of extent identifiers that are in the process of being
* deleted.
*/
protected final Set f_setDeletedIds = new CopyOnWriteArraySet<>();
}
// ----- inner class: BatchTasks ----------------------------------------
/**
* A collection of tasks to execute in a loop.
*/
protected static class BatchTasks
implements Runnable, KeyAssociation
{
// ----- constructors -----------------------------------------------
/**
* Construct a BatchTasks instance with the given list of tasks to
* execute.
*
* @param listTasks the tasks to execute
*/
protected BatchTasks(List listTasks)
{
f_listTasks = listTasks;
f_oAssociation = listTasks.isEmpty()
? null : ((KeyAssociation) listTasks.iterator().next()).getAssociatedKey();
}
// ----- Runnable methods -------------------------------------------
@Override
public void run()
{
for (Runnable task : f_listTasks)
{
task.run();
}
}
// ----- KeyAssociation methods -------------------------------------
@Override
public Object getAssociatedKey()
{
return f_oAssociation;
}
// ----- data members ---------------------------------------------------
/**
* List of tasks to execute.
*/
protected final List f_listTasks;
/**
* Association for this BatchTasks.
*/
protected final Object f_oAssociation;
}
// ----- inner class: AbstractPersistenceSnapshotTools ------------------
/**
* Abstract implementation of PersistenceTools which can be extended to
* support local snapshot operations for specific implementations.
*
* @author tam/hr 2014.11.21
* @since 12.2.1
*/
protected abstract class AbstractPersistenceSnapshotTools
extends AbstractPersistenceTools
{
// ----- constructors -----------------------------------------------
/**
* Construct an abstract implementation for a given snapshot directory.
*
* @param dirSnapshot the directory where the snapshot is
* @param info the information collected regarding the snapshot
*/
public AbstractPersistenceSnapshotTools(File dirSnapshot, OfflinePersistenceInfo info)
{
super(info);
f_dirSnapshot = dirSnapshot;
}
// ----- PersistenceTools methods -----------------------------------
/**
* Get the {@link PersistenceStatistics} for a local snapshot by using the
* implementation manager and visiting the store.
*
* @return the PersistenceStatistics for a local snapshot
*/
@Override
public PersistenceStatistics getStatistics()
{
String[] asFileList = f_info.getGUIDs();
String sCurrentGUID = null;
PersistenceStatistics stats = new PersistenceStatistics();
StatsVisitor visitor = new StatsVisitor(stats);
PersistentStore store;
for (int i = 0; i < asFileList.length; i++)
{
try
{
sCurrentGUID = asFileList[i];
store = AbstractPersistenceManager.this.open(sCurrentGUID, null);
validateStoreSealed(store);
visitor.setCaches(CachePersistenceHelper.getCacheNames(store));
store.iterate(CachePersistenceHelper.instantiatePersistenceVisitor(visitor));
}
finally
{
AbstractPersistenceManager.this.close(sCurrentGUID);
}
}
return stats;
}
// ----- helpers ----------------------------------------------------
/**
* Validate the given store within the GUID is sealed. Note: The store is
* opened and closed during this method call.
*
* @param sCurrentGUID the GUID to open store from
*
* @throws PersistenceException if the store is not sealed
*/
protected void validateStoreSealed(String sCurrentGUID)
{
PersistentStore store;
try
{
store = AbstractPersistenceManager.this.open(sCurrentGUID, null);
validateStoreSealed(store);
}
finally
{
AbstractPersistenceManager.this.close(sCurrentGUID);
}
}
/**
* Validate the given store is sealed.
*
* @param store the persistent store to validate
*
* @throws PersistenceException if the store is not sealed
*/
protected void validateStoreSealed(PersistentStore store)
{
if (!CachePersistenceHelper.isSealed(store))
{
throw CachePersistenceHelper.ensurePersistenceException(
new IllegalStateException("Store " + store.getId() +
" was not sealed correctly"));
}
}
// ----- data members --------------------------------------------
/**
* The snapshot directory.
*/
protected final File f_dirSnapshot;
}
// ----- accessors ------------------------------------------------------
/**
* Return the optional DaemonPool used to execute tasks.
*
* @return the DaemonPool or null if one hasn't been configured
*/
public DaemonPool getDaemonPool()
{
return m_pool;
}
/**
* Configure the DaemonPool used to execute tasks.
*
* @param pool the DaemonPool
*/
public void setDaemonPool(DaemonPool pool)
{
m_pool = pool;
}
/**
* Return the directory used to store persisted data.
*
* @return the data storage directory
*/
public File getDataDirectory()
{
return f_dirData;
}
/**
* Return the directory used to store "safe-deleted" persisted data.
*
* @return the trash storage directory
*/
public File getTrashDirectory()
{
return f_dirTrash;
}
/**
* Return the directory used to store lock files.
*
* @return the directory used to store lock files
*/
protected File getLockDirectory()
{
try
{
return FileHelper.ensureDir(f_dirLock);
}
catch (IOException ignore) {}
return f_dirLock;
}
/**
* Return the environment that created this manager.
*
* @return the environment that created this manager
*/
protected AbstractPersistenceEnvironment getPersistenceEnvironment()
{
return m_env;
}
/**
* Configure the environment that created this manager.
*
* @param env the environment that created this manager
*/
protected void setPersistenceEnvironment(AbstractPersistenceEnvironment env)
{
m_env = env;
if (env == null)
{
setDaemonPool(null);
}
else
{
setDaemonPool(env.getDaemonPool());
}
}
/**
* Return the map of open PersistentStore instances keyed by their identifiers.
*
* Note: The return map is "live". Any attempt to access or mutate it
* should be done while holding a monitor on this manager.
*
* @return the map of open PersistentStore instances
*/
public Map getPersistentStoreMap()
{
return f_mapStores;
}
// ----- constants ------------------------------------------------------
/**
* An empty long array (by definition immutable).
*/
protected static final long[] NO_LONGS = new long[0];
/**
* An empty String array (by definition immutable).
*/
protected static final String[] NO_STRINGS = AbstractPersistenceEnvironment.NO_STRINGS;
/**
* Magic header.
*/
private static final int MAGIC = 0x6A683735;
/**
* Serialization version.
*/
private static final int VERSION = 0;
// ----- store constants ------------------------------------------------
/**
* The initial state of a PersistenceStore.
*/
protected static final int STORE_STATE_INITIALIZED = 0;
/**
* The state of a PersistenceStore when it is in the process of being opened.
*/
protected static final int STORE_STATE_OPENING = 1;
/**
* The state of a PersistenceStore once it has been opened and is ready
* to process requests.
*/
protected static final int STORE_STATE_READY = 2;
/**
* The state of a PersistenceStore once it has been released and closed.
*/
protected static final int STORE_STATE_CLOSED = 4;
// ----- data members ---------------------------------------------------
/**
* The directory used to store persisted data.
*/
protected final File f_dirData;
/**
* The directory used to store "safe-deleted" data.
*/
protected final File f_dirTrash;
/**
* The directory used to store lock files (to protect against multi-process
* file system clean up).
*/
protected final File f_dirLock;
/**
* The name of this AbstractPersistenceManager.
*/
protected final String f_sName;
/**
* Map of open AbstractPersistentStore instances.
*/
protected final ConcurrentMap f_mapStores = new ConcurrentHashMap<>();
/**
* Set of outstanding tasks.
*/
protected final Set f_setTasks = new HashSet<>();
/**
* Whether this PersistenceManager has been released.
*/
protected volatile boolean m_fReleased;
/**
* The environment that created this AbstractPersistenceManager.
*/
protected AbstractPersistenceEnvironment m_env;
/**
* An optional DaemonPool used to execute tasks.
*/
protected DaemonPool m_pool;
}