Package org.redisson

Source Code of org.redisson.RedissonCountDownLatch

/**
* 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.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

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

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

/**
* Distributed alternative to the {@link java.util.concurrent.CountDownLatch}
*
* It has a advantage over {@link java.util.concurrent.CountDownLatch} --
* count can be reset via {@link #trySetCount}.
*
* @author Nikita Koksharov
*
*/
public class RedissonCountDownLatch extends RedissonObject implements RCountDownLatch {

    private final String groupName = "redisson_countdownlatch_";

    private static final Integer zeroCountMessage = 0;
    private static final Integer newCountMessage = 1;

    private static final ConcurrentMap<String, RedissonCountDownLatchEntry> ENTRIES = new ConcurrentHashMap<String, RedissonCountDownLatchEntry>();
   
    private final UUID id;

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

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

        Promise<Boolean> newPromise = newPromise();
        final RedissonCountDownLatchEntry value = new RedissonCountDownLatchEntry(newPromise);
        value.aquire();
        RedissonCountDownLatchEntry oldValue = ENTRIES.putIfAbsent(getEntryName(), value);
        if (oldValue != null) {
            Promise<Boolean> oldPromise = aquire();
            if (oldPromise == null) {
                return subscribe();
            }
            return oldPromise;
        }
       
        RedisPubSubAdapter<Integer> listener = new RedisPubSubAdapter<Integer>() {

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

            @Override
            public void message(String channel, Integer message) {
                if (!getChannelName().equals(channel)) {
                    return;
                }
                if (message.equals(zeroCountMessage)) {
                    value.getLatch().open();
                }
                if (message.equals(newCountMessage)) {
                    value.getLatch().close();
                }
            }

        };

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

    private void release() {
        while (true) {
            RedissonCountDownLatchEntry entry = ENTRIES.get(getEntryName());
            if (entry == null) {
                return;
            }
            RedissonCountDownLatchEntry newEntry = new RedissonCountDownLatchEntry(entry);
            newEntry.release();
            if (ENTRIES.replace(getEntryName(), entry, newEntry)) {
                if (newEntry.isFree()
                        && ENTRIES.remove(getEntryName(), newEntry)) {
                    Future future = connectionManager.unsubscribe(getChannelName());
                    future.awaitUninterruptibly();
                }
                return;
            }
        }
    }
   
    private Promise<Boolean> aquire() {
        while (true) {
            RedissonCountDownLatchEntry entry = ENTRIES.get(getEntryName());
            if (entry != null) {
                RedissonCountDownLatchEntry newEntry = new RedissonCountDownLatchEntry(entry);
                newEntry.aquire();
                if (ENTRIES.replace(getEntryName(), entry, newEntry)) {
                    return newEntry.getPromise();
                }
            } else {
                return null;
            }
        }
    }

    public void await() throws InterruptedException {
        Future<Boolean> promise = subscribe();
        try {
            promise.await();
           
            while (getCountInner() > 0) {
                // waiting for open state
                RedissonCountDownLatchEntry entry = ENTRIES.get(getEntryName());
                if (entry != null) {
                    entry.getLatch().await();
                }
            }
        } finally {
            release();
        }
    }


    @Override
    public boolean await(long time, TimeUnit unit) throws InterruptedException {
        Future<Boolean> promise = subscribe();
        try {
            if (!promise.await(time, unit)) {
                return false;
            }
           
            time = unit.toMillis(time);
            while (getCountInner() > 0) {
                if (time <= 0) {
                    return false;
                }
                long current = System.currentTimeMillis();
                // waiting for open state
                RedissonCountDownLatchEntry entry = ENTRIES.get(getEntryName());
                if (entry != null) {
                    entry.getLatch().await(time, TimeUnit.MILLISECONDS);
                }

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

    @Override
    public void countDown() {
        if (getCount() <= 0) {
            return;
        }

        connectionManager.write(new SyncOperation<Object, Void>() {
            @Override
            public Void execute(RedisConnection<Object, Object> conn) {
                Long val = conn.decr(getName());
                if (val == 0) {
                    conn.multi();
                    conn.del(getName());
                    conn.publish(getChannelName(), zeroCountMessage);
                    if (conn.exec().size() != 2) {
                        throw new IllegalStateException();
                    }
                } else if (val < 0) {
                    conn.del(getName());
                }
                return null;
            }
        });
    }

    private String getEntryName() {
        return id + getName();
    }
   
    private String getChannelName() {
        return groupName + getName();
    }

    @Override
    public long getCount() {
        return getCountInner();
    }

    private long getCountInner() {
        Number val = connectionManager.read(new ResultOperation<Number, Number>() {
            @Override
            protected Future<Number> execute(RedisAsyncConnection<Object, Number> async) {
                return async.get(getName());
            }
        });
       
        if (val == null) {
            return 0;
        }
        return val.longValue();
    }

    @Override
    public boolean trySetCount(final long count) {
        return connectionManager.write(new SyncOperation<Object, Boolean>() {

            @Override
            public Boolean execute(RedisConnection<Object, Object> conn) {
                conn.watch(getName());
                Long oldValue = (Long) conn.get(getName());
                if (oldValue != null) {
                    conn.unwatch();
                    return false;
                }
                conn.multi();
                conn.set(getName(), count);
                conn.publish(getChannelName(), newCountMessage);
                return conn.exec().size() == 2;
            }
        });
    }
   
    @Override
    public void delete() {
        connectionManager.write(new SyncOperation<Object, Void>() {
            @Override
            public Void execute(RedisConnection<Object, Object> conn) {
                conn.multi();
                conn.del(getName());
                conn.publish(getChannelName(), zeroCountMessage);
                if (conn.exec().size() != 2) {
                    throw new IllegalStateException();
                }
                return null;
            }
        });
    }

}
TOP

Related Classes of org.redisson.RedissonCountDownLatch

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.