io.tilt.minka.business.impl.SemaphoreImpl.java Source code

Java tutorial

Introduction

Here is the source code for io.tilt.minka.business.impl.SemaphoreImpl.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 io.tilt.minka.business.impl;

import static io.tilt.minka.business.Semaphore.Action.ANY;
import static io.tilt.minka.business.Semaphore.Hierarchy.CHILD;
import static io.tilt.minka.business.Semaphore.Hierarchy.PARENT;
import static io.tilt.minka.business.Semaphore.Hierarchy.SIBLING;
import static io.tilt.minka.business.Semaphore.Permission.DENIED;
import static io.tilt.minka.business.Semaphore.Permission.GRANTED;
import static io.tilt.minka.business.Semaphore.Permission.RETRY;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.lang.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.util.concurrent.CycleDetectingLockFactory;
import com.google.common.util.concurrent.CycleDetectingLockFactory.Policies;

import io.tilt.minka.api.Config;
import io.tilt.minka.business.Semaphore;
import io.tilt.minka.business.Semaphore.Action;
import io.tilt.minka.business.Semaphore.Permission;
import io.tilt.minka.business.Semaphore.Rule;
import io.tilt.minka.business.Semaphore.Scope;
import io.tilt.minka.domain.ShardID;
import io.tilt.minka.spectator.Locks;

/**
 * @author Cristian Gonzalez
 * @since Dec 28, 2015
 */
public class SemaphoreImpl extends ServiceImpl implements Semaphore {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private static final long DISTRIBUTED_LOCK_WAIT_MS = 1000l;

    /* for local scope actions */
    private Map<Action, ReentrantLock> locksByAction;
    private final Map<Action, Rule> rules;

    /* for global scope actions */
    private Locks locks;
    private final Config config;
    private final SpectatorSupplier supplier;

    public SemaphoreImpl(final Config config, final SpectatorSupplier supplier, final ShardID shardId) {
        this.config = config;
        this.rules = new HashMap<>();
        getLockingRules().forEach(rule -> this.rules.put(rule.getAction(), rule));
        this.supplier = supplier;
        locksByAction = new HashMap<>();
        final CycleDetectingLockFactory lockFactory = CycleDetectingLockFactory.newInstance(Policies.DISABLED);
        for (Action key : Action.values()) {
            ReentrantLock lock = lockFactory.newReentrantLock(key.toString());
            logger.debug("{}: ({}) Creating lock: {} for Action: {}", getClass().getSimpleName(), shardId,
                    lock.getClass().getSimpleName(), key);
            locksByAction.put(key, lock);
            //locksByAction.put(key, new ReentrantLock());
        }
    }

    @Override
    public void stop() {
        this.locks.close();
    }

    @Override
    public void start() {
        this.locks = new Locks(supplier.get());
    }

    @Override
    public Permission acquireBlocking(Action ia) {
        return acquire_(ia, true);
    }

    @Override
    public synchronized Permission acquire(Action e) {
        return acquire_(e, false);
    }

    private synchronized Permission acquire_(Action action, boolean blockThread) {
        Validate.notNull(action);
        Permission ret = checkState(action);
        if (ret == null) {
            final Rule rule = rules.get(action);
            final List<Action> fellows = rule.getRelated(SIBLING);
            if (fellows == null || fellows.isEmpty()) {
                ret = lock(action, blockThread, action) ? GRANTED : RETRY;
            } else {
                ret = lockRelated(action, blockThread, fellows) && lock(action, blockThread, null) ? GRANTED
                        : RETRY;
            }
        }
        return ret;
    }

