org.redisson.RedissonSemaphore.java Source code

Java tutorial

Introduction

Here is the source code for org.redisson.RedissonSemaphore.java

Source

/**
 * Copyright 2016 Nikita Koksharov
 *
 * 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 java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import org.redisson.api.RFuture;
import org.redisson.api.RSemaphore;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandExecutor;
import org.redisson.misc.RPromise;
import org.redisson.pubsub.SemaphorePubSub;

import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;

/**
 * Distributed and concurrent implementation of {@link java.util.concurrent.Semaphore}.
 * <p>
 * Works in non-fair mode. Therefore order of acquiring is unpredictable.
 *
 * @author Nikita Koksharov
 *
 */
public class RedissonSemaphore extends RedissonExpirable implements RSemaphore {

    private final SemaphorePubSub semaphorePubSub;

    final CommandExecutor commandExecutor;

    protected RedissonSemaphore(CommandExecutor commandExecutor, String name, SemaphorePubSub semaphorePubSub) {
        super(commandExecutor, name);
        this.commandExecutor = commandExecutor;
        this.semaphorePubSub = semaphorePubSub;
    }

    String getChannelName() {
        return getChannelName(getName());
    }

    public static String getChannelName(String name) {
        if (name.contains("{")) {
            return "redisson_sc:" + name;
        }
        return "redisson_sc:{" + name + "}";
    }

    @Override
    public void acquire() throws InterruptedException {
        acquire(1);
    }

    @Override
    public void acquire(int permits) throws InterruptedException {
        if (tryAcquire(permits)) {
            return;
        }

        RFuture<RedissonLockEntry> future = subscribe();
        commandExecutor.syncSubscription(future);
        try {
            while (true) {
                if (tryAcquire(permits)) {
                    return;
                }

                getEntry().getLatch().acquire(permits);
            }
        } finally {
            unsubscribe(future);
        }
        //        get(acquireAsync(permits));
    }

    @Override
    public RFuture<Void> acquireAsync() {
        return acquireAsync(1);
    }

    @Override
    public RFuture<Void> acquireAsync(final int permits) {
        final RPromise<Void> result = newPromise();
        RFuture<Boolean> tryAcquireFuture = tryAcquireAsync(permits);
        tryAcquireFuture.addListener(new FutureListener<Boolean>() {
            @Override
            public void operationComplete(Future<Boolean> future) throws Exception {
                if (!future.isSuccess()) {
                    result.tryFailure(future.cause());
                    return;
                }

                if (future.getNow()) {
                    if (!result.trySuccess(null)) {
                        releaseAsync(permits);
                    }
                    return;
                }

                final RFuture<RedissonLockEntry> subscribeFuture = subscribe();
                subscribeFuture.addListener(new FutureListener<RedissonLockEntry>() {
                    @Override
                    public void operationComplete(Future<RedissonLockEntry> future) throws Exception {
                        if (!future.isSuccess()) {
                            result.tryFailure(future.cause());
                            return;
                        }

                        acquireAsync(permits, subscribeFuture, result);
                    }

                });
            }
        });
        return result;
    }

    private void tryAcquireAsync(final AtomicLong time, final int permits,
            final RFuture<RedissonLockEntry> subscribeFuture, final RPromise<Boolean> result) {
        if (result.isDone()) {
            unsubscribe(subscribeFuture);
            return;
        }

        if (time.get() <= 0) {
            unsubscribe(subscribeFuture);
            result.trySuccess(false);
            return;
        }

        final long current = System.currentTimeMillis();
        RFuture<Boolean> tryAcquireFuture = tryAcquireAsync(permits);
        tryAcquireFuture.addListener(new FutureListener<Boolean>() {
            @Override
            public void operationComplete(Future<Boolean> future) throws Exception {
                if (!future.isSuccess()) {
                    unsubscribe(subscribeFuture);
                    result.tryFailure(future.cause());
                    return;
                }

                if (future.getNow()) {
                    unsubscribe(subscribeFuture);
                    if (!result.trySuccess(true)) {
                        releaseAsync(permits);
                    }
                    return;
                }

                long elapsed = System.currentTimeMillis() - current;
                time.addAndGet(-elapsed);

                if (time.get() <= 0) {
                    unsubscribe(subscribeFuture);
                    result.trySuccess(false);
                    return;
                }

                // waiting for message
                final long current = System.currentTimeMillis();
                final RedissonLockEntry entry = getEntry();
                synchronized (entry) {
                    if (entry.getLatch().tryAcquire()) {
                        tryAcquireAsync(time, permits, subscribeFuture, result);
                    } else {
                        final AtomicBoolean executed = new AtomicBoolean();
                        final AtomicReference<Timeout> futureRef = new AtomicReference<Timeout>();
                        final Runnable listener = new Runnable() {
                            @Override
                            public void run() {
                                executed.set(true);
                                if (futureRef.get() != null && !futureRef.get().cancel()) {
                                    entry.getLatch().release();
                                    return;
                                }
                                long elapsed = System.currentTimeMillis() - current;
                                time.addAndGet(-elapsed);

                                tryAcquireAsync(time, permits, subscribeFuture, result);
                            }
                        };
                        entry.addListener(listener);

                        long t = time.get();
                        if (!executed.get()) {
                            Timeout scheduledFuture = commandExecutor.getConnectionManager()
                                    .newTimeout(new TimerTask() {
                                        @Override
                                        public void run(Timeout timeout) throws Exception {
                                            synchronized (entry) {
                                                if (entry.removeListener(listener)) {
                                                    long elapsed = System.currentTimeMillis() - current;
                                                    time.addAndGet(-elapsed);

                                                    tryAcquireAsync(time, permits, subscribeFuture, result);
                                                }
                                            }
                                        }
                                    }, t, TimeUnit.MILLISECONDS);
                            futureRef.set(scheduledFuture);
                        }
                    }
                }
            }
        });

    }

