io.tilt.minka.spectator.Locks.java Source code

Java tutorial

Introduction

Here is the source code for io.tilt.minka.spectator.Locks.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.spectator;

import java.io.IOException;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.Validate;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.leader.LeaderLatch;
import org.apache.curator.framework.recipes.leader.LeaderLatch.CloseMode;
import org.apache.curator.framework.recipes.leader.LeaderLatchListener;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Over-curation of apache curator Locking recipes for over-simplified usage
 * 
 * @author Cristian Gonzalez
 * @since Dec 24, 2015
 *
 */
public class Locks extends Spectator {

    private static final String LOCK_PREFIX = "/lock:";
    private static final String LATCH_PREFIX = "/latch:";
    private static final Logger logger = LoggerFactory.getLogger(Locks.class);

    public Locks() {
        super();
    }

    public Locks(final String zookeeperHostPort) {
        super(zookeeperHostPort, null);
    }

    public Locks(final String zookeeperHostPort, String logId) {
        super(zookeeperHostPort, logId);
    }

    /**
     * Use the zookeper's host and port from System environment or properties
     */
    public Locks(Spectator spec) {
        super(spec, spec.getLogId());
    }

    /**
     * Participate 'ethernally' of an election among a cluster of leader candidates for a given duty
     * Like owning a lock, but the process hops between candidates until a leader for that duty is elected, 
     * while a leader is needed, if no other candidate exists, the leader is the first taking the lock.
     * 
     * Runs in a new thread always.
     * Idempotently.
     * 
     * @return      whether the listener participates in the leader meeting or not. True if it was done before.
     */
    public boolean runWhenLeader(final String ensembleName, final ServerCandidate server) {
        boolean participating = checkExistance(LATCH_PREFIX + ensembleName, server);
        if (!participating) {
            participating = createLeaderLatch(LATCH_PREFIX + ensembleName, server);
        }
        return participating;
    }

    /**
     * If you plan to orderly stop leader candidates instead of leaving that work to Spectator/Zookeeper
     * Then you can stop your candidates manually.
     * @param ensembleName
     * @param notifyCandidate
     */
    public void stopCandidateOrLeader(final String ensembleName, boolean notifyCandidate) {
        Iterator<UserInstanceObject> x = getUserMap().get(LATCH_PREFIX + ensembleName).iterator();
        while (x.hasNext()) {
            UserInstanceObject uio = x.next();
            if (uio.getServerCandidate() != null) {
                try {
                    uio.setCallbackListeners(notifyCandidate);
                    uio.getLatch().close();
                } catch (IOException e) {
                    logger.error("{}: ({}) Unexpected while trying to stop candidate", getClass().getSimpleName(),
                            getLogId(), e);
                }
            }
        }
    }

    /**
     * A race between runner candidates, first acquiring the lock will disable others to run.
     * i.e. run only if other duty with same Lock Is Not also running.
     * 
     * @param lockName      a name unique within the timewindow the runner intends to race for run
     * @param runnable      this is executed within the same caller's thread
     * @return              whether or not the runner acquired the lock and run  
     */
    public boolean runOnLockRace(final String lockName, final Runnable runnable) {
        return createLock(lockName, runnable, true, 0, TimeUnit.MICROSECONDS);
    }

    /**
     * A race between runner candidates, first acquiring the lock will disable others to run within the 
     * time window since run call and lock-wait time.
     * i.e. run only if no other duty is present or present withing a time window.
     * 
     * @param lockName      a name unique within the timewindow the runner intends to race for run
     * @param runnable      this is executed within the same caller's thread
     * @param lockWaitMs    time to wait for the lock to be released if already acquired
     * @return              whether or not the runner acquired the lock and run  
     */
    public boolean runOnLockRace(final String lockName, final Runnable runnable, final long lockWaitMs) {
        return createLock(lockName, runnable, true, lockWaitMs, TimeUnit.MILLISECONDS);
    }