    private Permission checkState(final Action action) {
        if (action.getScope() == Scope.LOCAL) {
            final ReentrantLock lock = g(action);
            if (lock.isHeldByCurrentThread()) {
                logger.error("{}: {} lock already acquired by YOU ! CHECK YOUR CODE !", getClass().getSimpleName(),
                        action);
                return DENIED;
                //throw new RuntimeException("Come on check your code consistency pal !");
            } else if (lock.isLocked()) {
                logger.error("{}: {} lock already acquired by {} thread ! and holds: {} more",
                        getClass().getSimpleName(), action, lock.isHeldByCurrentThread() ? "current" : "sibling",
                        lock.getQueueLength());
                return DENIED;
            }
        } else if (action.getScope() == Scope.GLOBAL) {
            // TODO
        }

        // in the case of not being slave to those master to me or any (got that?)
        // i simply let them acquire, otherwise behead it !
        for (final Entry<Action, Rule> entry : rules.entrySet()) {
            final List<Action> related = entry.getValue().getRelated(PARENT);
            if (!related.isEmpty()) {
                if (isLocked(entry.getKey()) && (related.contains(ANY) || related.contains(action))) {
                    if (!rules.get(action).getRelated(CHILD).contains(entry.getKey())) {
                        logger.warn(
                                "{}: {} Cannot be acquired because not Child of previously acquired Parent lock: {}",
                                getClass().getSimpleName(), action, entry.getKey());
                        return DENIED;
                    }
                }
            }
        }

        return null;
    }

    /**
     * this will consider threadlock and actually lock the current thread 
     * @param threadLock
     * @param related      operation dependencies upon a main action relies on to be not running
     * @return             Fail or Success by acquiring the group and releasing acquired locks if any failed       
     */
    private boolean lockRelated(final Action cause, final boolean threadLock, final List<Action> related) {

        final Action[] rollback = new Action[related.size()];
        int i = 0;
        for (final Action action : related) {
            if (lock(action, threadLock, cause)) {
                rollback[i] = action;
            } else {
                for (Action e : rollback) {
                    release_(e, cause);
                }
                return false;
            }
        }
        return true;
    }

    private boolean isLocked(Action action) {
        if (action.getScope() == Scope.LOCAL) {
            return g(action).isLocked();
        } else if (action.getScope() == Scope.GLOBAL) {
            // TODO 
            return false;
        }
        return false;
    }

    private boolean lock(final Action action, final boolean blockThread, final Action cause) {

        if (logger.isDebugEnabled()) {
            if (cause == null) {
                logger.debug("{}: {} is {}", getClass().getSimpleName(), action,
                        blockThread ? "Locking" : "TryLocking");
            } else {
                logger.debug("{}: {} is {}: {}", getClass().getSimpleName(), cause,
                        blockThread ? "Locking" : "TryLocking", action);
            }
        }
        switch (action.getScope()) {
        case GLOBAL:
            return locks.acquireDistributedLock(nameForDistributedLock(action, null), true,
                    DISTRIBUTED_LOCK_WAIT_MS, TimeUnit.MILLISECONDS);
        case LOCAL:
            ReentrantLock lock = g(action);
            if (blockThread) {
                lock.lock();
                return true;
            } else {
                return lock.tryLock();
            }
        default:
            logger.error("{}: what the heck ? --> {}", getClass().getSimpleName(), action.getScope());
            return false;
        }
    }

    @Override
    public void release(Action action) {
        release_(action, null);
    }

    private void release_(Action action, Action cause) {
        Validate.notNull(action);
        // first unlock the dependencies so others may start running
        if (cause == null) { // only when not rolling back to avoid deadlock
            final List<Action> group = rules.get(action).getRelated(SIBLING);
            if (group != null) {
                group.forEach(a -> release_(a, action));
            }
        }
        switch (action.getScope()) {
        case LOCAL:
            // then unlock the main lock
            ReentrantLock lock = g(action);
            if (lock == null) {
                logger.error("{}: Locks not implemented for action: {} ", getClass().getSimpleName(), action);
            } else {
                if (logger.isDebugEnabled() && cause != null) {
                    logger.debug("{}: {} is Releasing: {}", getClass().getSimpleName(), cause, action);
                }
                lock.unlock();
            }
            break;
        case GLOBAL:
            locks.releaseDistributedLock(nameForDistributedLock(action, null));
            break;
        }
    }

    private String nameForDistributedLock(final Action action, final String id) {
        return SpectatorSupplier.MINKA_SUBDOMAIN + "/" + config.getServiceName() + "/" + action.toString()
                + (id == null ? "" : "-" + id);
    }

    private ReentrantLock g(Action a) {
        return locksByAction.get(a);
    }
}