    private void acquireAsync(final int permits, final RFuture<RedissonLockEntry> subscribeFuture,
            final RPromise<Void> result) {
        if (result.isDone()) {
            unsubscribe(subscribeFuture);
            return;
        }

        RFuture<Boolean> tryAcquireFuture = tryAcquireAsync(permits);
        tryAcquireFuture.addListener(new FutureListener<Boolean>() {
            @Override
            public void operationComplete(Future<Boolean> future) throws Exception {
                if (!future.isSuccess()) {
                    unsubscribe(subscribeFuture);
                    result.tryFailure(future.cause());
                    return;
                }

                if (future.getNow()) {
                    unsubscribe(subscribeFuture);
                    if (!result.trySuccess(null)) {
                        releaseAsync(permits);
                    }
                    return;
                }

                final RedissonLockEntry entry = getEntry();
                synchronized (entry) {
                    if (entry.getLatch().tryAcquire(permits)) {
                        acquireAsync(permits, subscribeFuture, result);
                    } else {
                        Runnable listener = new Runnable() {
                            @Override
                            public void run() {
                                acquireAsync(permits, subscribeFuture, result);
                            }
                        };
                        entry.addListener(listener);
                    }
                }
            }
        });
    }

    @Override
    public boolean tryAcquire() {
        return tryAcquire(1);
    }

    @Override
    public boolean tryAcquire(int permits) {
        return get(tryAcquireAsync(permits));
    }

    @Override
    public RFuture<Boolean> tryAcquireAsync() {
        return tryAcquireAsync(1);
    }

