org.apache.accumulo.fate.zookeeper.ZooLock.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.accumulo.fate.zookeeper.ZooLock.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 org.apache.accumulo.fate.zookeeper;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.accumulo.fate.zookeeper.ZooUtil.LockID;
import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeMissingPolicy;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ZooLock implements Watcher {
    private static final Logger log = LoggerFactory.getLogger(ZooLock.class);

    public static final String LOCK_PREFIX = "zlock-";

    public enum LockLossReason {
        LOCK_DELETED, SESSION_EXPIRED
    }

    public interface LockWatcher {
        void lostLock(LockLossReason reason);

        /**
         * lost the ability to monitor the lock node, and its status is unknown
         */
        void unableToMonitorLockNode(Throwable e);
    }

    public interface AsyncLockWatcher extends LockWatcher {
        void acquiredLock();

        void failedToAcquireLock(Exception e);
    }

    private boolean lockWasAcquired;
    final private String path;
    protected final IZooReaderWriter zooKeeper;
    private String lock;
    private LockWatcher lockWatcher;
    private boolean watchingParent = false;
    private String asyncLock;

    public ZooLock(String zookeepers, int timeInMillis, String scheme, byte[] auth, String path) {
        this(new ZooCacheFactory().getZooCache(zookeepers, timeInMillis),
                ZooReaderWriter.getInstance(zookeepers, timeInMillis, scheme, auth), path);
    }

    protected ZooLock(ZooCache zc, IZooReaderWriter zrw, String path) {
        getLockDataZooCache = zc;
        this.path = path;
        zooKeeper = zrw;
        try {
            zooKeeper.getStatus(path, this);
            watchingParent = true;
        } catch (Exception ex) {
            log.warn("Error getting setting initial watch on ZooLock", ex);
            throw new RuntimeException(ex);
        }
    }

    private static class TryLockAsyncLockWatcher implements AsyncLockWatcher {

        boolean acquiredLock = false;
        LockWatcher lw;

        public TryLockAsyncLockWatcher(LockWatcher lw2) {
            this.lw = lw2;
        }

        @Override
        public void acquiredLock() {
            acquiredLock = true;
        }

        @Override
        public void failedToAcquireLock(Exception e) {
        }

        @Override
        public void lostLock(LockLossReason reason) {
            lw.lostLock(reason);
        }

        @Override
        public void unableToMonitorLockNode(Throwable e) {
            lw.unableToMonitorLockNode(e);
        }

    }

    public synchronized boolean tryLock(LockWatcher lw, byte data[]) throws KeeperException, InterruptedException {

        TryLockAsyncLockWatcher tlalw = new TryLockAsyncLockWatcher(lw);

        lockAsync(tlalw, data);

        if (tlalw.acquiredLock) {
            return true;
        }

        if (asyncLock != null) {
            zooKeeper.recursiveDelete(path + "/" + asyncLock, NodeMissingPolicy.SKIP);
            asyncLock = null;
        }

        return false;
    }

    private synchronized void lockAsync(final String myLock, final AsyncLockWatcher lw)
            throws KeeperException, InterruptedException {

        if (asyncLock == null) {
            throw new IllegalStateException("Called lockAsync() when asyncLock == null");
        }

        List<String> children = zooKeeper.getChildren(path);

        if (!children.contains(myLock)) {
            throw new RuntimeException("Lock attempt ephemeral node no longer exist " + myLock);
        }

        Collections.sort(children);
        if (log.isTraceEnabled()) {
            log.trace("Candidate lock nodes");
            for (String child : children) {
                log.trace("- " + child);
            }
        }

        if (children.get(0).equals(myLock)) {
            log.trace("First candidate is my lock, acquiring");
            if (!watchingParent) {
                throw new IllegalStateException("Can not acquire lock, no longer watching parent : " + path);
            }
            this.lockWatcher = lw;
            this.lock = myLock;
            asyncLock = null;
            lockWasAcquired = true;
            lw.acquiredLock();
            return;
        }
        String prev = null;
        for (String child : children) {
            if (child.equals(myLock)) {
                break;
            }

            prev = child;
        }

        final String lockToWatch = path + "/" + prev;
        log.trace("Establishing watch on " + lockToWatch);
        Stat stat = zooKeeper.getStatus(lockToWatch, new Watcher() {

            @Override
            public void process(WatchedEvent event) {
                if (log.isTraceEnabled()) {
                    log.trace("Processing event:");
                    log.trace("- type  " + event.getType());
                    log.trace("- path  " + event.getPath());
                    log.trace("- state " + event.getState());
                }
                boolean renew = true;
                if (event.getType() == EventType.NodeDeleted && event.getPath().equals(lockToWatch)) {
                    log.trace("Detected deletion of " + lockToWatch + ", attempting to acquire lock");
                    synchronized (ZooLock.this) {
                        try {
                            if (asyncLock != null) {
                                lockAsync(myLock, lw);
                            } else if (log.isTraceEnabled()) {
                                log.trace("While waiting for another lock " + lockToWatch + " " + myLock
                                        + " was deleted");
                            }
                        } catch (Exception e) {
                            if (lock == null) {
                                // have not acquired lock yet
                                lw.failedToAcquireLock(e);
                            }
                        }
                    }
                    renew = false;
                }

                if (event.getState() == KeeperState.Expired || event.getState() == KeeperState.Disconnected) {
                    synchronized (ZooLock.this) {
                        if (lock == null) {
                            lw.failedToAcquireLock(new Exception("Zookeeper Session expired / disconnected"));
                        }
                    }
                    renew = false;
                }
                if (renew) {
                    log.trace("Renewing watch on " + lockToWatch);
                    try {
                        Stat restat = zooKeeper.getStatus(lockToWatch, this);
                        if (restat == null) {
                            lockAsync(myLock, lw);
                        }
                    } catch (KeeperException e) {
                        lw.failedToAcquireLock(new Exception("Failed to renew watch on other master node"));
                    } catch (InterruptedException e) {
                        lw.failedToAcquireLock(new Exception("Failed to renew watch on other master node"));
                    }
                }
            }

        });

        if (stat == null)
            lockAsync(myLock, lw);
    }

    private void lostLock(LockLossReason reason) {
        LockWatcher localLw = lockWatcher;
        lock = null;
        lockWatcher = null;

        localLw.lostLock(reason);
    }

    public synchronized void lockAsync(final AsyncLockWatcher lw, byte data[]) {

        if (lockWatcher != null || lock != null || asyncLock != null) {
            throw new IllegalStateException();
        }

        lockWasAcquired = false;

        try {
            final String asyncLockPath = zooKeeper.putEphemeralSequential(path + "/" + LOCK_PREFIX, data);
            log.trace("Ephemeral node " + asyncLockPath + " created");
            Stat stat = zooKeeper.getStatus(asyncLockPath, new Watcher() {

                private void failedToAcquireLock() {
                    lw.failedToAcquireLock(new Exception("Lock deleted before acquired"));
                    asyncLock = null;
                }

                @Override
                public void process(WatchedEvent event) {
                    synchronized (ZooLock.this) {
                        if (lock != null && event.getType() == EventType.NodeDeleted
                                && event.getPath().equals(path + "/" + lock)) {
                            lostLock(LockLossReason.LOCK_DELETED);
                        } else if (asyncLock != null && event.getType() == EventType.NodeDeleted
                                && event.getPath().equals(path + "/" + asyncLock)) {
                            failedToAcquireLock();
                        } else if (event.getState() != KeeperState.Disconnected
                                && event.getState() != KeeperState.Expired && (lock != null || asyncLock != null)) {
                            log.debug("Unexpected event watching lock node " + event + " " + asyncLockPath);
                            try {
                                Stat stat2 = zooKeeper.getStatus(asyncLockPath, this);
                                if (stat2 == null) {
                                    if (lock != null)
                                        lostLock(LockLossReason.LOCK_DELETED);
                                    else if (asyncLock != null)
                                        failedToAcquireLock();
                                }
                            } catch (Throwable e) {
                                lockWatcher.unableToMonitorLockNode(e);
                                log.error("Failed to stat lock node " + asyncLockPath, e);
                            }
                        }

                    }
                }
            });

            if (stat == null) {
                lw.failedToAcquireLock(new Exception("Lock does not exist after create"));
                return;
            }

            asyncLock = asyncLockPath.substring(path.length() + 1);

            lockAsync(asyncLock, lw);

        } catch (KeeperException e) {
            lw.failedToAcquireLock(e);
        } catch (InterruptedException e) {
            lw.failedToAcquireLock(e);
        }
    }

    public synchronized boolean tryToCancelAsyncLockOrUnlock() throws InterruptedException, KeeperException {
        boolean del = false;

        if (asyncLock != null) {
            zooKeeper.recursiveDelete(path + "/" + asyncLock, NodeMissingPolicy.SKIP);
            del = true;
        }

        if (lock != null) {
            unlock();
            del = true;
        }

        return del;
    }

    public synchronized void unlock() throws InterruptedException, KeeperException {
        if (lock == null) {
            throw new IllegalStateException();
        }

        LockWatcher localLw = lockWatcher;
        String localLock = lock;

        lock = null;
        lockWatcher = null;

        zooKeeper.recursiveDelete(path + "/" + localLock, NodeMissingPolicy.SKIP);

        localLw.lostLock(LockLossReason.LOCK_DELETED);
    }

    public synchronized String getLockPath() {
        if (lock == null) {
            return null;
        }
        return path + "/" + lock;
    }

    public synchronized String getLockName() {
        return lock;
    }

    public synchronized LockID getLockID() {
        if (lock == null) {
            throw new IllegalStateException("Lock not held");
        }
        return new LockID(path, lock, zooKeeper.getZooKeeper().getSessionId());
    }

    /**
     * indicates if the lock was acquired in the past.... helps discriminate between the case where the lock was never held, or held and lost....
     *
     * @return true if the lock was aquired, otherwise false.
     */
    public synchronized boolean wasLockAcquired() {
        return lockWasAcquired;
    }

    public synchronized boolean isLocked() {
        return lock != null;
    }

    public synchronized void replaceLockData(byte[] b) throws KeeperException, InterruptedException {
        if (getLockPath() != null)
            zooKeeper.getZooKeeper().setData(getLockPath(), b, -1);
    }

    @Override
    public synchronized void process(WatchedEvent event) {
        log.debug("event " + event.getPath() + " " + event.getType() + " " + event.getState());

        watchingParent = false;

        if (event.getState() == KeeperState.Expired && lock != null) {
            lostLock(LockLossReason.SESSION_EXPIRED);
        } else {

            try { // set the watch on the parent node again
                zooKeeper.getStatus(path, this);
                watchingParent = true;
            } catch (KeeperException.ConnectionLossException ex) {
                // we can't look at the lock because we aren't connected, but our session is still good
                log.warn("lost connection to zookeeper");
            } catch (Exception ex) {
                if (lock != null || asyncLock != null) {
                    lockWatcher.unableToMonitorLockNode(ex);
                    log.error("Error resetting watch on ZooLock " + lock == null ? asyncLock : lock + " " + event,
                            ex);
                }
            }

        }

    }

    public static boolean isLockHeld(ZooCache zc, LockID lid) {

        List<String> children = zc.getChildren(lid.path);

        if (children == null || children.size() == 0) {
            return false;
        }

        children = new ArrayList<>(children);
        Collections.sort(children);

        String lockNode = children.get(0);
        if (!lid.node.equals(lockNode))
            return false;

        Stat stat = new Stat();
        return zc.get(lid.path + "/" + lid.node, stat) != null && stat.getEphemeralOwner() == lid.eid;
    }

    public static byte[] getLockData(ZooKeeper zk, String path) throws KeeperException, InterruptedException {
        List<String> children = zk.getChildren(path, false);

        if (children == null || children.size() == 0) {
            return null;
        }

        Collections.sort(children);

        String lockNode = children.get(0);

        return zk.getData(path + "/" + lockNode, false, null);
    }

    public static byte[] getLockData(org.apache.accumulo.fate.zookeeper.ZooCache zc, String path, Stat stat) {

        List<String> children = zc.getChildren(path);

        if (children == null || children.size() == 0) {
            return null;
        }

        children = new ArrayList<>(children);
        Collections.sort(children);

        String lockNode = children.get(0);

        if (!lockNode.startsWith(LOCK_PREFIX)) {
            throw new RuntimeException("Node " + lockNode + " at " + path + " is not a lock node");
        }

        return zc.get(path + "/" + lockNode, stat);
    }

    public static long getSessionId(ZooCache zc, String path) throws KeeperException, InterruptedException {
        List<String> children = zc.getChildren(path);

        if (children == null || children.size() == 0) {
            return 0;
        }

        children = new ArrayList<>(children);
        Collections.sort(children);

        String lockNode = children.get(0);

        Stat stat = new Stat();
        if (zc.get(path + "/" + lockNode, stat) != null)
            return stat.getEphemeralOwner();
        return 0;
    }

    private static ZooCache getLockDataZooCache;

    public long getSessionId() throws KeeperException, InterruptedException {
        return getSessionId(getLockDataZooCache, path);
    }

    public static void deleteLock(IZooReaderWriter zk, String path) throws InterruptedException, KeeperException {
        List<String> children;

        children = zk.getChildren(path);

        if (children == null || children.size() == 0) {
            throw new IllegalStateException("No lock is held at " + path);
        }

        Collections.sort(children);

        String lockNode = children.get(0);

        if (!lockNode.startsWith(LOCK_PREFIX)) {
            throw new RuntimeException("Node " + lockNode + " at " + path + " is not a lock node");
        }

        zk.recursiveDelete(path + "/" + lockNode, NodeMissingPolicy.SKIP);

    }

    public static boolean deleteLock(IZooReaderWriter zk, String path, String lockData)
            throws InterruptedException, KeeperException {
        List<String> children;

        children = zk.getChildren(path);

        if (children == null || children.size() == 0) {
            throw new IllegalStateException("No lock is held at " + path);
        }

        Collections.sort(children);

        String lockNode = children.get(0);

        if (!lockNode.startsWith(LOCK_PREFIX)) {
            throw new RuntimeException("Node " + lockNode + " at " + path + " is not a lock node");
        }

        byte[] data = zk.getData(path + "/" + lockNode, null);

        if (lockData.equals(new String(data, UTF_8))) {
            zk.recursiveDelete(path + "/" + lockNode, NodeMissingPolicy.FAIL);
            return true;
        }

        return false;
    }
}