/** * Factory method for retrieving an instance of ZooKeeperClient connected to the provided quorum * address. Will reuse existing connections if possible. The caller is responsible for * releasing the ZooKeeperClient. * * @param address of ZooKeeper quorum. * @return a ZooKeeperClient. */ public static ZooKeeperClient getZooKeeperClient(String address) { synchronized (CACHE_MONITOR) { final ZooKeeperClient existingClient = ZOOKEEPER_CACHE.get(address); if (existingClient == null) { final ZooKeeperClient newClient = new ZooKeeperClient(address).open(); ZOOKEEPER_CACHE.put(address, newClient); return newClient.retain(); } else { return existingClient.retain(); } } }
/** {@inheritDoc} */ @Override protected void finalize() throws Throwable { synchronized (CACHE_MONITOR) { synchronized (mMonitor) { if (mState != State.CLOSED) { LOG.warn("Finalizing unreleased ZooKeeperClient {}.", this); close(); } } } super.finalize(); } }
/** * Creates a ZooKeeper lock node. * * @param path ZooKeeper lock node path prefix. * @return the created ZooKeeper lock node path. * @throws KeeperException on error. */ private File createZKLockNode(File path) throws KeeperException { boolean parentCreated = false; while (true) { try { return mZKClient.create(path, EMPTY, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); } catch (NoNodeException nne) { LOG.debug("Cannot create ZooKeeper lock node {}: parent node does not exist.", path); Preconditions.checkState(!parentCreated); // creates the parent node at most once. mZKClient.createNodeRecursively(path.getParentFile()); parentCreated = true; } } }
/** * Initializes a new table layout monitor. * * @param zkClient ZooKeeper client. * @throws KeeperException on unrecoverable ZooKeeper error. */ public ZooKeeperMonitor(ZooKeeperClient zkClient) throws KeeperException { this.mZKClient = zkClient; this.mZKClient.createNodeRecursively(ROOT_ZOOKEEPER_PATH); // ZooKeeperClient.retain() should be the last line of the constructor. this.mZKClient.retain(); }
/** {@inheritDoc} */ @Override public void close() throws IOException { synchronized (mMonitor) { if (mCurrentNode != null) { try { if (ZooKeeperMonitor.this.mZKClient.exists(mCurrentNode) != null) { ZooKeeperMonitor.this.mZKClient.delete(mCurrentNode, -1); } } catch (KeeperException e) { throw new IOException(e); } } } } }
/** * Removes a ZooKeeper node and all of its children, if necessary. * * @param path of the node to remove. * @throws KeeperException on I/O error. */ public void deleteNodeRecursively(File path) throws KeeperException { Stat stat = exists(path); // Race condition if someone else updates the znode in the meantime if (stat == null) { return; } List<String> children = getChildren(path, null, null); for (String child : children) { deleteNodeRecursively(new File(path, child)); } delete(path, stat.getVersion()); }
/** * Creates a ZooKeeper node and all its parents, if necessary. * * @param path of the node to create. * @throws KeeperException on I/O error. */ public void createNodeRecursively(File path) throws KeeperException { if (exists(path) != null) { return; } if (path.getPath().equals("/")) { // No need to create the root node "/" : return; } final File parent = path.getParentFile(); if (parent != null) { createNodeRecursively(parent); } try { LOG.debug("Creating ZooKeeper node: {}", path); final File createdPath = this.create(path, EMPTY_BYTES, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); Preconditions.checkState(createdPath.equals(path)); } catch (NodeExistsException exn) { LOG.debug("ZooKeeper node already exists: {}", path); } }
new TreeSet<String>(mZKClient.getChildren(mLockDir, null, null)); final String[] children = childrenSet.toArray(new String[childrenSet.size()]); LOG.debug("{}: lock queue: {}", this, childrenSet); final File preceding = new File(mLockDir, children[index - 1]); LOG.debug("{}: waiting for preceding node {} to disappear", this, preceding); if (mZKClient.exists(preceding, mLockWatcher) != null) { if (absoluteDeadline > 0.0) { final long timeLeftMS = (long) ((absoluteDeadline - Time.now()) * 1000); LOG.debug("{}: out of time while acquiring lock, deleting {}", this, mCreatedPath); mZKClient.delete(mCreatedPath, -1); // -1 means any version mCreatedPath = null; return false;
/** * Notifies the users of a table of a new layout. * * <p> * The caller must ensure proper locking of table layout update operations through * {@link #newTableLayoutUpdateLock(FijiURI)}. * </p> * * @param tableURI Notify the users of the table with this URI. * @param layout Encoded layout update for the table with the specified URI. * @param version of the current table layout. * @throws KeeperException on unrecoverable ZooKeeper error. */ public void notifyNewTableLayout(FijiURI tableURI, byte[] layout, int version) throws KeeperException { final File layoutPath = getTableLayoutFile(tableURI); this.mZKClient.createNodeRecursively(layoutPath); // This should not be needed if we add a lock for layout updates. final Stat updateStat = this.mZKClient.setData(layoutPath, layout, version); LOG.debug("Updated layout for table {}. Layout version is {}.", tableURI, updateStat.getVersion()); }
/** * Unregisters this table user from ZooKeeper. * * @throws IOException on unrecoverable ZooKeeper error. */ private void unregister() throws IOException { synchronized (mMonitor) { if (mCurrentNode != null) { try { ZooKeeperMonitor.this.mZKClient.delete(mCurrentNode, -1); LOG.debug("TableUserRegistration node {} removed.", mCurrentNode); mCurrentNode = null; } catch (KeeperException e) { throw new IOException(e); } } } }
/** * Starts the ZooKeeper client. * * @return this */ private ZooKeeperClient open() { synchronized (mMonitor) { Preconditions.checkState(mState == State.INITIALIZED, "Cannot open ZooKeeperClient in state %s.", mState); mState = State.OPEN; Preconditions.checkState(mZKClient == null); createZKClient(); } return this; }
/** {@inheritDoc} */ @Override public void run() { try { ZooKeeperMonitor.this.mZKClient.createNodeRecursively(mUsersDir); } catch (KeeperException ke) { LOG.error("Unrecoverable ZooKeeper error: {}", ke.getMessage()); throw new RuntimeException(ke); } registerWatcher(); } };
/** * Registers a ZooKeeper watcher for the specified table's layout. * * <p> Retries on ZooKeeper failure (no deadline, no limit). </p> * <p> Dies whenever an exception pops up while running a handler. </p> */ private void registerWatcher() { try { final byte[] layoutUpdate = mZKClient.getData(mTableLayoutFile, mWatcher, mLayoutStat); if (!Arrays.equals(mLatestLayout, layoutUpdate)) { // Layout update may not be changed in the case where this was triggered by a ZooKeeper // connection state change. LOG.debug("Received layout update for table {}: {}.", mTableURI, Bytes.toStringBinary(layoutUpdate)); mLatestLayout = layoutUpdate; // This assumes handlers do not let exceptions pop up: mHandler.update(layoutUpdate); } } catch (KeeperException.NoNodeException nne) { LOG.info("Tracked table layout node for table {} has been removed. Tracking will cease.", mTableURI); } catch (KeeperException ke) { LOG.error("Unrecoverable ZooKeeper error: {}", ke.getMessage()); } }
ZooKeeperMonitor.this.mZKClient.getChildren(mUsersDir, mWatcher, mStat); LOG.debug("Received users update for table {}: {}.", mTableURI, zkNodeNames);
/** * Releases the lock. * * @throws InterruptedException if the thread is interrupted. * @throws KeeperException on error. */ private void unlockInternal() throws InterruptedException, KeeperException { File pathToDelete = null; synchronized (this) { Preconditions.checkState(null != mCreatedPath, "unlock() cannot be called while lock is unlocked."); pathToDelete = mCreatedPath; LOG.debug("Releasing lock {}: deleting {}", this, mCreatedPath); mCreatedPath = null; } mZKClient.delete(pathToDelete, -1); // -1 means any version }
/** * Register this table user with ZooKeeper. * * @param layoutID layout id known by table user. * @throws IOException on unrecoverable ZooKeeper error. */ private void register(String layoutID) throws IOException { synchronized (mMonitor) { try { final File usersDir = getTableUsersDir(mTableURI); LOG.debug("Registering user '{}' for Fiji table '{}' with layout ID '{}'.", mUserID, mTableURI, layoutID); ZooKeeperMonitor.this.mZKClient.createNodeRecursively(usersDir); final String nodeName = makeZKNodeName(mUserID, layoutID); final File nodePath = new File(usersDir, nodeName); final byte[] data = EMPTY_BYTES; mCurrentNode = ZooKeeperMonitor.this.mZKClient.create( nodePath, data, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); } catch (KeeperException e) { throw new IOException(e); } } }
/** {@inheritDoc} */ @Override public void release() throws IOException { LOG.debug("Releasing {}", this); synchronized (CACHE_MONITOR) { synchronized (mMonitor) { Preconditions.checkState(mState == State.OPEN, "Cannot release ZooKeeperClient %s in state %s.", this, mState); mRetainCount -= 1; if (mRetainCount == 0) { close(); } } } }
/** * Start this instance user registration. * * @throws IOException on unrecoverable ZooKeeper error. */ public void start() throws IOException { synchronized (mMonitor) { try { final File usersDir = getInstanceUsersDir(mInstanceURI); LOG.debug("Registering user '{}' for Fiji instance '{}' with system version '{}'.", mUserID, mInstanceURI, mSystemVersion); ZooKeeperMonitor.this.mZKClient.createNodeRecursively(usersDir); final String nodeName = makeZKNodeName(mUserID, mSystemVersion); final File nodePath = new File(usersDir, nodeName); mCurrentNode = ZooKeeperMonitor.this.mZKClient.create( nodePath, EMPTY_BYTES, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); } catch (KeeperException e) { throw new IOException(e); } } }