    @Override
    public RFuture<Boolean> tryAcquireAsync(int permits) {
        if (permits < 0) {
            throw new IllegalArgumentException("Permits amount can't be negative");
        }

        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "local value = redis.call('get', KEYS[1]); "
                        + "if (value ~= false and tonumber(value) >= tonumber(ARGV[1])) then "
                        + "local val = redis.call('decrby', KEYS[1], ARGV[1]); " + "return 1; " + "end; "
                        + "return 0;",
                Collections.<Object>singletonList(getName()), permits);
    }

    @Override
    public RFuture<Boolean> tryAcquireAsync(long waitTime, TimeUnit unit) {
        return tryAcquireAsync(1, waitTime, unit);
    }

    @Override
    public boolean tryAcquire(int permits, long waitTime, TimeUnit unit) throws InterruptedException {
        long time = unit.toMillis(waitTime);
        long current = System.currentTimeMillis();

        if (tryAcquire(permits)) {
            return true;
        }

        time -= (System.currentTimeMillis() - current);
        if (time <= 0) {
            return false;
        }

        current = System.currentTimeMillis();
        RFuture<RedissonLockEntry> future = subscribe();
        if (!await(future, time, TimeUnit.MILLISECONDS)) {
            return false;
        }

        try {
            time -= (System.currentTimeMillis() - current);
            if (time <= 0) {
                return false;
            }

            while (true) {
                current = System.currentTimeMillis();
                if (tryAcquire(permits)) {
                    return true;
                }

                time -= (System.currentTimeMillis() - current);
                if (time <= 0) {
                    return false;
                }

                // waiting for message
                current = System.currentTimeMillis();

                getEntry().getLatch().tryAcquire(permits, time, TimeUnit.MILLISECONDS);

                time -= (System.currentTimeMillis() - current);
                if (time <= 0) {
                    return false;
                }
            }
        } finally {
            unsubscribe(future);
        }
        //        return get(tryAcquireAsync(permits, waitTime, unit));
    }

    @Override
    public RFuture<Boolean> tryAcquireAsync(final int permits, long waitTime, TimeUnit unit) {
        final RPromise<Boolean> result = newPromise();
        final AtomicLong time = new AtomicLong(unit.toMillis(waitTime));
        final long current = System.currentTimeMillis();
        RFuture<Boolean> tryAcquireFuture = tryAcquireAsync(permits);
        tryAcquireFuture.addListener(new FutureListener<Boolean>() {
            @Override
            public void operationComplete(Future<Boolean> future) throws Exception {
                if (!future.isSuccess()) {
                    result.tryFailure(future.cause());
                    return;
                }

                if (future.getNow()) {
                    if (!result.trySuccess(true)) {
                        releaseAsync(permits);
                    }
                    return;
                }

                long elapsed = System.currentTimeMillis() - current;
                time.addAndGet(-elapsed);

                if (time.get() <= 0) {
                    result.trySuccess(false);
                    return;
                }

                final long current = System.currentTimeMillis();
                final AtomicReference<Timeout> futureRef = new AtomicReference<Timeout>();
                final RFuture<RedissonLockEntry> subscribeFuture = subscribe();
                subscribeFuture.addListener(new FutureListener<RedissonLockEntry>() {
                    @Override
                    public void operationComplete(Future<RedissonLockEntry> future) throws Exception {
                        if (!future.isSuccess()) {
                            result.tryFailure(future.cause());
                            return;
                        }

                        if (futureRef.get() != null) {
                            futureRef.get().cancel();
                        }

                        long elapsed = System.currentTimeMillis() - current;
                        time.addAndGet(-elapsed);

                        if (time.get() < 0) {
                            unsubscribe(subscribeFuture);
                            result.trySuccess(false);
                            return;
                        }

                        tryAcquireAsync(time, permits, subscribeFuture, result);
                    }
                });

                if (!subscribeFuture.isDone()) {
                    Timeout scheduledFuture = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
                        @Override
                        public void run(Timeout timeout) throws Exception {
                            if (!subscribeFuture.isDone()) {
                                result.trySuccess(false);
                            }
                        }
                    }, time.get(), TimeUnit.MILLISECONDS);
                    futureRef.set(scheduledFuture);
                }
            }
        });

        return result;
    }

    private RedissonLockEntry getEntry() {
        return semaphorePubSub.getEntry(getName());
    }

    private RFuture<RedissonLockEntry> subscribe() {
        return semaphorePubSub.subscribe(getName(), getChannelName(), commandExecutor.getConnectionManager());
    }

    private void unsubscribe(RFuture<RedissonLockEntry> future) {
        semaphorePubSub.unsubscribe(future.getNow(), getName(), getChannelName(),
                commandExecutor.getConnectionManager());
    }

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

    @Override
    public void release() {
        release(1);
    }

    @Override
    public void release(int permits) {
        get(releaseAsync(permits));
    }

    @Override
    public RFuture<Void> releaseAsync() {
        return releaseAsync(1);
    }

    @Override
    public RFuture<Void> releaseAsync(int permits) {
        if (permits < 0) {
            throw new IllegalArgumentException("Permits amount can't be negative");
        }

        return commandExecutor.evalWriteAsync(getName(), StringCodec.INSTANCE, RedisCommands.EVAL_VOID,
                "local value = redis.call('incrby', KEYS[1], ARGV[1]); "
                        + "redis.call('publish', KEYS[2], value); ",
                Arrays.<Object>asList(getName(), getChannelName()), permits);
    }

    @Override
    public int drainPermits() {
        Long res = commandExecutor.evalWrite(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_LONG,
                "local value = redis.call('get', KEYS[1]); " + "if (value == false or value == 0) then "
                        + "return 0; " + "end; " + "redis.call('set', KEYS[1], 0); " + "return value;",
                Collections.<Object>singletonList(getName()));
        return res.intValue();
    }

    @Override
    public int availablePermits() {
        Long res = commandExecutor.write(getName(), LongCodec.INSTANCE, RedisCommands.GET_LONG, getName());
        return res.intValue();
    }

    @Override
    public void setPermits(int permits) {
        get(setPermitsAsync(permits));
    }

    @Override
    public RFuture<Void> setPermitsAsync(int permits) {
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_VOID,
                "local value = redis.call('get', KEYS[1]); " + "if (value == false or value == 0) then "
                        + "redis.call('set', KEYS[1], ARGV[1]); " + "redis.call('publish', KEYS[2], ARGV[1]); "
                        + "end;",
                Arrays.<Object>asList(getName(), getChannelName()), permits);
    }

    @Override
    public boolean trySetPermits(int permits) {
        return get(trySetPermitsAsync(permits));
    }

    @Override
    public RFuture<Boolean> trySetPermitsAsync(int permits) {
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "local value = redis.call('get', KEYS[1]); " + "if (value == false or value == 0) then "
                        + "redis.call('set', KEYS[1], ARGV[1]); " + "redis.call('publish', KEYS[2], ARGV[1]); "
                        + "return 1;" + "end;" + "return 0;",
                Arrays.<Object>asList(getName(), getChannelName()), permits);
    }

    @Override
    public void reducePermits(int permits) {
        get(reducePermitsAsync(permits));
    }

    @Override
    public RFuture<Void> reducePermitsAsync(int permits) {
        if (permits < 0) {
            throw new IllegalArgumentException();
        }
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_VOID,
                "local value = redis.call('get', KEYS[1]); " + "if (value == false) then " + "value = 0;" + "end;"
                        + "redis.call('set', KEYS[1], value - ARGV[1]); ",
                Arrays.<Object>asList(getName(), getChannelName()), permits);
    }

}