All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.redisson.RedissonLock Maven / Gradle / Ivy

There is a newer version: 3.40.2
Show newest version
/**
 * Copyright 2014 Nikita Koksharov, Nickolay Borbit
 *
 * 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.redisson;

import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;

import java.io.Serializable;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;

import org.redisson.async.ResultOperation;
import org.redisson.async.SyncOperation;
import org.redisson.connection.ConnectionManager;
import org.redisson.core.RLock;

import com.lambdaworks.redis.RedisAsyncConnection;
import com.lambdaworks.redis.RedisConnection;
import com.lambdaworks.redis.pubsub.RedisPubSubAdapter;

/**
 * Distributed implementation of {@link java.util.concurrent.locks.Lock}
 * Implements reentrant lock.
 *
 * @author Nikita Koksharov
 *
 */
public class RedissonLock extends RedissonObject implements RLock {

    public static class LockValue implements Serializable {

        private static final long serialVersionUID = -8895632286065689476L;

        private UUID id;
        private Long threadId;
        // need for reentrant support
        private int counter;

        public LockValue() {
        }

        public LockValue(UUID id, Long threadId) {
            super();
            this.id = id;
            this.threadId = threadId;
        }

        public void decCounter() {
            counter--;
        }

        public void incCounter() {
            counter++;
        }

        public int getCounter() {
            return counter;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((id == null) ? 0 : id.hashCode());
            result = prime * result + ((threadId == null) ? 0 : threadId.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            LockValue other = (LockValue) obj;
            if (id == null) {
                if (other.id != null)
                    return false;
            } else if (!id.equals(other.id))
                return false;
            if (threadId == null) {
                if (other.threadId != null)
                    return false;
            } else if (!threadId.equals(other.threadId))
                return false;
            return true;
        }

    }

    private final UUID id;

    private static final Integer unlockMessage = 0;

    private static final ConcurrentMap ENTRIES = new ConcurrentHashMap();

    protected RedissonLock(ConnectionManager connectionManager, String name, UUID id) {
        super(connectionManager, name);
        this.id = id;
    }

    private void unsubscribe() {
        while (true) {
            RedissonLockEntry entry = ENTRIES.get(getEntryName());
            if (entry == null) {
                return;
            }

            RedissonLockEntry newEntry = new RedissonLockEntry(entry);
            newEntry.release();
            if (ENTRIES.replace(getEntryName(), entry, newEntry)) {
                if (newEntry.isFree()
                        && ENTRIES.remove(getEntryName(), newEntry)) {
                    synchronized (ENTRIES) {
                        // maybe added during subscription
                        if (!ENTRIES.containsKey(getEntryName())) {
                            connectionManager.unsubscribe(getChannelName());
                        }
                    }
                }
                return;
            }
        }
    }

    private String getEntryName() {
        return id + ":" + getName();
    }

    private Promise aquire() {
        while (true) {
            RedissonLockEntry entry = ENTRIES.get(getEntryName());
            if (entry == null) {
                return null;
            }

            RedissonLockEntry newEntry = new RedissonLockEntry(entry);
            newEntry.aquire();
            if (ENTRIES.replace(getEntryName(), entry, newEntry)) {
                return newEntry.getPromise();
            }
        }
    }

    private Future subscribe() {
        Promise promise = aquire();
        if (promise != null) {
            return promise;
        }

        Promise newPromise = newPromise();
        final RedissonLockEntry value = new RedissonLockEntry(newPromise);
        value.aquire();
        RedissonLockEntry oldValue = ENTRIES.putIfAbsent(getEntryName(), value);
        if (oldValue != null) {
            Promise oldPromise = aquire();
            if (oldPromise == null) {
                return subscribe();
            }
            return oldPromise;
        }

        RedisPubSubAdapter listener = new RedisPubSubAdapter() {

            @Override
            public void subscribed(String channel, long count) {
                if (getChannelName().equals(channel)
                        && !value.getPromise().isSuccess()) {
                    value.getPromise().setSuccess(true);
                }
            }

            @Override
            public void message(String channel, Object message) {
                if (message.equals(unlockMessage) && getChannelName().equals(channel)) {
                    value.getLatch().release();
                }
            }

        };

        synchronized (ENTRIES) {
            connectionManager.subscribe(listener, getChannelName());
        }
        return newPromise;
    }

    private String getChannelName() {
        return "redisson__lock__channel__{" + getName() + "}";
    }

    @Override
    public void lock() {
        try {
            lockInterruptibly();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return;
        }
    }

    @Override
    public void lock(long leaseTime, TimeUnit unit) {
        try {
            lockInterruptibly(leaseTime, unit);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return;
        }
    }


    @Override
    public void lockInterruptibly() throws InterruptedException {
        lockInterruptibly(-1, null);
    }

    @Override
    public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
        Long ttl;
        if (leaseTime != -1) {
            ttl = tryLockInner(leaseTime, unit);
        } else {
            ttl = tryLockInner();
        }
        // lock acquired
        if (ttl == null) {
            return;
        }

        subscribe().awaitUninterruptibly();

        try {
            while (true) {
                if (leaseTime != -1) {
                    ttl = tryLockInner(leaseTime, unit);
                } else {
                    ttl = tryLockInner();
                }
                // lock acquired
                if (ttl == null) {
                    break;
                }

                // waiting for message
                RedissonLockEntry entry = ENTRIES.get(getEntryName());
                if (ttl >= 0) {
                    entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } else {
                    entry.getLatch().acquire();
                }
            }
        } finally {
            unsubscribe();
        }
    }

