org.neo4j.kernel.ha.lock.SlaveLocksClient Maven / Gradle / Ivy
/*
* Copyright (c) 2002-2016 "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 .
*/
package org.neo4j.kernel.ha.lock;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.neo4j.collection.primitive.PrimitiveLongCollections;
import org.neo4j.com.ComException;
import org.neo4j.com.RequestContext;
import org.neo4j.com.Response;
import org.neo4j.graphdb.TransientDatabaseFailureException;
import org.neo4j.kernel.AvailabilityGuard;
import org.neo4j.kernel.AvailabilityGuard.UnavailableException;
import org.neo4j.kernel.DeadlockDetectedException;
import org.neo4j.kernel.ha.com.RequestContextFactory;
import org.neo4j.kernel.ha.com.master.Master;
import org.neo4j.kernel.impl.locking.LockClientStoppedException;
import org.neo4j.kernel.impl.locking.Locks;
import org.neo4j.kernel.impl.locking.ResourceTypes;
import org.neo4j.logging.Log;
import org.neo4j.logging.LogProvider;
import org.neo4j.storageengine.api.lock.AcquireLockTimeoutException;
import org.neo4j.storageengine.api.lock.ResourceType;
import static org.neo4j.kernel.impl.locking.LockType.READ;
import static org.neo4j.kernel.impl.locking.LockType.WRITE;
/**
* The slave locks client is responsible for managing locks on behalf of some actor on a slave machine. An actor
* could be a transaction or some other job that runs in the database.
*
* The client maintains a local "real" lock client, backed by some regular Locks implementation, but it also coordinates
* with the master for certain types of locks. If you grab a lock on a node, for instance, this class will grab a
* cluster-global lock by talking to the master machine, and then grab that same lock locally before returning.
*/
class SlaveLocksClient implements Locks.Client
{
private final Master master;
private final Locks.Client client;
private final Locks localLockManager;
private final RequestContextFactory requestContextFactory;
private final AvailabilityGuard availabilityGuard;
// Using atomic ints to avoid creating garbage through boxing.
private final Map> sharedLocks;
private final Map> exclusiveLocks;
private final Log log;
private boolean initialized;
private volatile boolean stopped;
public SlaveLocksClient(
Master master,
Locks.Client local,
Locks localLockManager,
RequestContextFactory requestContextFactory,
AvailabilityGuard availabilityGuard,
LogProvider logProvider )
{
this.master = master;
this.client = local;
this.localLockManager = localLockManager;
this.requestContextFactory = requestContextFactory;
this.availabilityGuard = availabilityGuard;
this.log = logProvider.getLog( getClass() );
sharedLocks = new HashMap<>();
exclusiveLocks = new HashMap<>();
}
private Map getLockMap(
Map> resourceMap,
ResourceType resourceType )
{
Map lockMap = resourceMap.get( resourceType );
if ( lockMap == null )
{
lockMap = new HashMap<>();
resourceMap.put( resourceType, lockMap );
}
return lockMap;
}
@Override
public void acquireShared( ResourceType resourceType, long... resourceIds ) throws AcquireLockTimeoutException
{
assertNotStopped();
Map lockMap = getLockMap( sharedLocks, resourceType );
long[] newResourceIds = onlyFirstTimeLocks( lockMap, resourceIds );
if ( newResourceIds.length > 0 )
{
acquireSharedOnMaster( resourceType, newResourceIds );
for ( long resourceId : newResourceIds )
{
if ( client.trySharedLock( resourceType, resourceId ) )
{
lockMap.put( resourceId, new AtomicInteger( 1 ) );
}
else
{
throw new LocalDeadlockDetectedException(
client, localLockManager, resourceType, resourceId, READ );
}
}
}
}
@Override
public void acquireExclusive( ResourceType resourceType, long... resourceIds ) throws
AcquireLockTimeoutException
{
assertNotStopped();
Map lockMap = getLockMap( exclusiveLocks, resourceType );
long[] newResourceIds = onlyFirstTimeLocks( lockMap, resourceIds );
if ( newResourceIds.length > 0 )
{
acquireExclusiveOnMaster( resourceType, newResourceIds );
for ( long resourceId : newResourceIds )
{
if ( client.tryExclusiveLock( resourceType, resourceId ) )
{
lockMap.put( resourceId, new AtomicInteger( 1 ) );
}
else
{
throw new LocalDeadlockDetectedException(
client, localLockManager, resourceType, resourceId, WRITE );
}
}
}
}
@Override
public boolean tryExclusiveLock( ResourceType resourceType, long resourceId )
{
throw newUnsupportedDirectTryLockUsageException();
}
@Override
public boolean trySharedLock( ResourceType resourceType, long resourceId )
{
throw newUnsupportedDirectTryLockUsageException();
}
@Override
public void releaseShared( ResourceType resourceType, long resourceId )
{
assertNotStopped();
Map lockMap = getLockMap( sharedLocks, resourceType );
AtomicInteger counter = lockMap.get( resourceId );
if ( counter == null )
{
throw new IllegalStateException( this + " cannot release lock it does not hold: SHARED " +
resourceType + "[" + resourceId + "]" );
}
if ( counter.decrementAndGet() == 0 )
{
lockMap.remove( resourceId );
client.releaseShared( resourceType, resourceId );
}
}
@Override
public void releaseExclusive( ResourceType resourceType, long resourceId )
{
assertNotStopped();
Map lockMap = getLockMap( exclusiveLocks, resourceType );
AtomicInteger counter = lockMap.get( resourceId );
if ( counter == null )
{
throw new IllegalStateException( this + " cannot release lock it does not hold: EXCLUSIVE " +
resourceType + "[" + resourceId + "]" );
}
if ( counter.decrementAndGet() == 0 )
{
lockMap.remove( resourceId );
client.releaseExclusive( resourceType, resourceId );
}
}
@Override
public void stop()
{
client.stop();
stopLockSessionOnMaster();
stopped = true;
}
@Override
public void close()
{
client.close();
sharedLocks.clear();
exclusiveLocks.clear();
if ( initialized )
{
if ( !stopped )
{
closeLockSessionOnMaster();
stopped = true;
}
initialized = false;
}
}
@Override
public int getLockSessionId()
{
assertNotStopped();
return initialized ? client.getLockSessionId() : -1;
}
private void stopLockSessionOnMaster()
{
try
{
endLockSessionOnMaster( false );
}
catch ( Throwable t )
{
log.warn( "Unable to stop lock session on master", t );
}
}
private void closeLockSessionOnMaster()
{
endLockSessionOnMaster( true );
}
private void endLockSessionOnMaster( boolean success )
{
try ( Response ignored = master.endLockSession( newRequestContextFor( client ), success ) )
{
// Lock session is closed on master at this point
}
catch ( ComException e )
{
throw new DistributedLockFailureException(
"Failed to end the lock session on the master (which implies releasing all held locks)",
master, e );
}
}
private long[] onlyFirstTimeLocks( Map lockMap, long[] resourceIds )
{
int cursor = 0;
for ( int i = 0; i < resourceIds.length; i++ )
{
AtomicInteger preExistingLock = lockMap.get( resourceIds[i] );
if ( preExistingLock != null )
{
// We already hold this lock, just increment the local reference count
preExistingLock.incrementAndGet();
}
else
{
resourceIds[cursor++] = resourceIds[i];
}
}
if ( cursor == 0 )
{
return PrimitiveLongCollections.EMPTY_LONG_ARRAY;
}
return cursor == resourceIds.length ? resourceIds : Arrays.copyOf( resourceIds, cursor );
}
private void acquireSharedOnMaster( ResourceType resourceType, long... resourceIds )
{
if ( resourceType == ResourceTypes.NODE
|| resourceType == ResourceTypes.RELATIONSHIP
|| resourceType == ResourceTypes.GRAPH_PROPS
|| resourceType == ResourceTypes.LEGACY_INDEX )
{
makeSureTxHasBeenInitialized();
RequestContext requestContext = newRequestContextFor( this );
try ( Response response = master.acquireSharedLock( requestContext, resourceType, resourceIds ) )
{
receiveLockResponse( response );
}
catch ( ComException e )
{
throw new DistributedLockFailureException( "Cannot get shared lock(s) on master", master, e );
}
}
}
private void acquireExclusiveOnMaster( ResourceType resourceType, long... resourceIds )
{
makeSureTxHasBeenInitialized();
RequestContext requestContext = newRequestContextFor( this );
try ( Response response = master.acquireExclusiveLock( requestContext, resourceType, resourceIds ) )
{
receiveLockResponse( response );
}
catch ( ComException e )
{
throw new DistributedLockFailureException( "Cannot get exclusive lock(s) on master", master, e );
}
}
private void receiveLockResponse( Response response )
{
LockResult result = response.response();
switch ( result.getStatus() )
{
case DEAD_LOCKED:
throw new DeadlockDetectedException( result.getMessage() );
case NOT_LOCKED:
throw new UnsupportedOperationException();
case OK_LOCKED:
break;
default:
throw new UnsupportedOperationException( result.toString() );
}
}
private void makeSureTxHasBeenInitialized()
{
try
{
availabilityGuard.checkAvailable();
}
catch ( UnavailableException e )
{
throw new TransientDatabaseFailureException( "Database not available", e );
}
if ( !initialized )
{
try ( Response ignored = master.newLockSession( newRequestContextFor( client ) ) )
{
// Lock session is initialized on master at this point
}
catch ( Exception exception )
{
// Temporary wrapping, we should review the exception structure of the Locks API to allow this to
// not use runtime exceptions here.
ComException e;
if ( exception instanceof ComException )
{
e = (ComException) exception;
}
else
{
e = new ComException( exception );
}
throw new DistributedLockFailureException( "Failed to start a new lock session on master", master, e );
}
initialized = true;
}
}
private RequestContext newRequestContextFor( Locks.Client client )
{
return requestContextFactory.newRequestContext( client.getLockSessionId() );
}
private void assertNotStopped()
{
if ( stopped )
{
throw new LockClientStoppedException( this );
}
}
private UnsupportedOperationException newUnsupportedDirectTryLockUsageException()
{
return new UnsupportedOperationException(
"Distributed tryLocks are not supported. They only work with local lock managers." );
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy