org.openspaces.core.map.LockManager.java Source code

Java tutorial

Introduction

Here is the source code for org.openspaces.core.map.LockManager.java

Source

/*
 * Copyright (c) 2008-2016, GigaSpaces Technologies, 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 org.openspaces.core.map;

import com.gigaspaces.client.transaction.DistributedTransactionManagerProvider;
import com.j_spaces.core.IJSpace;
import com.j_spaces.core.client.ReadModifiers;
import com.j_spaces.map.IMap;
import com.j_spaces.map.MapEntryFactory;
import com.j_spaces.map.SpaceMapEntry;

import net.jini.core.transaction.Transaction;
import net.jini.core.transaction.TransactionException;
import net.jini.core.transaction.TransactionFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openspaces.core.SpaceTimeoutException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.transaction.CannotCreateTransactionException;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * The lock manager is built on top of {@link IMap} and supports the ability to lock and unlock
 * certain keys within the map.
 *
 * @author kimchy
 */
public class LockManager {

    /**
     * A empty lock value written to indicate a lock when there is no value to lock on (i.e. calling
     * lock on a key where there is no value in the cache).
     */
    final public static Object EMPTY_LOCK_VALUE = "";

    public static boolean isEmptyLockValue(Object value) {
        return (value instanceof String) && ((String) value).length() == 0;
    }

    private static Log logger = LogFactory.getLog(LockManager.class);

    private final IMap map;

    private final ConcurrentHashMap<String, Transaction> lockedUIDHashMap = new ConcurrentHashMap<String, Transaction>();

    private final IJSpace masterSpace;

    private final BlockingQueue<SpaceMapEntry> templatePool;

    private final DistributedTransactionManagerProvider transactionManagerProvider;

    /**
     * Creates a new Lock Manager based on the {@link com.j_spaces.map.IMap}.
     */
    public LockManager(IMap map) {
        this.map = map;
        this.masterSpace = map.getMasterSpace();
        try {
            transactionManagerProvider = new DistributedTransactionManagerProvider();
        } catch (TransactionException e) {
            throw new CannotCreateTransactionException("Failed to obtain transaction lock manager", e);
        }

        templatePool = new ArrayBlockingQueue<SpaceMapEntry>(1000);
        for (int i = 0; i < 1000; i++) {
            templatePool.add(MapEntryFactory.create());
        }
    }

    /**
     * Locks the given key for any updates. Retruns a {@link org.openspaces.core.map.LockHandle}
     * that can be used to perform specific updates under the same lock (by using the transaction
     * object stored within it).
     *
     * <p>Might create an empty value if there is no value in order to lock on. The empty value can
     * be checked using {@link #isEmptyLockValue(Object)}.
     *
     * @param key                   The key to lock
     * @param lockTimeToLive        The lock time to live (in milliseconds)
     * @param timeoutWaitingForLock The time to wait for an already locked lock
     * @return LockHandle that can be used to perfrom operations under the given lock
     */
    public LockHandle lock(Object key, long lockTimeToLive, long timeoutWaitingForLock) {

        String uid = String.valueOf(key);
        Transaction tr = null;
        try {
            tr = getTransaction(lockTimeToLive);
        } finally {
            if (tr == null) {
                lockedUIDHashMap.remove(uid);
                return null;
            }
        }

        SpaceMapEntry ee = getTemplate(key);
        try {
            Object retTake = masterSpace.readIfExists(ee, tr, timeoutWaitingForLock,
                    ReadModifiers.EXCLUSIVE_READ_LOCK);
            if (retTake == null) {
                // TODO GS-9310: design and implement a solution for locking non-existent keys.
                map.put(key, EMPTY_LOCK_VALUE, tr, Integer.MAX_VALUE);
            }
        } catch (SpaceTimeoutException e) {
            try {
                tr.abort();
            } catch (Exception re) {
                logger.warn("Failed to abort transaction", e);
            }
            // rethrow
            throw e;
        } catch (Throwable t) {
            try {
                tr.abort();
            } catch (Exception re) {
                logger.warn("Failed to abort transaction", t);
            }
            lockedUIDHashMap.remove(uid);
            throw new DataAccessResourceFailureException("Failed to obtain lock for key [" + key + "]", t);
        } finally {
            releaseTemplate(ee);
        }

        //otherwise, map uid->txn
        lockedUIDHashMap.put(uid, tr);
        return new LockHandle(this, tr, key);
    }

    /**
     * Unlocks the given key and puts the given value in a single operation.
     *
     * @param key   The key to unlock and put the value in
     * @param value The value to put after unlocking the key
     */
    public void putAndUnlock(Object key, Object value) {
        String uid = String.valueOf(key);
        Transaction tr = lockedUIDHashMap.get(uid);

        if (tr == null) {
            map.put(key, value, null, Integer.MAX_VALUE);
            return;
        }

        try {
            map.put(key, value, tr, Integer.MAX_VALUE);
            tr.commit();
        } catch (Throwable t) {
            logger.warn("Failed to commit transaction and unlock the key [" + key + "], ignoring", t);
        } finally {
            lockedUIDHashMap.remove(uid);
        }
    }

    /**
     * Returns <code>true</code> if the given key is locked. Otherwise returns <code>false</code>.
     *
     * @param key The key to check if it locked or not.
     * @return <code>true</code> if the given key is locked or not.
     */
    public boolean islocked(Object key) {
        // first check locally
        String uid = String.valueOf(key);
        if (lockedUIDHashMap.containsKey(uid))
            return true;

        // now check globally
        SpaceMapEntry ee = getTemplate(key);
        try {
            Object lockEntry = masterSpace.readIfExists(ee, null, 0);
            return lockEntry == null;
        } catch (Exception e) {
            return true;
        } finally {
            releaseTemplate(ee);
        }
    }

    /**
     * Unlocks the given lock on the key
     *
     * @param key The key to unlock
     */
    public void unlock(Object key) {
        String uid = String.valueOf(key);

        Transaction tr = lockedUIDHashMap.get(uid);
        if (tr == null) {
            return;
        }

        try {
            tr.commit();
        } catch (Exception e) {
            logger.warn("Failed to commit transaction and unlocking the object, ignoring", e);
        } finally {
            lockedUIDHashMap.remove(uid);
        }
    }

    private Transaction getTransaction(long timeout) throws CannotCreateTransactionException {
        Transaction.Created tCreated;
        try {
            tCreated = TransactionFactory.create(transactionManagerProvider.getTransactionManager(), timeout);
        } catch (Exception e) {
            throw new CannotCreateTransactionException("Failed to create lock transaction", e);
        }
        return tCreated.transaction;
    }

    private SpaceMapEntry getTemplate(Object key) {
        SpaceMapEntry ee;
        try {
            ee = templatePool.poll(100, TimeUnit.MILLISECONDS);
            if (ee == null) {
                ee = MapEntryFactory.create();
            }
        } catch (InterruptedException e) {
            throw new DataAccessResourceFailureException("Failed to take resource from pool", e);
        }
        ee.setKey(key);
        // to support load balancing
        return ee;
    }

    private void releaseTemplate(SpaceMapEntry ee) {
        if (ee != null) {
            templatePool.offer(ee);
        }
    }

}