1 /*
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19 package org.apache.hadoop.hbase.util;
20
21 import java.io.IOException;
22 import java.io.InterruptedIOException;
23 import java.util.concurrent.ConcurrentHashMap;
24 import java.util.concurrent.ConcurrentMap;
25
26 import org.apache.hadoop.classification.InterfaceAudience;
27
28 /**
29 * Allows multiple concurrent clients to lock on a numeric id with a minimal
30 * memory overhead. The intended usage is as follows:
31 *
32 * <pre>
33 * IdLock.Entry lockEntry = idLock.getLockEntry(id);
34 * try {
35 * // User code.
36 * } finally {
37 * idLock.releaseLockEntry(lockEntry);
38 * }</pre>
39 */
40 @InterfaceAudience.Private
41 public class IdLock {
42
43 /** An entry returned to the client as a lock object */
44 public static class Entry {
45 private final long id;
46 private int numWaiters;
47 private boolean isLocked = true;
48
49 private Entry(long id) {
50 this.id = id;
51 }
52
53 public String toString() {
54 return "id=" + id + ", numWaiter=" + numWaiters + ", isLocked="
55 + isLocked;
56 }
57 }
58
59 private ConcurrentMap<Long, Entry> map =
60 new ConcurrentHashMap<Long, Entry>();
61
62 /**
63 * Blocks until the lock corresponding to the given id is acquired.
64 *
65 * @param id an arbitrary number to lock on
66 * @return an "entry" to pass to {@link #releaseLockEntry(Entry)} to release
67 * the lock
68 * @throws IOException if interrupted
69 */
70 public Entry getLockEntry(long id) throws IOException {
71 Entry entry = new Entry(id);
72 Entry existing;
73 while ((existing = map.putIfAbsent(entry.id, entry)) != null) {
74 synchronized (existing) {
75 if (existing.isLocked) {
76 ++existing.numWaiters; // Add ourselves to waiters.
77 while (existing.isLocked) {
78 try {
79 existing.wait();
80 } catch (InterruptedException e) {
81 --existing.numWaiters; // Remove ourselves from waiters.
82 throw new InterruptedIOException(
83 "Interrupted waiting to acquire sparse lock");
84 }
85 }
86
87 --existing.numWaiters; // Remove ourselves from waiters.
88 existing.isLocked = true;
89 return existing;
90 }
91 // If the entry is not locked, it might already be deleted from the
92 // map, so we cannot return it. We need to get our entry into the map
93 // or get someone else's locked entry.
94 }
95 }
96 return entry;
97 }
98
99 /**
100 * Must be called in a finally block to decrease the internal counter and
101 * remove the monitor object for the given id if the caller is the last
102 * client.
103 *
104 * @param entry the return value of {@link #getLockEntry(long)}
105 */
106 public void releaseLockEntry(Entry entry) {
107 synchronized (entry) {
108 entry.isLocked = false;
109 if (entry.numWaiters > 0) {
110 entry.notify();
111 } else {
112 map.remove(entry.id);
113 }
114 }
115 }
116
117 /** For testing */
118 void assertMapEmpty() {
119 assert map.size() == 0;
120 }
121
122 }