    /**
     * The runner candidate waits for the lock to be released by others if already acquired
     * 
     * @param lockName      a name unique within the timewindow the runner intends to race for run
     * @param runnable      this is executed within the same caller's thread
     * @return              whether or not the runner acquired the lock and run  
     */
    public boolean runWhenUnlocked(final String lockName, final Runnable runnable) {
        return createLock(lockName, runnable, false, 0, TimeUnit.MILLISECONDS);
    }

    private boolean createLeaderLatch(final String ensembleName, final ServerCandidate server) {
        boolean participating = false;
        try {
            final CuratorFramework client = getClient();
            if (client == null) {
                logger.error("{}: ({}) Cannot use Distributed utilities before setting Zookeeper connection",
                        getClass().getSimpleName(), getLogId());
                return false;
            }

            final String latchNameObj = ensembleName;
            // let the notification option available to further client's manual close's option
            final LeaderLatch latch = new LeaderLatch(client, latchNameObj, String.valueOf(server.hashCode()),
                    CloseMode.NOTIFY_LEADER);
            UserInstanceObject uio = new UserInstanceObject(latch, server);
            latch.addListener(new LeaderLatchListener() {
                public void notLeader() {
                    logger.info("Locks: ({}) LeaderLatch on: {} removing leadership from: {}", getLogId(),
                            latchNameObj, server.getClass().getName());
                    if (uio.callbackListeners()) {
                        server.stop();
                    } else {
                        logger.info("Locks: ({}) Avoiding calling back user's listeners", getLogId());
                    }
                }

                public void isLeader() {
                    logger.info("Locks: ({}) LeaderLatch on: {} giving leadership to: {}", getLogId(), latchNameObj,
                            server.getClass().getSimpleName());
                    server.start();
                }
            });
            // save it for future ref.
            add(latchNameObj, uio);
            latch.start(); // participate of election
            participating = true;
            if (logger.isDebugEnabled()) {
                logger.debug("{}: ({}) Participating of election process in lock {} ", getClass().getSimpleName(),
                        getLogId(), latchNameObj);
            }
        } catch (Exception e) {
            if (isStarted() && isConnected()) {
                logger.error("{}: ({}) Unexpected while registering: {} with leader listener: {}",
                        getClass().getSimpleName(), getLogId(), server.getClass().getSimpleName(), ensembleName, e);
            } else {
                logger.error("{}: ({}) Zookeeper Disconection: while registering: {} with leader listener: {}",
                        getClass().getSimpleName(), getLogId(), server.getClass().getSimpleName(), ensembleName,
                        e.getMessage());
            }
        }
        return participating;
    }

    public boolean acquireDistributedLock(final String lockName, final boolean lockExclusively, final long lockWait,
            final TimeUnit lockWaitTimeUnit) {

        Validate.notNull(lockName, "the duty Name is required");

        boolean run = false;
        final CuratorFramework client = getClient();
        if (client == null) {
            logger.error("{}: ({}) Cannot use Distributed utilities before setting Zookeeper connection",
                    getClass().getSimpleName(), getLogId());
            return false;
        }

        final String lockNameObj = LOCK_PREFIX + lockName;
        try {
            InterProcessMutex mutex = new InterProcessMutex(client, lockNameObj);
            if (lockExclusively) {
                if (run = mutex.acquire(lockWait, lockWaitTimeUnit)) {
                    run = true;
                    final UserInstanceObject uo = new UserInstanceObject(mutex, null);
                    add(lockNameObj, uo);
                } else {
                    logger.warn(lockNameObj, "lock was already acquired");
                    run = false;
                }
            } else {
                mutex.acquire(); // wait until others release
                run = true;
            }
        } catch (Exception e) {
            if (isStarted() && isConnected()) {
                logger.error("{}: ({}) Unexpected while registering leader listener: {}",
                        getClass().getSimpleName(), getLogId(), lockNameObj, e);
            } else {
                logger.error("{}: ({}) Zookeper Disconnection: while registering leader listener: {}",
                        getClass().getSimpleName(), getLogId(), lockNameObj, e.getMessage());
            }
        }
        return run;
    }

