Permalink
Browse files

MODE-1822 Changed how transactional workspace caching is done

I identified a number of issues when working on this problem. The
previous change to use a separate workspace cache for each transaction
was not working correctly, because changes persisted (but not committed)
in one SessionCache (e.g., the normal cache used within a JCR Session)
would not be visible to other SessionCache instances (e.g., the one
used by the JCR Session's version manager). Plus, because the workspace
caches are specific to a single named workspace (including the hidden
"system" workspace), changes in one workspace within one transaction
were not visible to the other components participating in that transaction.

A TransactionalWorkspaceCaches class was created to manage the
TransactionalWorkspaceCache instances used by the various session
(and SessionCache instances) participating in a transaction. It keeps
track of a single TransactionalWorkspaceCache for each named workspace
used within each transaction. Additionally, all TransactionalWorkspaceCache
instances within a transaction are thus notified when changes are persisted
but not yet committed. As soon as the transaction is committed, the
TransactionalWorkspaceCache for each workspace used in the transaction
are all cleared, and the AbstractSessionCache instances are notified
and changed. There also was additional wiring so that the
AbstractSessionCache used by a JCR Session are all notified of any
changes to other (temporary) SessionCaches.

Hopefully all of this infrastructure has very little effect on JCR Sessions
that are not used within user transactions. About the only effect is that
the JCR Session will check for a currently-running user transaction (which
should be fast) upon save() and some workspace-write operations;
otherwise, there should be no effect. Any time a JCR Session is created
within a user transaction or used to save within a user transaction, that
session knows about the transaction and uses the transactional workspace
cache infrastructure; only when a session exists prior to the start of a
user transaction does the checking for a current user transaction need
to be done.

Several of the tests in TransactionalTests were modified, and several
were added. Several other tests that started failing were examined, found
to be incorrect, and then corrected.
  • Loading branch information...
1 parent 9898e68 commit ac7272530eef2256bad9a382bb87d5e4ea3c9f91 @rhauch rhauch committed Mar 7, 2013
Showing with 680 additions and 34 deletions.
  1. +1 −0 modeshape-jcr/src/main/java/org/modeshape/jcr/JcrI18n.java
  2. +8 −0 modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java
  3. +43 −2 modeshape-jcr/src/main/java/org/modeshape/jcr/JcrSession.java
  4. +8 −7 modeshape-jcr/src/main/java/org/modeshape/jcr/ModeShapeFederationManager.java
  5. +7 −0 modeshape-jcr/src/main/java/org/modeshape/jcr/cache/NodeCache.java
  6. +1 −0 modeshape-jcr/src/main/java/org/modeshape/jcr/cache/RepositoryCache.java
  7. +9 −0 modeshape-jcr/src/main/java/org/modeshape/jcr/cache/SessionCache.java
  8. +182 −0 modeshape-jcr/src/main/java/org/modeshape/jcr/cache/SessionCacheWrapper.java
  9. +8 −0 modeshape-jcr/src/main/java/org/modeshape/jcr/cache/SessionEnvironment.java
  10. +60 −6 modeshape-jcr/src/main/java/org/modeshape/jcr/cache/document/AbstractSessionCache.java
  11. +1 −1 modeshape-jcr/src/main/java/org/modeshape/jcr/cache/document/LazyCachedNode.java
  12. +3 −3 modeshape-jcr/src/main/java/org/modeshape/jcr/cache/document/SessionNode.java
  13. +6 −1 modeshape-jcr/src/main/java/org/modeshape/jcr/cache/document/TransactionalWorkspaceCache.java
  14. +130 −0 modeshape-jcr/src/main/java/org/modeshape/jcr/cache/document/TransactionalWorkspaceCaches.java
  15. +5 −0 modeshape-jcr/src/main/java/org/modeshape/jcr/cache/document/WorkspaceCache.java
  16. +4 −4 modeshape-jcr/src/main/java/org/modeshape/jcr/cache/document/WritableSessionCache.java
  17. +6 −0 modeshape-jcr/src/main/java/org/modeshape/jcr/txn/SynchronizedTransactions.java
  18. +9 −0 modeshape-jcr/src/main/java/org/modeshape/jcr/txn/Transactions.java
  19. +1 −0 modeshape-jcr/src/main/resources/org/modeshape/jcr/JcrI18n.properties
  20. +3 −3 modeshape-jcr/src/test/java/org/modeshape/jcr/JcrRepositoryTest.java
  21. +33 −0 modeshape-jcr/src/test/java/org/modeshape/jcr/ModeShapeTckTest.java
  22. +133 −6 modeshape-jcr/src/test/java/org/modeshape/jcr/TransactionsTest.java
  23. +10 −1 modeshape-jcr/src/test/java/org/modeshape/jcr/cache/document/AbstractSessionCacheTest.java
  24. +7 −0 modeshape-jcr/src/test/java/org/modeshape/jcr/cache/document/ReadOnlySessionCacheTest.java
  25. +2 −0 modeshape-jcr/src/test/resources/log4j.properties
@@ -404,6 +404,7 @@
public static I18n failedWhileRollingBackDestroyToRuntimeError;
public static I18n unexpectedException;
public static I18n errorDeterminingCurrentTransactionAssumingNone;
+ public static I18n errorWhileRollingBackActiveTransactionUsingWorkspaceThatIsBeingDeleted;
public static I18n configurationError;
public static I18n configurationWarning;
@@ -121,6 +121,7 @@
import org.modeshape.jcr.cache.change.WorkspaceRemoved;
import org.modeshape.jcr.cache.document.DocumentStore;
import org.modeshape.jcr.cache.document.LocalDocumentStore;
+import org.modeshape.jcr.cache.document.TransactionalWorkspaceCaches;
import org.modeshape.jcr.federation.FederatedDocumentStore;
import org.modeshape.jcr.mimetype.MimeTypeDetector;
import org.modeshape.jcr.mimetype.MimeTypeDetectors;
@@ -1746,15 +1747,22 @@ protected ChangeBus createBus( RepositoryConfiguration.Clustering clusteringConf
protected static class RepositorySessionEnvironment implements SessionEnvironment {
private final Transactions transactions;
+ private final TransactionalWorkspaceCaches transactionalWorkspaceCacheFactory;
protected RepositorySessionEnvironment( Transactions transactions ) {
this.transactions = transactions;
+ this.transactionalWorkspaceCacheFactory = new TransactionalWorkspaceCaches(transactions);
}
@Override
public Transactions getTransactions() {
return transactions;
}
+
+ @Override
+ public TransactionalWorkspaceCaches getTransactionalWorkspaceCacheFactory() {
+ return transactionalWorkspaceCacheFactory;
+ }
}
protected static class RepositoryMonitorFactory implements MonitorFactory {
@@ -85,6 +85,7 @@
import org.modeshape.jcr.cache.RepositoryCache;
import org.modeshape.jcr.cache.SessionCache;
import org.modeshape.jcr.cache.SessionCache.SaveContext;
+import org.modeshape.jcr.cache.SessionCacheWrapper;
import org.modeshape.jcr.cache.WorkspaceNotFoundException;
import org.modeshape.jcr.cache.WrappedException;
import org.modeshape.jcr.cache.document.WorkspaceCache;
@@ -308,7 +309,11 @@ final SessionCache cache() {
}
final SessionCache createSystemCache( boolean readOnly ) {
- return repository.createSystemSession(context, readOnly);
+ // This method returns a SessionCache used by various Session-owned components that can automatically
+ // save system content. This session should be notified when such activities happen.
+ SessionCache systemCache = repository.createSystemSession(context, readOnly);
+ return readOnly ? systemCache : new SystemSessionCache(systemCache);
+
}
final JcrNodeTypeManager nodeTypeManager() {
@@ -356,7 +361,8 @@ final JcrSession spawnSession( String workspaceName,
}
final SessionCache spawnSessionCache( boolean readOnly ) {
- return repository().repositoryCache().createSession(context(), workspaceName(), readOnly);
+ SessionCache cache = repository().repositoryCache().createSession(context(), workspaceName(), readOnly);
+ return readOnly ? cache : new SystemSessionCache(cache);
}
final void addContextData( String key,
@@ -1912,4 +1918,39 @@ public void processAfterLocking( MutableCachedNode modifiedNode,
}
}
}
+
+ protected class SystemSessionCache extends SessionCacheWrapper {
+ protected SystemSessionCache( SessionCache delegate ) {
+ super(delegate);
+ }
+
+ @Override
+ public void save() {
+ super.save();
+ signalSaveOfSystemChanges();
+ }
+
+ @Override
+ public void save( SessionCache otherSession,
+ PreSave preSaveOperation ) {
+ super.save(otherSession, preSaveOperation);
+ signalSaveOfSystemChanges();
+ }
+
+ @Override
+ public void save( Set<NodeKey> toBeSaved,
+ SessionCache otherSession,
+ PreSave preSaveOperation ) {
+ super.save(toBeSaved, otherSession, preSaveOperation);
+ signalSaveOfSystemChanges();
+ }
+
+ /**
+ * This method can be called by workspace-write methods, which (if a transaction has started after this session was
+ * created) can persist changes (via their SessionCache.save())
+ */
+ private void signalSaveOfSystemChanges() {
+ cache().checkForTransaction();
+ }
+ };
}
@@ -28,12 +28,13 @@
import org.modeshape.common.util.CheckArg;
import org.modeshape.jcr.api.federation.FederationManager;
import org.modeshape.jcr.cache.NodeKey;
+import org.modeshape.jcr.cache.SessionCache;
import org.modeshape.jcr.cache.document.WritableSessionCache;
import org.modeshape.jcr.value.Path;
/**
* Implementation of the {@link FederationManager} interface.
- *
+ *
* @author Horia Chiorean (hchiorea@redhat.com)
*/
public class ModeShapeFederationManager implements FederationManager {
@@ -51,9 +52,9 @@ public void createProjection( String absNodePath,
String alias ) throws RepositoryException {
NodeKey key = session.getNode(absNodePath).key();
- WritableSessionCache internalSession = (WritableSessionCache)session.spawnSessionCache(false);
- internalSession.createProjection(key, sourceName, externalPath, alias);
- internalSession.save();
+ SessionCache writer = session.spawnSessionCache(false);
+ ((WritableSessionCache)writer.unwrap()).createProjection(key, sourceName, externalPath, alias);
+ writer.save();
}
@Override
@@ -68,8 +69,8 @@ public void removeProjection( String projectionPath ) throws RepositoryException
NodeKey federatedNodeKey = session.getNode(path.getParent().getString()).key();
NodeKey externalNodeKey = session.getNode(path.getString()).key();
- WritableSessionCache internalSession = (WritableSessionCache)session.spawnSessionCache(false);
- internalSession.removeProjection(federatedNodeKey, externalNodeKey);
- internalSession.save();
+ SessionCache writer = session.spawnSessionCache(false);
+ ((WritableSessionCache)writer.unwrap()).removeProjection(federatedNodeKey, externalNodeKey);
+ writer.save();
}
}
@@ -77,4 +77,11 @@
* @return an iterator over all the cache's node keys; never null
*/
Iterator<NodeKey> getAllNodeKeysAtAndBelow( NodeKey startingKey );
+
+ /**
+ * Unwrap this instance.
+ *
+ * @return the unwrapped SessionCache instance, or this object if there is no wrapper.
+ */
+ NodeCache unwrap();
}
@@ -668,6 +668,7 @@ void removeWorkspace( String name ) {
if (removed != null) {
try {
removed.signalDeleted();
+ sessionContext.getTransactionalWorkspaceCacheFactory().remove(name);
} finally {
if (workspaceCacheManager instanceof EmbeddedCacheManager) {
((EmbeddedCacheManager)workspaceCacheManager).removeCache(cacheNameForWorkspace(name));
@@ -266,4 +266,13 @@ public void save( SessionCache otherSession,
*/
public NodeKey createNodeKey( String sourceName,
String identifier );
+
+ /**
+ * Check whether this session is running within a transaction. This is commonly called by components that change persistent
+ * state. Such persistent state might not be noticed by this session cache.
+ */
+ public void checkForTransaction();
+
+ @Override
+ public SessionCache unwrap();
}
@@ -0,0 +1,182 @@
+/*
+ * ModeShape (http://www.modeshape.org)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * ModeShape is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.modeshape.jcr.cache;
+
+import java.util.Iterator;
+import java.util.Set;
+import org.modeshape.jcr.ExecutionContext;
+
+/**
+ * A {@link SessionCache} implementation that wraps another and is suitable to extend and overwrite only those methods that are
+ * required.
+ */
+public class SessionCacheWrapper implements SessionCache {
+
+ private final SessionCache delegate;
+
+ public SessionCacheWrapper( SessionCache delegate ) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public SessionCache unwrap() {
+ return delegate.unwrap();
+ }
+
+ @Override
+ public void clear() {
+ delegate.clear();
+ }
+
+ @Override
+ public NodeKey getRootKey() {
+ return delegate.getRootKey();
+ }
+
+ @Override
+ public CachedNode getNode( NodeKey key ) {
+ return delegate.getNode(key);
+ }
+
+ @Override
+ public CachedNode getNode( ChildReference reference ) {
+ return delegate.getNode(reference);
+ }
+
+ @Override
+ public Iterator<NodeKey> getAllNodeKeys() {
+ return delegate.getAllNodeKeys();
+ }
+
+ @Override
+ public Iterator<NodeKey> getAllNodeKeysAtAndBelow( NodeKey startingKey ) {
+ return delegate.getAllNodeKeysAtAndBelow(startingKey);
+ }
+
+ @Override
+ public ExecutionContext getContext() {
+ return delegate.getContext();
+ }
+
+ @Override
+ public void addContextData( String key,
+ String value ) {
+ delegate.addContextData(key, value);
+ }
+
+ @Override
+ public void save() {
+ delegate.save();
+ }
+
+ @Override
+ public void save( Set<NodeKey> toBeSaved,
+ SessionCache otherSession,
+ PreSave preSaveOperation ) {
+ delegate.save(toBeSaved, otherSession, preSaveOperation);
+ }
+
+ @Override
+ public void save( SessionCache otherSession,
+ PreSave preSaveOperation ) {
+ delegate.save(otherSession, preSaveOperation);
+ }
+
+ @Override
+ public boolean hasChanges() {
+ return delegate.hasChanges();
+ }
+
+ @Override
+ public Set<NodeKey> getChangedNodeKeys() {
+ return delegate.getChangedNodeKeys();
+ }
+
+ @Override
+ public Set<NodeKey> getChangedNodeKeysAtOrBelow( CachedNode node ) throws NodeNotFoundException {
+ return delegate.getChangedNodeKeysAtOrBelow(node);
+ }
+
+ @Override
+ public Set<NodeKey> getNodeKeysAtAndBelow( NodeKey nodeKey ) {
+ return delegate.getNodeKeysAtAndBelow(nodeKey);
+ }
+
+ @Override
+ public void clear( CachedNode node ) {
+ delegate.clear(node);
+ }
+
+ @Override
+ public NodeCache getWorkspace() {
+ return delegate.getWorkspace();
+ }
+
+ @Override
+ public MutableCachedNode mutable( NodeKey key ) throws NodeNotFoundException, UnsupportedOperationException {
+ return delegate.mutable(key);
+ }
+
+ @Override
+ public void destroy( NodeKey key ) throws NodeNotFoundException, UnsupportedOperationException {
+ delegate.destroy(key);
+ }
+
+ @Override
+ public boolean isDestroyed( NodeKey key ) {
+ return delegate.isDestroyed(key);
+ }
+
+ @Override
+ public NodeKey createNodeKey() {
+ return delegate.createNodeKey();
+ }
+
+ @Override
+ public NodeKey createNodeKeyWithIdentifier( String identifier ) {
+ return delegate.createNodeKeyWithIdentifier(identifier);
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return delegate.isReadOnly();
+ }
+
+ @Override
+ public NodeKey createNodeKeyWithSource( String sourceName ) {
+ return delegate.createNodeKeyWithSource(sourceName);
+ }
+
+ @Override
+ public NodeKey createNodeKey( String sourceName,
+ String identifier ) {
+ return delegate.createNodeKey(sourceName, identifier);
+ }
+
+ @Override
+ public void checkForTransaction() {
+ delegate.checkForTransaction();
+ }
+
+}
@@ -26,6 +26,7 @@
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
+import org.modeshape.jcr.cache.document.TransactionalWorkspaceCaches;
import org.modeshape.jcr.txn.Transactions;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.Path;
@@ -43,6 +44,13 @@
*/
Transactions getTransactions();
+ /**
+ * Get the factory used to obtain the transactional workspace caches.
+ *
+ * @return the factory; never null
+ */
+ TransactionalWorkspaceCaches getTransactionalWorkspaceCacheFactory();
+
public static interface Monitor {
/**
* Add to the index the information about a node.
Oops, something went wrong.

0 comments on commit ac72725

Please sign in to comment.