    @Override
    public boolean tryLock() {
        return tryLockInner() == null;
    }

    private Long tryLockInner() {
        final LockValue currentLock = new LockValue(id, Thread.currentThread().getId());
        currentLock.incCounter();

        return connectionManager.write(getName(), new SyncOperation() {

            @Override
            public Long execute(RedisConnection connection) {
                Boolean res = connection.setnx(getName(), currentLock);
                if (!res) {
                    connection.watch(getName());
                    LockValue lock = (LockValue) connection.get(getName());
                    if (lock != null && lock.equals(currentLock)) {
                        lock.incCounter();
                        connection.multi();
                        connection.set(getName(), lock);
                        if (connection.exec().size() == 1) {
                            return null;
                        }
                    }
                    connection.unwatch();

                    Long ttl = connection.pttl(getName());
                    return ttl;
                }
                return null;
            }
        });
    }

    private Long tryLockInner(final long leaseTime, final TimeUnit unit) {
        final LockValue currentLock = new LockValue(id, Thread.currentThread().getId());
        currentLock.incCounter();

        return connectionManager.write(getName(), new SyncOperation() {
            @Override
            public Long execute(RedisConnection connection) {
                long time = unit.toMillis(leaseTime);
                String res = connection.setexnx(getName(), currentLock, time);
                if ("OK".equals(res)) {
                    return null;
                } else {
                    connection.watch(getName());
                    LockValue lock = (LockValue) connection.get(getName());
                    if (lock != null && lock.equals(currentLock)) {
                        lock.incCounter();
                        connection.multi();
                        connection.psetex(getName(), time, lock);
                        if (connection.exec().size() == 1) {
                            return null;
                        }
                    }
                    connection.unwatch();

                    Long ttl = connection.pttl(getName());
                    return ttl;
                }
            }
        });
    }

    public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        long time = unit.toMillis(waitTime);
        Long ttl;
        if (leaseTime != -1) {
            ttl = tryLockInner(leaseTime, unit);
        } else {
            ttl = tryLockInner();
        }
        // lock acquired
        if (ttl == null) {
            return true;
        }

        if (!subscribe().awaitUninterruptibly(time, unit)) {
            return false;
        }

        try {
            while (true) {
                if (leaseTime != -1) {
                    ttl = tryLockInner(leaseTime, unit);
                } else {
                    ttl = tryLockInner();
                }
                // lock acquired
                if (ttl == null) {
                    break;
                }

                if (time <= 0) {
                    return false;
                }

                // waiting for message
                long current = System.currentTimeMillis();
                RedissonLockEntry entry = ENTRIES.get(getEntryName());

                if (ttl >= 0 && ttl < time) {
                    entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } else {
                    entry.getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
                }

                long elapsed = System.currentTimeMillis() - current;
                time -= elapsed;
            }
            return true;
        } finally {
            unsubscribe();
        }
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return tryLock(time, -1, unit);
    }

    @Override
    public void unlock() {
        connectionManager.write(getName(), new SyncOperation() {
            @Override
            public Void execute(RedisConnection connection) {
                LockValue lock = (LockValue) connection.get(getName());
                if (lock != null) {
                    LockValue currentLock = new LockValue(id, Thread.currentThread().getId());
                    if (lock.equals(currentLock)) {
                        if (lock.getCounter() > 1) {
                            lock.decCounter();
                            connection.set(getName(), lock);
                        } else {
                            unlock(connection);
                        }
                    } else {
                        throw new IllegalMonitorStateException("Attempt to unlock lock, not locked by current id: "
                                + id + " thread-id: " + Thread.currentThread().getId());
                    }
                } else {
                    // could be deleted
                }
                return null;
            }
        });
    }

    private void unlock(RedisConnection connection) {
        int counter = 0;
        while (counter < 5) {
            connection.multi();
            connection.del(getName());
            connection.publish(getChannelName(), unlockMessage);
            List res = connection.exec();
            if (res.size() == 2) {
                return;
            }
            counter++;
        }
        throw new IllegalStateException("Can't unlock lock after 5 attempts. Current id: "
                + id + " thread-id: " + Thread.currentThread().getId());
    }

    @Override
    public Condition newCondition() {
        // TODO implement
        throw new UnsupportedOperationException();
    }

    @Override
    public void forceUnlock() {
        connectionManager.write(getName(), new SyncOperation() {
            @Override
            public Void execute(RedisConnection connection) {
                unlock(connection);
                return null;
            }
        });
    }

    @Override
    public boolean isLocked() {
        return getCurrentLock() != null;
    }

    private LockValue getCurrentLock() {
        LockValue lock = connectionManager.read(getName(), new ResultOperation() {
            @Override
            protected Future execute(RedisAsyncConnection async) {
                return async.get(getName());
            }
        });
        return lock;
    }

    @Override
    public boolean isHeldByCurrentThread() {
        LockValue lock = getCurrentLock();
        LockValue currentLock = new LockValue(id, Thread.currentThread().getId());
        return lock != null && lock.equals(currentLock);
    }

    @Override
    public int getHoldCount() {
        LockValue lock = getCurrentLock();
        LockValue currentLock = new LockValue(id, Thread.currentThread().getId());
        if (lock != null && lock.equals(currentLock)) {
            return lock.getCounter();
        }
        return 0;
    }

    @Override
    public void delete() {
        forceUnlock();
    }

}