    public boolean releaseDistributedLock(final String lockName) {
        final String lockNameObj = LOCK_PREFIX + lockName;
        Set<UserInstanceObject> set = getUserMap().get(lockNameObj);
        if (set == null) {
            logger.error("{}: ({}) Lock: {} is unknown or never acquired", getClass().getSimpleName(), getLogId(),
                    lockName);
            return false;
        }

        for (UserInstanceObject uio : set) {
            try {
                final InterProcessMutex mutex = uio.getMutex();
                if (uio.getMutex() != null) {
                    mutex.release();
                } else {
                    logger.error("{}: ({}) Lock: {} jas mo mutex object, never acquired ?",
                            getClass().getSimpleName(), getLogId(), lockName);
                }
                remove(lockNameObj, uio);
            } catch (Exception e) {
                if (isStarted() && isConnected()) {
                    logger.error("{}: ({}) Unexpected while releasing mutex: {}", getClass().getSimpleName(),
                            getLogId(), lockNameObj, e);
                } else {
                    logger.error("{}: ({}) Zookeper Disconnection: while releasing mutex: {}",
                            getClass().getSimpleName(), getLogId(), lockNameObj, e.getMessage());
                }
            }
        }
        return true;
    }

    private boolean createLock(final String lockName, final Runnable runnable, final boolean lockExclusively,
            final long lockWait, final TimeUnit lockWaitTimeUnit) {

        Validate.notNull(lockName, "the duty Name is required");
        Validate.notNull(runnable, "the duty Listener is required");

        boolean run = false;
        final CuratorFramework client = getClient();
        if (client == null) {
            logger.error("{}: ({}) Cannot use Distributed utilities before setting Zookeeper connection",
                    getClass().getSimpleName(), getLogId());
            return false;
        }

        if (checkExistance(LOCK_PREFIX + lockName, runnable)) {
            throw new RuntimeException("Cannot create lock with existing name: " + lockName
                    + " and the same listener! : " + runnable.toString());
        }

        InterProcessMutex mutex = null;
        final UserInstanceObject uo = new UserInstanceObject(mutex, runnable);

        final String lockNameObj = LOCK_PREFIX + lockName;
        try {
            add(lockNameObj, uo);
            mutex = new InterProcessMutex(client, lockNameObj);
            if (lockExclusively) {
                // i.e.: if other process has the lock: EXIT FAST
                if (run = mutex.acquire(lockWait, lockWaitTimeUnit)) {
                    runnable.run();
                } else {
                }
            } else {
                mutex.acquire(); // wait until others release
                runnable.run();
                run = true;
            }
        } catch (Exception e) {
            if (isStarted() && isConnected()) {
                logger.error("{}: ({}) Unexpected while registering leader listener: {}",
                        getClass().getSimpleName(), getLogId(), lockNameObj, e);
            } else {
                logger.error("{}: ({}) Zookeper Disconnection: while registering leader listener: {}",
                        getClass().getSimpleName(), getLogId(), lockNameObj, e.getMessage());
            }
        } finally {
            try {
                if (mutex != null && mutex.isAcquiredInThisProcess()) {
                    mutex.release();
                }
                remove(lockNameObj, uo);
                // just for consistency
                //runnable.stop();
            } catch (Exception e) {
                if (isStarted() && isConnected()) {
                    logger.error("{}: ({}) Unexpected while releasing mutex: {}", getClass().getSimpleName(),
                            getLogId(), lockNameObj, e);
                } else {
                    logger.error("{}: ({}) Zookeper Disconnection: while releasing mutex: {}",
                            getClass().getSimpleName(), getLogId(), lockNameObj, e.getMessage());
                }
            }
        }
        return run;
    }

}