/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.hazelcast.concurrent.lock; import com.hazelcast.concurrent.lock.operations.LocalLockCleanupOperation; import com.hazelcast.concurrent.lock.operations.LockReplicationOperation; import com.hazelcast.concurrent.lock.operations.UnlockOperation; import com.hazelcast.config.LockConfig; import com.hazelcast.core.DistributedObject; import com.hazelcast.instance.MemberImpl; import com.hazelcast.nio.serialization.Data; import com.hazelcast.partition.strategy.StringPartitioningStrategy; import com.hazelcast.spi.ClientAwareService; import com.hazelcast.spi.ManagedService; import com.hazelcast.spi.MemberAttributeServiceEvent; import com.hazelcast.spi.MembershipAwareService; import com.hazelcast.spi.MembershipServiceEvent; import com.hazelcast.spi.MigrationAwareService; import com.hazelcast.spi.NodeEngine; import com.hazelcast.spi.ObjectNamespace; import com.hazelcast.spi.Operation; import com.hazelcast.spi.OperationService; import com.hazelcast.spi.PartitionMigrationEvent; import com.hazelcast.spi.PartitionReplicationEvent; import com.hazelcast.spi.QuorumAwareService; import com.hazelcast.spi.RemoteService; import com.hazelcast.spi.impl.PartitionSpecificRunnable; import com.hazelcast.spi.impl.operationservice.InternalOperationService; import com.hazelcast.spi.partition.MigrationEndpoint; import com.hazelcast.spi.properties.GroupProperty; import com.hazelcast.spi.properties.HazelcastProperties; import com.hazelcast.util.Clock; import com.hazelcast.util.ConstructorFunction; import com.hazelcast.util.ContextMutexFactory; import java.util.Collection; import java.util.LinkedList; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import static com.hazelcast.util.ConcurrencyUtil.getOrPutSynchronized; @SuppressWarnings("checkstyle:methodcount") public final class LockServiceImpl implements LockService, ManagedService, RemoteService, MembershipAwareService, MigrationAwareService, ClientAwareService, QuorumAwareService { private static final Object NULL_OBJECT = new Object(); private final NodeEngine nodeEngine; private final LockStoreContainer[] containers; private final ConcurrentMap<String, ConstructorFunction<ObjectNamespace, LockStoreInfo>> constructors = new ConcurrentHashMap<String, ConstructorFunction<ObjectNamespace, LockStoreInfo>>(); private final ConcurrentMap<String, Object> quorumConfigCache = new ConcurrentHashMap<String, Object>(); private final ContextMutexFactory quorumConfigCacheMutexFactory = new ContextMutexFactory(); private final long maxLeaseTimeInMillis; public LockServiceImpl(NodeEngine nodeEngine) { this.nodeEngine = nodeEngine; this.containers = new LockStoreContainer[nodeEngine.getPartitionService().getPartitionCount()]; for (int i = 0; i < containers.length; i++) { containers[i] = new LockStoreContainer(this, i); } maxLeaseTimeInMillis = getMaxLeaseTimeInMillis(nodeEngine.getProperties()); } NodeEngine getNodeEngine() { return nodeEngine; } @Override public void init(NodeEngine nodeEngine, Properties properties) { registerLockStoreConstructor(SERVICE_NAME, new ConstructorFunction<ObjectNamespace, LockStoreInfo>() { public LockStoreInfo createNew(ObjectNamespace key) { return new LockStoreInfo() { @Override public int getBackupCount() { return 1; } @Override public int getAsyncBackupCount() { return 0; } }; } }); } @Override public void reset() { for (LockStoreContainer container : containers) { for (LockStoreImpl lockStore : container.getLockStores()) { lockStore.clear(); } } } @Override public void shutdown(boolean terminate) { for (LockStoreContainer container : containers) { container.clear(); } } @Override public long getMaxLeaseTimeInMillis() { return maxLeaseTimeInMillis; } @Override public void registerLockStoreConstructor(String serviceName, ConstructorFunction<ObjectNamespace, LockStoreInfo> constructorFunction) { boolean put = constructors.putIfAbsent(serviceName, constructorFunction) == null; if (!put) { throw new IllegalArgumentException("LockStore constructor for service[" + serviceName + "] " + "is already registered!"); } } /** * Gets the constructor for the given service, or null if the constructor doesn't exist. * * @param serviceName the name of the constructor to look up. * @return the found ConstructorFunction. */ ConstructorFunction<ObjectNamespace, LockStoreInfo> getConstructor(String serviceName) { return constructors.get(serviceName); } @Override public LockStore createLockStore(int partitionId, ObjectNamespace namespace) { final LockStoreContainer container = getLockContainer(partitionId); container.getOrCreateLockStore(namespace); return new LockStoreProxy(container, namespace); } @Override public void clearLockStore(int partitionId, ObjectNamespace namespace) { LockStoreContainer container = getLockContainer(partitionId); container.clearLockStore(namespace); } public LockStoreContainer getLockContainer(int partitionId) { return containers[partitionId]; } public LockStoreImpl getLockStore(int partitionId, ObjectNamespace namespace) { return getLockContainer(partitionId).getOrCreateLockStore(namespace); } @Override public void memberAdded(MembershipServiceEvent event) { } @Override public void memberRemoved(MembershipServiceEvent event) { final MemberImpl member = event.getMember(); final String uuid = member.getUuid(); releaseLocksOwnedBy(uuid); } @Override public void memberAttributeChanged(MemberAttributeServiceEvent event) { } private void releaseLocksOwnedBy(final String uuid) { final InternalOperationService operationService = (InternalOperationService) nodeEngine.getOperationService(); for (final LockStoreContainer container : containers) { operationService.execute(new PartitionSpecificRunnable() { @Override public void run() { for (LockStoreImpl lockStore : container.getLockStores()) { cleanUpLock(operationService, uuid, container.getPartitionId(), lockStore); } } @Override public int getPartitionId() { return container.getPartitionId(); } }); } } private void cleanUpLock(OperationService operationService, String uuid, int partitionId, LockStoreImpl lockStore) { Collection<LockResource> locks = lockStore.getLocks(); for (LockResource lock : locks) { Data key = lock.getKey(); if (uuid.equals(lock.getOwner()) && !lock.isTransactional()) { UnlockOperation op = createLockCleanupOperation(partitionId, lockStore.getNamespace(), key, uuid); // op will be executed on partition thread locally. Invocation is to handle retries. operationService.invokeOnTarget(SERVICE_NAME, op, nodeEngine.getThisAddress()); } lockStore.cleanWaitersAndSignalsFor(key, uuid); } } private UnlockOperation createLockCleanupOperation(int partitionId, ObjectNamespace namespace, Data key, String uuid) { UnlockOperation op = new LocalLockCleanupOperation(namespace, key, uuid); op.setAsyncBackup(true); op.setNodeEngine(nodeEngine); op.setServiceName(SERVICE_NAME); op.setService(LockServiceImpl.this); op.setPartitionId(partitionId); op.setValidateTarget(false); return op; } @Override public Collection<LockResource> getAllLocks() { final Collection<LockResource> locks = new LinkedList<LockResource>(); for (LockStoreContainer container : containers) { for (LockStoreImpl lockStore : container.getLockStores()) { locks.addAll(lockStore.getLocks()); } } return locks; } @Override public void beforeMigration(PartitionMigrationEvent partitionMigrationEvent) { } @Override public Operation prepareReplicationOperation(PartitionReplicationEvent event) { int partitionId = event.getPartitionId(); LockStoreContainer container = containers[partitionId]; int replicaIndex = event.getReplicaIndex(); LockReplicationOperation op = new LockReplicationOperation(container, partitionId, replicaIndex); if (op.isEmpty()) { return null; } else { return op; } } @Override public void commitMigration(PartitionMigrationEvent event) { if (event.getMigrationEndpoint() == MigrationEndpoint.SOURCE) { clearLockStoresHavingLesserBackupCountThan(event.getPartitionId(), event.getNewReplicaIndex()); } else { scheduleEvictions(event.getPartitionId()); } // Local locks are local to the partition and replicaIndex where they have been acquired. // That is the reason they are removed on any partition event on the destination. removeLocalLocks(event.getPartitionId()); } private void removeLocalLocks(int partitionId) { LockStoreContainer container = containers[partitionId]; for (LockStoreImpl lockStore : container.getLockStores()) { lockStore.removeLocalLocks(); } } private void scheduleEvictions(int partitionId) { long now = Clock.currentTimeMillis(); LockStoreContainer container = containers[partitionId]; for (LockStoreImpl ls : container.getLockStores()) { for (LockResource lock : ls.getLocks()) { long expirationTime = lock.getExpirationTime(); if (expirationTime == Long.MAX_VALUE || expirationTime < 0) { continue; } long leaseTime = expirationTime - now; if (leaseTime <= 0) { ls.forceUnlock(lock.getKey()); } else { ls.scheduleEviction(lock.getKey(), lock.getVersion(), leaseTime); } } } } @Override public void rollbackMigration(PartitionMigrationEvent event) { if (event.getMigrationEndpoint() == MigrationEndpoint.DESTINATION) { clearLockStoresHavingLesserBackupCountThan(event.getPartitionId(), event.getCurrentReplicaIndex()); } } private void clearLockStoresHavingLesserBackupCountThan(int partitionId, int thresholdReplicaIndex) { LockStoreContainer container = containers[partitionId]; for (LockStoreImpl lockStore : container.getLockStores()) { if (thresholdReplicaIndex < 0 || thresholdReplicaIndex > lockStore.getTotalBackupCount()) { lockStore.clear(); } } } @Override public DistributedObject createDistributedObject(String objectId) { return new LockProxy(nodeEngine, this, objectId); } @Override public void destroyDistributedObject(String objectId) { final Data key = nodeEngine.getSerializationService().toData(objectId, StringPartitioningStrategy.INSTANCE); final int partitionId = nodeEngine.getPartitionService().getPartitionId(key); final LockStoreImpl lockStore = containers[partitionId].getLockStore(new InternalLockNamespace(objectId)); if (lockStore != null) { InternalOperationService operationService = (InternalOperationService) nodeEngine.getOperationService(); operationService.execute(new PartitionSpecificRunnable() { @Override public void run() { lockStore.forceUnlock(key); } @Override public int getPartitionId() { return partitionId; } }); } } @Override public void clientDisconnected(String clientUuid) { releaseLocksOwnedBy(clientUuid); } public static long getMaxLeaseTimeInMillis(HazelcastProperties hazelcastProperties) { return hazelcastProperties.getMillis(GroupProperty.LOCK_MAX_LEASE_TIME_SECONDS); } @Override public String getQuorumName(final String name) { // we use caching here because lock operations are often and we should avoid lock config lookup Object quorumName = getOrPutSynchronized(quorumConfigCache, name, quorumConfigCacheMutexFactory, new ConstructorFunction<String, Object>() { @Override public Object createNew(String arg) { LockConfig lockConfig = nodeEngine.getConfig().findLockConfig(name); String quorumName = lockConfig.getQuorumName(); // The quorumName will be null if there is no quorum defined for this data structure, // but the QuorumService is active, due to another data structure with a quorum configuration return quorumName == null ? NULL_OBJECT : quorumName; } }); return quorumName == NULL_OBJECT ? null : (String) quorumName; } }