/* * Copyright (c) 2002-2009 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.kernel.impl.core; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; import javax.transaction.TransactionManager; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.NotFoundException; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.RelationshipType; import org.neo4j.graphdb.event.TransactionData; import org.neo4j.kernel.impl.cache.AdaptiveCacheManager; import org.neo4j.kernel.impl.cache.Cache; import org.neo4j.kernel.impl.cache.LruCache; import org.neo4j.kernel.impl.cache.SoftLruCache; import org.neo4j.kernel.impl.nioneo.store.PropertyData; import org.neo4j.kernel.impl.nioneo.store.PropertyIndexData; import org.neo4j.kernel.impl.nioneo.store.RelationshipChainPosition; import org.neo4j.kernel.impl.nioneo.store.RelationshipData; import org.neo4j.kernel.impl.nioneo.store.RelationshipTypeData; import org.neo4j.kernel.impl.persistence.IdGenerator; import org.neo4j.kernel.impl.persistence.PersistenceManager; import org.neo4j.kernel.impl.transaction.LockException; import org.neo4j.kernel.impl.transaction.LockManager; import org.neo4j.kernel.impl.transaction.LockType; import org.neo4j.kernel.impl.util.ArrayMap; import org.neo4j.kernel.impl.util.IntArray; public class NodeManager { private static Logger log = Logger.getLogger( NodeManager.class.getName() ); private int referenceNodeId = 0; private final GraphDatabaseService graphDbService; private final Cache<Integer,NodeImpl> nodeCache; private final Cache<Integer,RelationshipImpl> relCache; private final AdaptiveCacheManager cacheManager; private final LockManager lockManager; private final TransactionManager transactionManager; private final LockReleaser lockReleaser; private final PropertyIndexManager propertyIndexManager; private final RelationshipTypeHolder relTypeHolder; private final PersistenceManager persistenceManager; private final IdGenerator idGenerator; private boolean useAdaptiveCache = true; private float adaptiveCacheHeapRatio = 0.77f; private int minNodeCacheSize = 0; private int minRelCacheSize = 0; private int maxNodeCacheSize = 1500; private int maxRelCacheSize = 3500; private static final int LOCK_STRIPE_COUNT = 5; private final ReentrantLock loadLocks[] = new ReentrantLock[LOCK_STRIPE_COUNT]; NodeManager( GraphDatabaseService graphDb, AdaptiveCacheManager cacheManager, LockManager lockManager, LockReleaser lockReleaser, TransactionManager transactionManager, PersistenceManager persistenceManager, IdGenerator idGenerator, boolean useNewCaches ) { this.graphDbService = graphDb; this.cacheManager = cacheManager; this.lockManager = lockManager; this.transactionManager = transactionManager; this.propertyIndexManager = new PropertyIndexManager( transactionManager, persistenceManager, idGenerator ); this.lockReleaser = lockReleaser; lockReleaser.setNodeManager( this ); lockReleaser.setPropertyIndexManager( propertyIndexManager ); this.persistenceManager = persistenceManager; this.idGenerator = idGenerator; this.relTypeHolder = new RelationshipTypeHolder( transactionManager, persistenceManager, idGenerator ); if ( useNewCaches ) { nodeCache = new SoftLruCache<Integer,NodeImpl>( "NodeCache" ); relCache = new SoftLruCache<Integer,RelationshipImpl>( "RelationshipCache" ); } else { nodeCache = new LruCache<Integer,NodeImpl>( "NodeCache", 1500, this.cacheManager ); relCache = new LruCache<Integer,RelationshipImpl>( "RelationshipCache", 3500, this.cacheManager ); } for ( int i = 0; i < loadLocks.length; i++ ) { loadLocks[i] = new ReentrantLock(); } } public GraphDatabaseService getGraphDbService() { return graphDbService; } public boolean isUsingSoftReferenceCache() { return nodeCache instanceof SoftLruCache; } private void parseParams( Map<Object,Object> params ) { if ( params.containsKey( "use_adaptive_cache" ) ) { String value = (String) params.get( "use_adaptive_cache" ); if ( value.toLowerCase().equals( "yes" ) ) { useAdaptiveCache = true; } else if ( value.toLowerCase().equals( "no" ) ) { useAdaptiveCache = false; } else { log.warning( "Unable to parse use_adaptive_cache=" + value ); } } if ( params.containsKey( "adaptive_cache_heap_ratio" ) ) { Object value = params.get( "adaptive_cache_heap_ratio" ); try { adaptiveCacheHeapRatio = Float.parseFloat( (String) value ); } catch ( NumberFormatException e ) { log.warning( "Unable to parse adaptive_cache_heap_ratio " + value ); } if ( adaptiveCacheHeapRatio < 0.1f ) { adaptiveCacheHeapRatio = 0.1f; } if ( adaptiveCacheHeapRatio > 0.95f ) { adaptiveCacheHeapRatio = 0.95f; } } if ( params.containsKey( "min_node_cache_size" ) ) { Object value = params.get( "min_node_cache_size" ); try { minNodeCacheSize = Integer.parseInt( (String) value ); } catch ( NumberFormatException e ) { log.warning( "Unable to parse min_node_cache_size " + value ); } } if ( params.containsKey( "min_relationship_cache_size" ) ) { Object value = params.get( "min_relationship_cache_size" ); try { minRelCacheSize = Integer.parseInt( (String) value ); } catch ( NumberFormatException e ) { log.warning( "Unable to parse min_relationship_cache_size " + value ); } } if ( params.containsKey( "max_node_cache_size" ) ) { Object value = params.get( "max_node_cache_size" ); try { maxNodeCacheSize = Integer.parseInt( (String) value ); } catch ( NumberFormatException e ) { log.warning( "Unable to parse max_node_cache_size " + value ); } } if ( params.containsKey( "max_relationship_cache_size" ) ) { Object value = params.get( "max_relationship_cache_size" ); try { maxRelCacheSize = Integer.parseInt( (String) value ); } catch ( NumberFormatException e ) { log.warning( "Unable to parse max_relationship_cache_size " + value ); } } } public void start( Map<Object,Object> params ) { parseParams( params ); nodeCache.resize( maxNodeCacheSize ); relCache.resize( maxRelCacheSize ); if ( useAdaptiveCache ) { cacheManager.registerCache( nodeCache, adaptiveCacheHeapRatio, minNodeCacheSize ); cacheManager.registerCache( relCache, adaptiveCacheHeapRatio, minRelCacheSize ); cacheManager.start( params ); } } public void stop() { if ( useAdaptiveCache ) { cacheManager.stop(); cacheManager.unregisterCache( nodeCache ); cacheManager.unregisterCache( relCache ); } relTypeHolder.clear(); } public Node createNode() { int id = idGenerator.nextId( Node.class ); NodeImpl node = new NodeImpl( id, true, this ); acquireLock( node, LockType.WRITE ); boolean success = false; try { persistenceManager.nodeCreate( id ); nodeCache.put( (int) node.getId(), node ); success = true; return new NodeProxy( id, this ); } finally { releaseLock( node, LockType.WRITE ); if ( !success ) { setRollbackOnly(); } } } public Relationship createRelationship( Node startNode, Node endNode, RelationshipType type ) { if ( startNode == null || endNode == null || type == null ) { throw new IllegalArgumentException( "Null parameter, startNode=" + startNode + ", endNode=" + endNode + ", type=" + type ); } if ( !relTypeHolder.isValidRelationshipType( type ) ) { relTypeHolder.addValidRelationshipType( type.name(), true ); } int startNodeId = (int) startNode.getId(); NodeImpl firstNode = getLightNode( startNodeId ); if ( firstNode == null ) { setRollbackOnly(); throw new NotFoundException( "First node[" + startNode.getId() + "] deleted" ); } int endNodeId = (int) endNode.getId(); NodeImpl secondNode = getLightNode( endNodeId ); if ( secondNode == null ) { setRollbackOnly(); throw new NotFoundException( "Second node[" + endNode.getId() + "] deleted" ); } int id = idGenerator.nextId( Relationship.class ); RelationshipImpl rel = new RelationshipImpl( id, startNodeId, endNodeId, type, true, this ); boolean firstNodeTaken = false; boolean secondNodeTaken = false; acquireLock( rel, LockType.WRITE ); boolean success = false; try { acquireLock( firstNode, LockType.WRITE ); firstNodeTaken = true; acquireLock( secondNode, LockType.WRITE ); secondNodeTaken = true; int typeId = getRelationshipTypeIdFor( type ); persistenceManager.relationshipCreate( id, typeId, startNodeId, endNodeId ); firstNode.addRelationship( type, id ); secondNode.addRelationship( type, id ); relCache.put( (int) rel.getId(), rel ); success = true; return new RelationshipProxy( id, this ); } finally { boolean releaseFailed = false; if ( firstNodeTaken ) { try { releaseLock( firstNode, LockType.WRITE ); } catch ( Exception e ) { releaseFailed = true; e.printStackTrace(); log.severe( "Failed to release lock" ); } } if ( secondNodeTaken ) { try { releaseLock( secondNode, LockType.WRITE ); } catch ( Exception e ) { releaseFailed = true; e.printStackTrace(); log.severe( "Failed to release lock" ); } } releaseLock( rel, LockType.WRITE ); if ( !success ) { setRollbackOnly(); } if ( releaseFailed ) { throw new LockException( "Unable to release locks [" + startNode + "," + endNode + "] in relationship create->" + rel ); } } } private ReentrantLock lockId( int id ) { int stripe = id % LOCK_STRIPE_COUNT; if ( stripe < 0 ) { stripe *= -1; } ReentrantLock lock = loadLocks[stripe]; lock.lock(); return lock; } public Node getNodeById( int nodeId ) throws NotFoundException { NodeImpl node = nodeCache.get( nodeId ); if ( node != null ) { return new NodeProxy( nodeId, this ); } node = new NodeImpl( nodeId, this ); ReentrantLock loadLock = lockId( nodeId ); try { if ( nodeCache.get( nodeId ) != null ) { return new NodeProxy( nodeId, this ); } if ( !persistenceManager.loadLightNode( nodeId ) ) { throw new NotFoundException( "Node[" + nodeId + "]" ); } nodeCache.put( nodeId, node ); return new NodeProxy( nodeId, this ); } finally { loadLock.unlock(); } } NodeImpl getLightNode( int nodeId ) { NodeImpl node = nodeCache.get( nodeId ); if ( node != null ) { return node; } node = new NodeImpl( nodeId, this ); ReentrantLock loadLock = lockId( nodeId ); try { if ( nodeCache.get( nodeId ) != null ) { node = nodeCache.get( nodeId ); return node; } if ( !persistenceManager.loadLightNode( nodeId ) ) { return null; } nodeCache.put( nodeId, node ); return node; } finally { loadLock.unlock(); } } NodeImpl getNodeForProxy( int nodeId ) { NodeImpl node = nodeCache.get( nodeId ); if ( node != null ) { return node; } node = new NodeImpl( nodeId, this ); ReentrantLock loadLock = lockId( nodeId ); try { if ( nodeCache.get( nodeId ) != null ) { node = nodeCache.get( nodeId ); return node; } if ( !persistenceManager.loadLightNode( nodeId ) ) { throw new NotFoundException( "Node[" + nodeId + "] not found." ); } nodeCache.put( nodeId, node ); return node; } finally { loadLock.unlock(); } } public Node getReferenceNode() throws NotFoundException { if ( referenceNodeId == -1 ) { throw new NotFoundException( "No reference node set" ); } return getNodeById( referenceNodeId ); } void setReferenceNodeId( int nodeId ) { this.referenceNodeId = nodeId; } public Relationship getRelationshipById( int relId ) throws NotFoundException { RelationshipImpl relationship = relCache.get( relId ); if ( relationship != null ) { return new RelationshipProxy( relId, this ); } relationship = new RelationshipImpl( relId, this ); ReentrantLock loadLock = lockId( relId ); try { if ( relCache.get( relId ) != null ) { relationship = relCache.get( relId ); return new RelationshipProxy( relId, this ); } RelationshipData data = persistenceManager.loadLightRelationship( relId ); if ( data == null ) { throw new NotFoundException( "Relationship[" + relId + "]" ); } int typeId = data.relationshipType(); RelationshipType type = getRelationshipTypeById( typeId ); if ( type == null ) { throw new NotFoundException( "Relationship[" + data.getId() + "] exist but relationship type[" + typeId + "] not found." ); } final int startNodeId = data.firstNode(); final int endNodeId = data.secondNode(); relationship = new RelationshipImpl( relId, startNodeId, endNodeId, type, false, this ); relCache.put( relId, relationship ); return new RelationshipProxy( relId, this ); } finally { loadLock.unlock(); } } RelationshipType getRelationshipTypeById( int id ) { return relTypeHolder.getRelationshipType( id ); } RelationshipImpl getRelForProxy( int relId ) { RelationshipImpl relationship = relCache.get( relId ); if ( relationship != null ) { return relationship; } relationship = new RelationshipImpl( relId, this ); ReentrantLock loadLock = lockId( relId ); try { if ( relCache.get( relId ) != null ) { relationship = relCache.get( relId ); return relationship; } RelationshipData data = persistenceManager.loadLightRelationship( relId ); if ( data == null ) { throw new NotFoundException( "Relationship[" + relId + "] not found." ); } int typeId = data.relationshipType(); RelationshipType type = getRelationshipTypeById( typeId ); if ( type == null ) { throw new NotFoundException( "Relationship[" + data.getId() + "] exist but relationship type[" + typeId + "] not found." ); } relationship = new RelationshipImpl( relId, data.firstNode(), data.secondNode(), type, false, this ); relCache.put( relId, relationship ); return relationship; } finally { loadLock.unlock(); } } public void removeNodeFromCache( int nodeId ) { nodeCache.remove( nodeId ); } public void removeRelationshipFromCache( int id ) { relCache.remove( id ); } Object loadPropertyValue( int id ) { return persistenceManager.loadPropertyValue( id ); } RelationshipChainPosition getRelationshipChainPosition( NodeImpl node ) { return persistenceManager.getRelationshipChainPosition( (int) node.getId() ); } ArrayMap<String,IntArray> getMoreRelationships( NodeImpl node ) { int nodeId = (int) node.getId(); RelationshipChainPosition position = node.getRelChainPosition(); Iterable<RelationshipData> rels = persistenceManager.getMoreRelationships( nodeId, position ); ArrayMap<String,IntArray> newRelationshipMap = new ArrayMap<String,IntArray>(); for ( RelationshipData rel : rels ) { int relId = rel.getId(); RelationshipImpl relImpl = relCache.get( relId ); RelationshipType type = null; if ( relImpl == null ) { type = getRelationshipTypeById( rel.relationshipType() ); assert type != null; relImpl = new RelationshipImpl( relId, rel.firstNode(), rel.secondNode(), type, false, this ); relCache.put( relId, relImpl ); } else { type = relImpl.getType(); } IntArray relationshipSet = newRelationshipMap.get( type.name() ); if ( relationshipSet == null ) { relationshipSet = new IntArray(); newRelationshipMap.put( type.name(), relationshipSet ); } relationshipSet.add( relId ); } return newRelationshipMap; } ArrayMap<Integer,PropertyData> loadProperties( NodeImpl node, boolean light ) { return persistenceManager.loadNodeProperties( (int) node.getId(), light ); } ArrayMap<Integer,PropertyData> loadProperties( RelationshipImpl relationship, boolean light ) { return persistenceManager.loadRelProperties( (int) relationship.getId(), light ); } public int getNodeCacheSize() { return nodeCache.size(); } public int getRelationshipCacheSize() { return relCache.size(); } public void clearCache() { nodeCache.clear(); relCache.clear(); } void setRollbackOnly() { try { transactionManager.setRollbackOnly(); } catch ( IllegalStateException e ) { // this exception always get generated in a finally block and // when it happens another exception has already been thrown // (most likley NotInTransactionException) log.log( Level.FINE, "Failed to set transaction rollback only", e ); } catch ( javax.transaction.SystemException se ) { // our TM never throws this exception log.log( Level.SEVERE, "Failed to set transaction rollback only", se ); } } void acquireLock( Object resource, LockType lockType ) { if ( lockType == LockType.READ ) { lockManager.getReadLock( resource ); } else if ( lockType == LockType.WRITE ) { lockManager.getWriteLock( resource ); } else { throw new LockException( "Unknown lock type: " + lockType ); } } void releaseLock( Object resource, LockType lockType ) { if ( lockType == LockType.READ ) { lockManager.releaseReadLock( resource ); } else if ( lockType == LockType.WRITE ) { lockReleaser.addLockToTransaction( resource, lockType ); } else { throw new LockException( "Unknown lock type: " + lockType ); } } public long getHighestPossibleIdInUse( Class<?> clazz ) { return idGenerator.getHighestPossibleIdInUse( clazz ); } public long getNumberOfIdsInUse( Class<?> clazz ) { return idGenerator.getNumberOfIdsInUse( clazz ); } public void removeRelationshipTypeFromCache( int id ) { relTypeHolder.removeRelType( id ); } void addPropertyIndexes( PropertyIndexData[] propertyIndexes ) { propertyIndexManager.addPropertyIndexes( propertyIndexes ); } void setHasAllpropertyIndexes( boolean hasAll ) { propertyIndexManager.setHasAll( hasAll ); } void clearPropertyIndexes() { propertyIndexManager.clear(); } PropertyIndex getIndexFor( int keyId ) { return propertyIndexManager.getIndexFor( keyId ); } Iterable<PropertyIndex> index( String key ) { return propertyIndexManager.index( key ); } boolean hasAllPropertyIndexes() { return propertyIndexManager.hasAll(); } boolean hasIndexFor( int keyId ) { return propertyIndexManager.hasIndexFor( keyId ); } PropertyIndex createPropertyIndex( String key ) { return propertyIndexManager.createPropertyIndex( key ); } int getRelationshipTypeIdFor( RelationshipType type ) { return relTypeHolder.getIdFor( type ); } void addRawRelationshipTypes( RelationshipTypeData[] relTypes ) { relTypeHolder.addRawRelationshipTypes( relTypes ); } Iterable<RelationshipType> getRelationshipTypes() { return relTypeHolder.getRelationshipTypes(); } ArrayMap<Integer,PropertyData> deleteNode( NodeImpl node ) { int nodeId = (int) node.getId(); deletePrimitive( node ); return persistenceManager.nodeDelete( nodeId ); // remove from node cache done via event } int nodeAddProperty( NodeImpl node, PropertyIndex index, Object value ) { int nodeId = (int) node.getId(); return persistenceManager.nodeAddProperty( nodeId, index, value ); } void nodeChangeProperty( NodeImpl node, int propertyId, Object value ) { int nodeId = (int) node.getId(); persistenceManager.nodeChangeProperty( nodeId, propertyId, value ); } void nodeRemoveProperty( NodeImpl node, int propertyId ) { int nodeId = (int) node.getId(); persistenceManager.nodeRemoveProperty( nodeId, propertyId ); } ArrayMap<Integer,PropertyData> deleteRelationship( RelationshipImpl rel ) { int relId = (int) rel.getId(); deletePrimitive( rel ); return persistenceManager.relDelete( relId ); // remove in rel cache done via event } int relAddProperty( RelationshipImpl rel, PropertyIndex index, Object value ) { int relId = (int) rel.getId(); return persistenceManager.relAddProperty( relId, index, value ); } void relChangeProperty( RelationshipImpl rel, int propertyId, Object value ) { int relId = (int) rel.getId(); persistenceManager.relChangeProperty( relId, propertyId, value ); } void relRemoveProperty( RelationshipImpl rel, int propertyId ) { int relId = (int) rel.getId(); persistenceManager.relRemoveProperty( relId, propertyId ); } public IntArray getCowRelationshipRemoveMap( NodeImpl node, String type ) { return lockReleaser.getCowRelationshipRemoveMap( node, type ); } public IntArray getCowRelationshipRemoveMap( NodeImpl node, String type, boolean create ) { return lockReleaser.getCowRelationshipRemoveMap( node, type, create ); } public ArrayMap<String,IntArray> getCowRelationshipAddMap( NodeImpl node ) { return lockReleaser.getCowRelationshipAddMap( node ); } public IntArray getCowRelationshipAddMap( NodeImpl node, String string ) { return lockReleaser.getCowRelationshipAddMap( node, string ); } public IntArray getCowRelationshipAddMap( NodeImpl node, String string, boolean create ) { return lockReleaser.getCowRelationshipAddMap( node, string, create ); } public NodeImpl getNodeIfCached( int nodeId ) { return nodeCache.get( nodeId ); } public RelationshipImpl getRelIfCached( int nodeId ) { return relCache.get( nodeId ); } public ArrayMap<Integer,PropertyData> getCowPropertyRemoveMap( Primitive primitive ) { return lockReleaser.getCowPropertyRemoveMap( primitive ); } private void deletePrimitive( Primitive primitive ) { lockReleaser.deletePrimitive( primitive ); } public ArrayMap<Integer,PropertyData> getCowPropertyAddMap( Primitive primitive ) { return lockReleaser.getCowPropertyAddMap( primitive ); } public ArrayMap<Integer,PropertyData> getCowPropertyAddMap( Primitive primitive, boolean create ) { return lockReleaser.getCowPropertyAddMap( primitive, create ); } public ArrayMap<Integer,PropertyData> getCowPropertyRemoveMap( Primitive primitive, boolean create ) { return lockReleaser.getCowPropertyRemoveMap( primitive, create ); } LockReleaser getLockReleaser() { return this.lockReleaser; } void addRelationshipType( RelationshipTypeData type ) { relTypeHolder.addRawRelationshipType( type ); } void addPropertyIndex( PropertyIndexData index ) { propertyIndexManager.addPropertyIndex( index ); } public TransactionData getTransactionData() { return lockReleaser.getTransactionData(); } IntArray getCreatedNodes() { return persistenceManager.getCreatedNodes(); } boolean nodeCreated( int nodeId ) { return persistenceManager.isNodeCreated( nodeId ); } boolean relCreated( int relId ) { return persistenceManager.isRelationshipCreated( relId ); } public String getKeyForProperty( int propertyId ) { int keyId = persistenceManager.getKeyIdForProperty( propertyId ); return propertyIndexManager.getIndexFor( keyId ).getKey(); } }