/* * Eoulsan development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public License version 2.1 or * later and CeCILL-C. This should be distributed with the code. * If you do not have a copy, see: * * http://www.gnu.org/licenses/lgpl-2.1.txt * http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.txt * * Copyright for this code is held jointly by the Genomic platform * of the Institut de Biologie de l'École normale supérieure and * the individual authors. These should be listed in @author doc * comments. * * For more information on the Eoulsan project and its aims, * or to join the Eoulsan Google group, visit the home page * at: * * http://outils.genomique.biologie.ens.fr/eoulsan * */ package fr.ens.biologie.genomique.eoulsan.util.locker; import java.io.IOException; import java.util.Collections; import java.util.List; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.ZooKeeper; /** * This class implements a locker using Zookeeper. See {@link <a href= * "http://altamiracorp.com/blog/employee-posts/distributed-lock-using-zookeeper"> * blog post</a>} * @author Laurent Jourdren * @since 2.0 */ public class ZooKeeperLocker implements Locker, Watcher { private final ZooKeeper zk; private final String lockBasePath; private final String lockName; private final boolean sequentialLockName; private String lockPath; private boolean response; @Override public void lock() throws IOException { // Test if the connection is alive if (!this.response) { throw new IOException("Connection to Zookeeper is not alive"); } try { if (this.zk.exists(this.lockBasePath, false) == null) { this.zk.create(this.lockBasePath, null, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } // lockPath will be different than (lockBasePath + "/" + lockName) because // of the sequence number ZooKeeper appends this.lockPath = this.zk.create(this.lockBasePath + "/" + this.lockName, null, Ids.OPEN_ACL_UNSAFE, this.sequentialLockName ? CreateMode.EPHEMERAL_SEQUENTIAL : CreateMode.EPHEMERAL); final Object lock = new Object(); synchronized (lock) { while (true) { List<String> nodes = this.zk.getChildren(this.lockBasePath, new Watcher() { @Override public void process(final WatchedEvent event) { synchronized (lock) { lock.notifyAll(); } } }); Collections.sort(nodes); if (this.lockPath.endsWith(nodes.get(0))) { return; } lock.wait(); } } } catch (KeeperException | InterruptedException e) { throw new IOException(e); } } @Override public void unlock() throws IOException { try { this.zk.delete(this.lockPath, -1); this.lockPath = null; this.zk.close(); this.response = false; } catch (KeeperException | InterruptedException e) { throw new IOException(e); } } // // Watcher method // @Override public void process(final WatchedEvent event) { if (event.getState() == KeeperState.SyncConnected) { this.response = true; } } // // Constructor // /** * Public constructor. * @throws IOException if an error occurs while creating the ZooKeeper * connection */ public ZooKeeperLocker(final String connectString, final int sessionTimeout, final String lockBasePath, final String lockName) throws IOException { this(connectString, sessionTimeout, lockBasePath, lockName, true); } /** * Public constructor. * @param connectString Zookeeper connection string * @param sessionTimeout session time out * @param lockBasePath lock base path * @param lockName lock name * @param sequentialLockName sequential lock * @throws IOException if an error occurs while creating the ZooKeeper * connection */ public ZooKeeperLocker(final String connectString, final int sessionTimeout, final String lockBasePath, final String lockName, final boolean sequentialLockName) throws IOException { this.lockBasePath = lockBasePath; this.lockName = lockName; this.sequentialLockName = sequentialLockName; this.zk = new ZooKeeper(connectString, sessionTimeout, this); // Try to connect to ZooKeeper server in the next 30 seconds int count = 0; while (!this.response) { if (count > 30) { throw new IOException("Unable to connect to Zookeeper"); } try { Thread.sleep(1000); } catch (InterruptedException e) { throw new IOException(e); } count++; } } }