View Javadoc

1   /**
2    * Copyright The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.zookeeper.lock;
21  
22  import java.io.IOException;
23  import java.util.Comparator;
24  import java.util.List;
25  import java.util.concurrent.CountDownLatch;
26  import java.util.concurrent.TimeUnit;
27  import java.util.concurrent.atomic.AtomicReference;
28  
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.apache.hadoop.classification.InterfaceAudience;
32  import org.apache.hadoop.hbase.InterProcessLock;
33  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
34  import org.apache.hadoop.hbase.zookeeper.DeletionListener;
35  import org.apache.hadoop.hbase.zookeeper.ZKUtil;
36  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
37  import org.apache.zookeeper.CreateMode;
38  import org.apache.zookeeper.KeeperException;
39  import org.apache.zookeeper.KeeperException.BadVersionException;
40  import org.apache.zookeeper.data.Stat;
41  
42  import com.google.common.base.Preconditions;
43  
44  /**
45   * ZooKeeper based HLock implementation. Based on the Shared Locks recipe.
46   * (see:
47   * <a href="http://zookeeper.apache.org/doc/trunk/recipes.html">
48   * ZooKeeper Recipes and Solutions
49   * </a>)
50   */
51  @InterfaceAudience.Private
52  public abstract class ZKInterProcessLockBase implements InterProcessLock {
53  
54    private static final Log LOG = LogFactory.getLog(ZKInterProcessLockBase.class);
55  
56    /** ZNode prefix used by processes acquiring reader locks */
57    protected static final String READ_LOCK_CHILD_NODE_PREFIX = "read-";
58  
59    /** ZNode prefix used by processes acquiring writer locks */
60    protected static final String WRITE_LOCK_CHILD_NODE_PREFIX = "write-";
61  
62    protected final ZooKeeperWatcher zkWatcher;
63    protected final String parentLockNode;
64    protected final String fullyQualifiedZNode;
65    protected final byte[] metadata;
66    protected final MetadataHandler handler;
67  
68    // If we acquire a lock, update this field
69    protected final AtomicReference<AcquiredLock> acquiredLock =
70        new AtomicReference<AcquiredLock>(null);
71  
72    /**
73     * Represents information about a lock held by this thread.
74     */
75    protected static class AcquiredLock {
76      private final String path;
77      private final int version;
78  
79      /**
80       * Store information about a lock.
81       * @param path The path to a lock's ZNode
82       * @param version The current version of the lock's ZNode
83       */
84      public AcquiredLock(String path, int version) {
85        this.path = path;
86        this.version = version;
87      }
88  
89      public String getPath() {
90        return path;
91      }
92  
93      public int getVersion() {
94        return version;
95      }
96  
97      @Override
98      public String toString() {
99        return "AcquiredLockInfo{" +
100           "path='" + path + '\'' +
101           ", version=" + version +
102           '}';
103     }
104   }
105 
106   protected static class ZNodeComparator implements Comparator<String> {
107 
108     public static final ZNodeComparator COMPARATOR = new ZNodeComparator();
109 
110     private ZNodeComparator() {
111     }
112 
113     /** Parses sequenceId from the znode name. Zookeeper documentation
114      * states: The sequence number is always fixed length of 10 digits, 0 padded
115      */
116     public static int getChildSequenceId(String childZNode) {
117       Preconditions.checkNotNull(childZNode);
118       assert childZNode.length() >= 10;
119       String sequenceIdStr = childZNode.substring(childZNode.length() - 10);
120       return Integer.parseInt(sequenceIdStr);
121     }
122 
123     @Override
124     public int compare(String zNode1, String zNode2) {
125       int seq1 = getChildSequenceId(zNode1);
126       int seq2 = getChildSequenceId(zNode2);
127       return seq1 - seq2;
128     }
129   }
130 
131   /**
132    * Called by implementing classes.
133    * @param zkWatcher
134    * @param parentLockNode The lock ZNode path
135    * @param metadata
136    * @param handler
137    * @param childNode The prefix for child nodes created under the parent
138    */
139   protected ZKInterProcessLockBase(ZooKeeperWatcher zkWatcher,
140       String parentLockNode, byte[] metadata, MetadataHandler handler, String childNode) {
141     this.zkWatcher = zkWatcher;
142     this.parentLockNode = parentLockNode;
143     this.fullyQualifiedZNode = ZKUtil.joinZNode(parentLockNode, childNode);
144     this.metadata = metadata;
145     this.handler = handler;
146   }
147 
148   /**
149    * {@inheritDoc}
150    */
151   @Override
152   public void acquire() throws IOException, InterruptedException {
153     tryAcquire(-1);
154   }
155 
156   /**
157    * {@inheritDoc}
158    */
159   @Override
160   public boolean tryAcquire(long timeoutMs)
161   throws IOException, InterruptedException {
162     boolean hasTimeout = timeoutMs != -1;
163     long waitUntilMs =
164         hasTimeout ?EnvironmentEdgeManager.currentTimeMillis() + timeoutMs : -1;
165     String createdZNode;
166     try {
167       createdZNode = createLockZNode();
168     } catch (KeeperException ex) {
169       throw new IOException("Failed to create znode: " + fullyQualifiedZNode, ex);
170     }
171     while (true) {
172       List<String> children;
173       try {
174         children = ZKUtil.listChildrenNoWatch(zkWatcher, parentLockNode);
175       } catch (KeeperException e) {
176         LOG.error("Unexpected ZooKeeper error when listing children", e);
177         throw new IOException("Unexpected ZooKeeper exception", e);
178       }
179       String pathToWatch;
180       if ((pathToWatch = getLockPath(createdZNode, children)) == null) {
181         break;
182       }
183       CountDownLatch deletedLatch = new CountDownLatch(1);
184       String zkPathToWatch =
185           ZKUtil.joinZNode(parentLockNode, pathToWatch);
186       DeletionListener deletionListener =
187           new DeletionListener(zkWatcher, zkPathToWatch, deletedLatch);
188       zkWatcher.registerListener(deletionListener);
189       try {
190         if (ZKUtil.setWatchIfNodeExists(zkWatcher, zkPathToWatch)) {
191           // Wait for the watcher to fire
192           if (hasTimeout) {
193             long remainingMs = waitUntilMs - EnvironmentEdgeManager.currentTimeMillis();
194             if (remainingMs < 0 ||
195                 !deletedLatch.await(remainingMs, TimeUnit.MILLISECONDS)) {
196               LOG.warn("Unable to acquire the lock in " + timeoutMs +
197                   " milliseconds.");
198               try {
199                 ZKUtil.deleteNode(zkWatcher, createdZNode);
200               } catch (KeeperException e) {
201                 LOG.warn("Unable to remove ZNode " + createdZNode);
202               }
203               return false;
204             }
205           } else {
206             deletedLatch.await();
207           }
208           if (deletionListener.hasException()) {
209             Throwable t = deletionListener.getException();
210             throw new IOException("Exception in the watcher", t);
211           }
212         }
213       } catch (KeeperException e) {
214         throw new IOException("Unexpected ZooKeeper exception", e);
215       } finally {
216         zkWatcher.unregisterListener(deletionListener);
217       }
218     }
219     updateAcquiredLock(createdZNode);
220     LOG.debug("Successfully acquired a lock for " + createdZNode);
221     return true;
222   }
223 
224   private String createLockZNode() throws KeeperException {
225     try {
226       return ZKUtil.createNodeIfNotExistsNoWatch(zkWatcher, fullyQualifiedZNode,
227           metadata, CreateMode.EPHEMERAL_SEQUENTIAL);
228     } catch (KeeperException.NoNodeException nne) {
229       //create parents, retry
230       ZKUtil.createWithParents(zkWatcher, parentLockNode);
231       return createLockZNode();
232     }
233   }
234 
235   /**
236    * Check if a child znode represents a write lock.
237    * @param child The child znode we want to check.
238    * @return whether the child znode represents a write lock
239    */
240   protected static boolean isChildWriteLock(String child) {
241     int idx = child.lastIndexOf(ZKUtil.ZNODE_PATH_SEPARATOR);
242     String suffix = child.substring(idx + 1);
243     return suffix.startsWith(WRITE_LOCK_CHILD_NODE_PREFIX);
244   }
245 
246   /**
247    * Update state as to indicate that a lock is held
248    * @param createdZNode The lock znode
249    * @throws IOException If an unrecoverable ZooKeeper error occurs
250    */
251   protected void updateAcquiredLock(String createdZNode) throws IOException {
252     Stat stat = new Stat();
253     byte[] data = null;
254     Exception ex = null;
255     try {
256       data = ZKUtil.getDataNoWatch(zkWatcher, createdZNode, stat);
257     } catch (KeeperException e) {
258       LOG.warn("Cannot getData for znode:" + createdZNode, e);
259       ex = e;
260     }
261     if (data == null) {
262       LOG.error("Can't acquire a lock on a non-existent node " + createdZNode);
263       throw new IllegalStateException("ZNode " + createdZNode +
264           "no longer exists!", ex);
265     }
266     AcquiredLock newLock = new AcquiredLock(createdZNode, stat.getVersion());
267     if (!acquiredLock.compareAndSet(null, newLock)) {
268       LOG.error("The lock " + fullyQualifiedZNode +
269           " has already been acquired by another process!");
270       throw new IllegalStateException(fullyQualifiedZNode +
271           " is held by another process");
272     }
273   }
274 
275   /**
276    * {@inheritDoc}
277    */
278   @Override
279   public void release() throws IOException, InterruptedException {
280     AcquiredLock lock = acquiredLock.get();
281     if (lock == null) {
282       LOG.error("Cannot release lock" +
283           ", process does not have a lock for " + fullyQualifiedZNode);
284       throw new IllegalStateException("No lock held for " + fullyQualifiedZNode);
285     }
286     try {
287       if (ZKUtil.checkExists(zkWatcher, lock.getPath()) != -1) {
288         ZKUtil.deleteNode(zkWatcher, lock.getPath(), lock.getVersion());
289         if (!acquiredLock.compareAndSet(lock, null)) {
290           LOG.debug("Current process no longer holds " + lock + " for " +
291               fullyQualifiedZNode);
292           throw new IllegalStateException("Not holding a lock for " +
293               fullyQualifiedZNode +"!");
294         }
295       }
296       if (LOG.isDebugEnabled()) {
297         LOG.debug("Successfully released " + lock.getPath());
298       }
299     } catch (BadVersionException e) {
300       throw new IllegalStateException(e);
301     } catch (KeeperException e) {
302       throw new IOException(e);
303     }
304   }
305 
306   /**
307    * Process metadata stored in a ZNode using a callback object passed to
308    * this instance.
309    * <p>
310    * @param lockZNode The node holding the metadata
311    * @return True if metadata was ready and processed
312    * @throws IOException If an unexpected ZooKeeper error occurs
313    * @throws InterruptedException If interrupted when reading the metadata
314    */
315   protected boolean handleLockMetadata(String lockZNode)
316   throws IOException, InterruptedException {
317     byte[] metadata = null;
318     try {
319       metadata = ZKUtil.getData(zkWatcher, lockZNode);
320     } catch (KeeperException ex) {
321       LOG.warn("Cannot getData for znode:" + lockZNode, ex);
322     }
323     if (metadata == null) {
324       return false;
325     }
326     if (handler != null) {
327       handler.handleMetadata(metadata);
328     }
329     return true;
330   }
331 
332   /**
333    * Determine based on a list of children under a ZNode, whether or not a
334    * process which created a specified ZNode has obtained a lock. If a lock is
335    * not obtained, return the path that we should watch awaiting its deletion.
336    * Otherwise, return null.
337    * This method is abstract as the logic for determining whether or not a
338    * lock is obtained depends on the type of lock being implemented.
339    * @param myZNode The ZNode created by the process attempting to acquire
340    *                a lock
341    * @param children List of all child ZNodes under the lock's parent ZNode
342    * @return The path to watch, or null if myZNode can represent a correctly
343    *         acquired lock.
344    */
345   protected abstract String getLockPath(String myZNode, List<String> children)
346   throws IOException, InterruptedException;
347 }