001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.master.procedure;
019
020import java.util.ArrayList;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.function.Function;
025import org.apache.hadoop.hbase.ServerName;
026import org.apache.hadoop.hbase.TableName;
027import org.apache.hadoop.hbase.master.locking.LockProcedure;
028import org.apache.hadoop.hbase.procedure2.LockAndQueue;
029import org.apache.hadoop.hbase.procedure2.LockType;
030import org.apache.hadoop.hbase.procedure2.LockedResource;
031import org.apache.hadoop.hbase.procedure2.LockedResourceType;
032import org.apache.hadoop.hbase.procedure2.Procedure;
033import org.apache.yetus.audience.InterfaceAudience;
034
035import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap;
036
037/**
038 * <p>
039 * Locks on namespaces, tables, and regions.
040 * </p>
041 * <p>
042 * Since LockAndQueue implementation is NOT thread-safe, schedLock() guards all calls to these
043 * locks.
044 * </p>
045 */
046@InterfaceAudience.Private
047class SchemaLocking {
048
049  private final Function<Long, Procedure<?>> procedureRetriever;
050  private final Map<ServerName, LockAndQueue> serverLocks = new HashMap<>();
051  private final Map<String, LockAndQueue> namespaceLocks = new HashMap<>();
052  private final Map<TableName, LockAndQueue> tableLocks = new HashMap<>();
053  // Single map for all regions irrespective of tables. Key is encoded region name.
054  private final Map<String, LockAndQueue> regionLocks = new HashMap<>();
055  private final Map<String, LockAndQueue> peerLocks = new HashMap<>();
056  private final LockAndQueue metaLock;
057
058  public SchemaLocking(Function<Long, Procedure<?>> procedureRetriever) {
059    this.procedureRetriever = procedureRetriever;
060    this.metaLock = new LockAndQueue(procedureRetriever);
061  }
062
063  private <T> LockAndQueue getLock(Map<T, LockAndQueue> map, T key) {
064    LockAndQueue lock = map.get(key);
065    if (lock == null) {
066      lock = new LockAndQueue(procedureRetriever);
067      map.put(key, lock);
068    }
069    return lock;
070  }
071
072  LockAndQueue getTableLock(TableName tableName) {
073    return getLock(tableLocks, tableName);
074  }
075
076  LockAndQueue removeTableLock(TableName tableName) {
077    return tableLocks.remove(tableName);
078  }
079
080  LockAndQueue getNamespaceLock(String namespace) {
081    return getLock(namespaceLocks, namespace);
082  }
083
084  LockAndQueue getRegionLock(String encodedRegionName) {
085    return getLock(regionLocks, encodedRegionName);
086  }
087
088  /**
089   * @deprecated only used for {@link RecoverMetaProcedure}. Should be removed along with
090   *             {@link RecoverMetaProcedure}.
091   */
092  @Deprecated
093  LockAndQueue getMetaLock() {
094    return metaLock;
095  }
096
097  LockAndQueue removeRegionLock(String encodedRegionName) {
098    return regionLocks.remove(encodedRegionName);
099  }
100
101  LockAndQueue getServerLock(ServerName serverName) {
102    return getLock(serverLocks, serverName);
103  }
104
105  LockAndQueue removeServerLock(ServerName serverName) {
106    return serverLocks.remove(serverName);
107  }
108
109  LockAndQueue getPeerLock(String peerId) {
110    return getLock(peerLocks, peerId);
111  }
112
113  LockAndQueue removePeerLock(String peerId) {
114    return peerLocks.remove(peerId);
115  }
116
117  private LockedResource createLockedResource(LockedResourceType resourceType, String resourceName,
118    LockAndQueue queue) {
119    LockType lockType;
120    Procedure<?> exclusiveLockOwnerProcedure;
121    int sharedLockCount;
122
123    if (queue.hasExclusiveLock()) {
124      lockType = LockType.EXCLUSIVE;
125      exclusiveLockOwnerProcedure = queue.getExclusiveLockOwnerProcedure();
126      sharedLockCount = 0;
127    } else {
128      lockType = LockType.SHARED;
129      exclusiveLockOwnerProcedure = null;
130      sharedLockCount = queue.getSharedLockCount();
131    }
132
133    List<Procedure<?>> waitingProcedures = new ArrayList<>();
134
135    queue.filterWaitingQueue(p -> p instanceof LockProcedure)
136      .forEachOrdered(waitingProcedures::add);
137
138    return new LockedResource(resourceType, resourceName, lockType, exclusiveLockOwnerProcedure,
139      sharedLockCount, waitingProcedures);
140  }
141
142  private <T> void addToLockedResources(List<LockedResource> lockedResources,
143    Map<T, LockAndQueue> locks, Function<T, String> keyTransformer,
144    LockedResourceType resourcesType) {
145    locks.entrySet().stream().filter(e -> e.getValue().isLocked())
146      .map(e -> createLockedResource(resourcesType, keyTransformer.apply(e.getKey()), e.getValue()))
147      .forEachOrdered(lockedResources::add);
148  }
149
150  /**
151   * List lock queues.
152   * @return the locks
153   */
154  List<LockedResource> getLocks() {
155    List<LockedResource> lockedResources = new ArrayList<>();
156    addToLockedResources(lockedResources, serverLocks, sn -> sn.getServerName(),
157      LockedResourceType.SERVER);
158    addToLockedResources(lockedResources, namespaceLocks, Function.identity(),
159      LockedResourceType.NAMESPACE);
160    addToLockedResources(lockedResources, tableLocks, tn -> tn.getNameAsString(),
161      LockedResourceType.TABLE);
162    addToLockedResources(lockedResources, regionLocks, Function.identity(),
163      LockedResourceType.REGION);
164    addToLockedResources(lockedResources, peerLocks, Function.identity(), LockedResourceType.PEER);
165    addToLockedResources(lockedResources, ImmutableMap.of(TableName.META_TABLE_NAME, metaLock),
166      tn -> tn.getNameAsString(), LockedResourceType.META);
167    return lockedResources;
168  }
169
170  /**
171   * @return {@link LockedResource} for resource of specified type & name. null if resource is not
172   *         locked.
173   */
174  LockedResource getLockResource(LockedResourceType resourceType, String resourceName) {
175    LockAndQueue queue;
176    switch (resourceType) {
177      case SERVER:
178        queue = serverLocks.get(ServerName.valueOf(resourceName));
179        break;
180      case NAMESPACE:
181        queue = namespaceLocks.get(resourceName);
182        break;
183      case TABLE:
184        queue = tableLocks.get(TableName.valueOf(resourceName));
185        break;
186      case REGION:
187        queue = regionLocks.get(resourceName);
188        break;
189      case PEER:
190        queue = peerLocks.get(resourceName);
191        break;
192      case META:
193        queue = metaLock;
194      default:
195        queue = null;
196    }
197    return queue != null ? createLockedResource(resourceType, resourceName, queue) : null;
198  }
199
200  /**
201   * Removes all locks by clearing the maps. Used when procedure executor is stopped for failure and
202   * recovery testing.
203   */
204  void clear() {
205    serverLocks.clear();
206    namespaceLocks.clear();
207    tableLocks.clear();
208    regionLocks.clear();
209    peerLocks.clear();
210  }
211
212  @Override
213  public String toString() {
214    return "serverLocks=" + filterUnlocked(this.serverLocks) + ", namespaceLocks="
215      + filterUnlocked(this.namespaceLocks) + ", tableLocks=" + filterUnlocked(this.tableLocks)
216      + ", regionLocks=" + filterUnlocked(this.regionLocks) + ", peerLocks="
217      + filterUnlocked(this.peerLocks) + ", metaLocks="
218      + filterUnlocked(ImmutableMap.of(TableName.META_TABLE_NAME, metaLock));
219  }
220
221  private String filterUnlocked(Map<?, LockAndQueue> locks) {
222    StringBuilder sb = new StringBuilder("{");
223    int initialLength = sb.length();
224    for (Map.Entry<?, LockAndQueue> entry : locks.entrySet()) {
225      if (!entry.getValue().isLocked()) {
226        continue;
227      }
228      if (sb.length() > initialLength) {
229        sb.append(", ");
230      }
231      sb.append("{").append(entry.getKey()).append("=").append(entry.getValue()).append("}");
232    }
233    sb.append("}");
234    return sb.toString();
235  }
236}