001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase.master.locking;
020
021import java.io.IOException;
022import java.util.concurrent.CountDownLatch;
023import java.util.concurrent.TimeUnit;
024
025import org.apache.hadoop.hbase.TableName;
026import org.apache.hadoop.hbase.client.RegionInfo;
027import org.apache.hadoop.hbase.master.HMaster;
028import org.apache.hadoop.hbase.procedure2.LockType;
029import org.apache.hadoop.hbase.util.NonceKey;
030import org.apache.yetus.audience.InterfaceAudience;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
034
035/**
036 * Functions to acquire lock on table/namespace/regions.
037 */
038@InterfaceAudience.Private
039public final class LockManager {
040  private static final Logger LOG = LoggerFactory.getLogger(LockManager.class);
041  private final HMaster master;
042  private final RemoteLocks remoteLocks;
043
044  public LockManager(HMaster master) {
045    this.master = master;
046    this.remoteLocks = new RemoteLocks();
047  }
048
049  public RemoteLocks remoteLocks() {
050    return remoteLocks;
051  }
052
053  public MasterLock createMasterLock(final String namespace,
054      final LockType type, final String description) {
055    return new MasterLock(namespace, type, description);
056  }
057
058  public MasterLock createMasterLock(final TableName tableName,
059      final LockType type, final String description) {
060    return new MasterLock(tableName, type, description);
061  }
062
063  public MasterLock createMasterLock(final RegionInfo[] regionInfos, final String description) {
064    return new MasterLock(regionInfos, description);
065  }
066
067  private void submitProcedure(final LockProcedure proc, final NonceKey nonceKey) {
068    proc.setOwner(master.getMasterProcedureExecutor().getEnvironment().getRequestUser());
069    master.getMasterProcedureExecutor().submitProcedure(proc, nonceKey);
070  }
071
072  /**
073   * Locks on namespace/table/regions.
074   * Underneath, uses procedure framework and queues a {@link LockProcedure} which waits in a
075   * queue until scheduled.
076   * Use this lock instead LockManager.remoteLocks() for MASTER ONLY operations for two advantages:
077   * - no need of polling on LockProcedure to check if lock was acquired.
078   * - Generous timeout for lock preemption (default 10 min), no need to spawn thread for heartbeats.
079   * (timeout configuration {@link LockProcedure#DEFAULT_LOCAL_MASTER_LOCKS_TIMEOUT_MS}).
080   */
081  public class MasterLock {
082    private final String namespace;
083    private final TableName tableName;
084    private final RegionInfo[] regionInfos;
085    private final LockType type;
086    private final String description;
087
088    private LockProcedure proc = null;
089
090    public MasterLock(final String namespace,
091        final LockType type, final String description) {
092      this.namespace = namespace;
093      this.tableName = null;
094      this.regionInfos = null;
095      this.type = type;
096      this.description = description;
097    }
098
099    public MasterLock(final TableName tableName,
100        final LockType type, final String description) {
101      this.namespace = null;
102      this.tableName = tableName;
103      this.regionInfos = null;
104      this.type = type;
105      this.description = description;
106    }
107
108    public MasterLock(final RegionInfo[] regionInfos, final String description) {
109      this.namespace = null;
110      this.tableName = null;
111      this.regionInfos = regionInfos;
112      this.type = LockType.EXCLUSIVE;
113      this.description = description;
114    }
115
116    /**
117     * Acquire the lock, waiting indefinitely until the lock is released or
118     * the thread is interrupted.
119     * @throws InterruptedException If current thread is interrupted while
120     *                              waiting for the lock
121     */
122    public boolean acquire() throws InterruptedException {
123      return tryAcquire(0);
124    }
125
126    /**
127     * Acquire the lock within a wait time.
128     * @param timeoutMs The maximum time (in milliseconds) to wait for the lock,
129     *                  0 to wait indefinitely
130     * @return True if the lock was acquired, false if waiting time elapsed
131     *         before the lock was acquired
132     * @throws InterruptedException If the thread is interrupted while waiting to
133     *                              acquire the lock
134     */
135    public boolean tryAcquire(final long timeoutMs) throws InterruptedException {
136      if (proc != null && proc.isLocked()) {
137        return true;
138      }
139      // Use new condition and procedure every time lock is requested.
140      final CountDownLatch lockAcquireLatch = new CountDownLatch(1);
141      if (regionInfos != null) {
142        proc = new LockProcedure(master.getConfiguration(), regionInfos, type,
143            description, lockAcquireLatch);
144      } else if (tableName != null) {
145        proc = new LockProcedure(master.getConfiguration(), tableName, type,
146            description, lockAcquireLatch);
147      } else if (namespace != null) {
148        proc = new LockProcedure(master.getConfiguration(), namespace, type,
149            description, lockAcquireLatch);
150      } else {
151        throw new UnsupportedOperationException("no namespace/table/region provided");
152      }
153
154      // The user of a MasterLock should be 'hbase', the only case where this is not true
155      // is if from inside a coprocessor we try to take a master lock (which should be avoided)
156      proc.setOwner(master.getMasterProcedureExecutor().getEnvironment().getRequestUser());
157      master.getMasterProcedureExecutor().submitProcedure(proc);
158
159      long deadline = (timeoutMs > 0) ? System.currentTimeMillis() + timeoutMs : Long.MAX_VALUE;
160      while (deadline >= System.currentTimeMillis() && !proc.isLocked()) {
161        try {
162          lockAcquireLatch.await(deadline - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
163        } catch (InterruptedException e) {
164          LOG.info("InterruptedException when waiting for lock: " + proc.toString());
165          // kind of weird, releasing a lock which is not locked. This is to make the procedure
166          // finish immediately whenever it gets scheduled so that it doesn't hold the lock.
167          release();
168          throw e;
169        }
170      }
171      if (!proc.isLocked()) {
172        LOG.info("Timed out waiting to acquire procedure lock: " + proc.toString());
173        release();
174        return false;
175      }
176      return true;
177    }
178
179    /**
180     * Release the lock.
181     * No-op if the lock was never acquired.
182     */
183    public void release() {
184      if (proc != null) {
185        proc.unlock(master.getMasterProcedureExecutor().getEnvironment());
186      }
187      proc = null;
188    }
189
190    @Override
191    public String toString() {
192      return "MasterLock: proc = " + proc.toString();
193    }
194
195    @VisibleForTesting
196    LockProcedure getProc() {
197      return proc;
198    }
199  }
200
201  /**
202   * Locks on namespace/table/regions for remote operations.
203   * Since remote operations are unreliable and the client/RS may die anytime and never release
204   * locks, regular heartbeats are required to keep the lock held.
205   */
206  public class RemoteLocks {
207    public long requestNamespaceLock(final String namespace, final LockType type,
208        final String description, final NonceKey nonceKey)
209        throws IllegalArgumentException, IOException {
210      master.getMasterCoprocessorHost().preRequestLock(namespace, null, null, type, description);
211      final LockProcedure proc = new LockProcedure(master.getConfiguration(), namespace,
212          type, description, null);
213      submitProcedure(proc, nonceKey);
214      master.getMasterCoprocessorHost().postRequestLock(namespace, null, null, type, description);
215      return proc.getProcId();
216    }
217
218    public long requestTableLock(final TableName tableName, final LockType type,
219        final String description, final NonceKey nonceKey)
220        throws IllegalArgumentException, IOException {
221      master.getMasterCoprocessorHost().preRequestLock(null, tableName, null, type, description);
222      final LockProcedure proc = new LockProcedure(master.getConfiguration(), tableName,
223          type, description, null);
224      submitProcedure(proc, nonceKey);
225      master.getMasterCoprocessorHost().postRequestLock(null, tableName, null, type, description);
226      return proc.getProcId();
227    }
228
229    /**
230     * @throws IllegalArgumentException if all regions are not from same table.
231     */
232    public long requestRegionsLock(final RegionInfo[] regionInfos, final String description,
233        final NonceKey nonceKey)
234    throws IllegalArgumentException, IOException {
235      master.getMasterCoprocessorHost().preRequestLock(null, null, regionInfos,
236            LockType.EXCLUSIVE, description);
237      final LockProcedure proc = new LockProcedure(master.getConfiguration(), regionInfos,
238          LockType.EXCLUSIVE, description, null);
239      submitProcedure(proc, nonceKey);
240      master.getMasterCoprocessorHost().postRequestLock(null, null, regionInfos,
241            LockType.EXCLUSIVE, description);
242      return proc.getProcId();
243    }
244
245    /**
246     * @param keepAlive if false, release the lock.
247     * @return true, if procedure is found and it has the lock; else false.
248     */
249    public boolean lockHeartbeat(final long procId, final boolean keepAlive) throws IOException {
250      final LockProcedure proc = master.getMasterProcedureExecutor()
251        .getProcedure(LockProcedure.class, procId);
252      if (proc == null) return false;
253
254      master.getMasterCoprocessorHost().preLockHeartbeat(proc, keepAlive);
255
256      proc.updateHeartBeat();
257      if (!keepAlive) {
258        proc.unlock(master.getMasterProcedureExecutor().getEnvironment());
259      }
260
261      master.getMasterCoprocessorHost().postLockHeartbeat(proc, keepAlive);
262
263      return proc.isLocked();
264    }
265  }
266}