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 static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertNull;
023import static org.junit.jupiter.api.Assertions.assertSame;
024import static org.junit.jupiter.api.Assertions.assertTrue;
025
026import java.io.IOException;
027import java.lang.reflect.Field;
028import java.lang.reflect.Method;
029import java.util.Arrays;
030import java.util.HashMap;
031import java.util.List;
032import java.util.Map;
033import org.apache.hadoop.hbase.ServerName;
034import org.apache.hadoop.hbase.TableName;
035import org.apache.hadoop.hbase.client.RegionInfo;
036import org.apache.hadoop.hbase.client.RegionInfoBuilder;
037import org.apache.hadoop.hbase.master.locking.LockProcedure;
038import org.apache.hadoop.hbase.master.procedure.TableProcedureInterface.TableOperationType;
039import org.apache.hadoop.hbase.procedure2.LockType;
040import org.apache.hadoop.hbase.procedure2.LockedResource;
041import org.apache.hadoop.hbase.procedure2.LockedResourceType;
042import org.apache.hadoop.hbase.procedure2.Procedure;
043import org.apache.hadoop.hbase.procedure2.ProcedureEvent;
044import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility.TestProcedure;
045import org.apache.hadoop.hbase.testclassification.MasterTests;
046import org.apache.hadoop.hbase.testclassification.SmallTests;
047import org.apache.hadoop.hbase.util.Bytes;
048import org.junit.jupiter.api.AfterEach;
049import org.junit.jupiter.api.BeforeEach;
050import org.junit.jupiter.api.Tag;
051import org.junit.jupiter.api.Test;
052import org.junit.jupiter.api.TestInfo;
053import org.slf4j.Logger;
054import org.slf4j.LoggerFactory;
055
056@Tag(MasterTests.TAG)
057@Tag(SmallTests.TAG)
058public class TestMasterProcedureScheduler {
059
060  private static final Logger LOG = LoggerFactory.getLogger(TestMasterProcedureScheduler.class);
061
062  private MasterProcedureScheduler queue;
063
064  private Map<Long, Procedure<?>> procedures;
065  private String testMethodName;
066
067  @BeforeEach
068  public void setTestMethod(TestInfo testInfo) {
069    testMethodName = testInfo.getTestMethod().get().getName();
070  }
071
072  @BeforeEach
073  public void setUp() throws IOException {
074    procedures = new HashMap<>();
075    queue = new MasterProcedureScheduler(procedures::get);
076    queue.start();
077  }
078
079  @AfterEach
080  public void tearDown() throws IOException {
081    assertEquals(0, queue.size(), "proc-queue expected to be empty");
082    queue.stop();
083    queue.clear();
084  }
085
086  /**
087   * Verify simple create/insert/fetch/delete of the table queue.
088   */
089  @Test
090  public void testSimpleTableOpsQueues() throws Exception {
091    final int NUM_TABLES = 10;
092    final int NUM_ITEMS = 10;
093
094    int count = 0;
095    for (int i = 1; i <= NUM_TABLES; ++i) {
096      TableName tableName = TableName.valueOf(String.format("test-%04d", i));
097      // insert items
098      for (int j = 1; j <= NUM_ITEMS; ++j) {
099        queue.addBack(new TestTableProcedure(i * 1000 + j, tableName,
100          TableProcedureInterface.TableOperationType.REGION_EDIT));
101        assertEquals(++count, queue.size());
102      }
103    }
104    assertEquals(NUM_TABLES * NUM_ITEMS, queue.size());
105
106    for (int j = 1; j <= NUM_ITEMS; ++j) {
107      for (int i = 1; i <= NUM_TABLES; ++i) {
108        Procedure<?> proc = queue.poll();
109        assertTrue(proc != null);
110        TableName tableName = ((TestTableProcedure) proc).getTableName();
111        queue.waitTableExclusiveLock(proc, tableName);
112        queue.wakeTableExclusiveLock(proc, tableName);
113        queue.completionCleanup(proc);
114        assertEquals(--count, queue.size());
115        assertEquals(i * 1000 + j, proc.getProcId());
116      }
117    }
118    assertEquals(0, queue.size());
119
120    for (int i = 1; i <= NUM_TABLES; ++i) {
121      final TableName tableName = TableName.valueOf(String.format("test-%04d", i));
122      final TestTableProcedure dummyProc =
123        new TestTableProcedure(100, tableName, TableProcedureInterface.TableOperationType.DELETE);
124      // complete the table deletion
125      assertTrue(queue.markTableAsDeleted(tableName, dummyProc));
126    }
127  }
128
129  /**
130   * Check that the table queue is not deletable until every procedure in-progress is completed
131   * (this is a special case for write-locks).
132   */
133  @Test
134  public void testCreateDeleteTableOperationsWithWriteLock() throws Exception {
135    final TableName tableName = TableName.valueOf(testMethodName);
136
137    final TestTableProcedure dummyProc =
138      new TestTableProcedure(100, tableName, TableProcedureInterface.TableOperationType.DELETE);
139
140    queue.addBack(
141      new TestTableProcedure(1, tableName, TableProcedureInterface.TableOperationType.EDIT));
142
143    // table can't be deleted because one item is in the queue
144    assertFalse(queue.markTableAsDeleted(tableName, dummyProc));
145
146    // fetch item and take a lock
147    Procedure<?> proc = queue.poll();
148    assertEquals(1, proc.getProcId());
149    // take the xlock
150    assertEquals(false, queue.waitTableExclusiveLock(proc, tableName));
151    // table can't be deleted because we have the lock
152    assertEquals(0, queue.size());
153    assertFalse(queue.markTableAsDeleted(tableName, dummyProc));
154    // release the xlock
155    queue.wakeTableExclusiveLock(proc, tableName);
156    // complete the table deletion
157    assertTrue(queue.markTableAsDeleted(tableName, proc));
158  }
159
160  /**
161   * Check that the table queue is not deletable until every procedure in-progress is completed
162   * (this is a special case for read-locks).
163   */
164  @Test
165  public void testCreateDeleteTableOperationsWithReadLock() throws Exception {
166    final TableName tableName = TableName.valueOf(testMethodName);
167    final int nitems = 2;
168
169    final TestTableProcedure dummyProc =
170      new TestTableProcedure(100, tableName, TableProcedureInterface.TableOperationType.DELETE);
171
172    for (int i = 1; i <= nitems; ++i) {
173      queue.addBack(
174        new TestTableProcedure(i, tableName, TableProcedureInterface.TableOperationType.READ));
175    }
176
177    // table can't be deleted because one item is in the queue
178    assertFalse(queue.markTableAsDeleted(tableName, dummyProc));
179
180    Procedure<?>[] procs = new Procedure[nitems];
181    for (int i = 0; i < nitems; ++i) {
182      // fetch item and take a lock
183      Procedure<?> proc = queue.poll();
184      procs[i] = proc;
185      assertEquals(i + 1, proc.getProcId());
186      // take the rlock
187      assertEquals(false, queue.waitTableSharedLock(proc, tableName));
188      // table can't be deleted because we have locks and/or items in the queue
189      assertFalse(queue.markTableAsDeleted(tableName, dummyProc));
190    }
191
192    for (int i = 0; i < nitems; ++i) {
193      // table can't be deleted because we have locks
194      assertFalse(queue.markTableAsDeleted(tableName, dummyProc));
195      // release the rlock
196      queue.wakeTableSharedLock(procs[i], tableName);
197    }
198
199    // there are no items and no lock in the queeu
200    assertEquals(0, queue.size());
201    // complete the table deletion
202    assertTrue(queue.markTableAsDeleted(tableName, dummyProc));
203  }
204
205  /**
206   * Verify the correct logic of RWLocks on the queue
207   */
208  @Test
209  public void testVerifyRwLocks() throws Exception {
210    final TableName tableName = TableName.valueOf(testMethodName);
211    queue.addBack(
212      new TestTableProcedure(1, tableName, TableProcedureInterface.TableOperationType.EDIT));
213    queue.addBack(
214      new TestTableProcedure(2, tableName, TableProcedureInterface.TableOperationType.READ));
215    queue.addBack(
216      new TestTableProcedure(3, tableName, TableProcedureInterface.TableOperationType.EDIT));
217
218    // Fetch the 1st item and take the write lock
219    Procedure<?> proc = queue.poll();
220    assertEquals(1, proc.getProcId());
221    assertEquals(false, queue.waitTableExclusiveLock(proc, tableName));
222
223    // Fetch the 2nd item and verify that the lock can't be acquired
224    assertEquals(null, queue.poll(0));
225
226    // Release the write lock and acquire the read lock
227    releaseTableExclusiveLockAndComplete(proc, tableName);
228
229    // Fetch the 2nd item and take the read lock
230    Procedure<?> rdProc = queue.poll();
231    assertEquals(2, rdProc.getProcId());
232    assertEquals(false, queue.waitTableSharedLock(rdProc, tableName));
233
234    // Fetch the 3rd item and verify that the lock can't be acquired
235    assertEquals(null, queue.poll(0));
236
237    // release the rdlock of item 2 and take the wrlock for the 3d item
238    queue.wakeTableSharedLock(rdProc, tableName);
239
240    queue.addBack(
241      new TestTableProcedure(4, tableName, TableProcedureInterface.TableOperationType.READ));
242    queue.addBack(
243      new TestTableProcedure(5, tableName, TableProcedureInterface.TableOperationType.READ));
244
245    // Fetch the 3rd item and take the write lock
246    Procedure<?> wrProc = queue.poll();
247    assertEquals(false, queue.waitTableExclusiveLock(wrProc, tableName));
248
249    // Fetch 4th item and verify that the lock can't be acquired
250    assertEquals(null, queue.poll(0));
251
252    // Release the write lock and acquire the read lock
253    releaseTableExclusiveLockAndComplete(wrProc, tableName);
254
255    // Fetch the 4th item and take the read lock
256    rdProc = queue.poll();
257    assertEquals(4, rdProc.getProcId());
258    assertEquals(false, queue.waitTableSharedLock(rdProc, tableName));
259
260    // Fetch the 4th item and take the read lock
261    Procedure<?> rdProc2 = queue.poll();
262    assertEquals(5, rdProc2.getProcId());
263    assertEquals(false, queue.waitTableSharedLock(rdProc2, tableName));
264
265    // Release 4th and 5th read-lock
266    queue.wakeTableSharedLock(rdProc, tableName);
267    queue.wakeTableSharedLock(rdProc2, tableName);
268
269    // remove table queue
270    assertEquals(0, queue.size());
271    assertTrue(queue.markTableAsDeleted(tableName, wrProc), "queue should be deleted");
272  }
273
274  @Test
275  public void testVerifyNamespaceRwLocks() throws Exception {
276    String nsName1 = "ns1";
277    String nsName2 = "ns2";
278    TableName tableName1 = TableName.valueOf(nsName1, testMethodName);
279    TableName tableName2 = TableName.valueOf(nsName2, testMethodName);
280    queue.addBack(
281      new TestNamespaceProcedure(1, nsName1, TableProcedureInterface.TableOperationType.EDIT));
282    queue.addBack(
283      new TestTableProcedure(2, tableName1, TableProcedureInterface.TableOperationType.EDIT));
284    queue.addBack(
285      new TestTableProcedure(3, tableName2, TableProcedureInterface.TableOperationType.EDIT));
286    queue.addBack(
287      new TestNamespaceProcedure(4, nsName2, TableProcedureInterface.TableOperationType.EDIT));
288
289    // Fetch the 1st item and take the write lock
290    Procedure<?> procNs1 = queue.poll();
291    assertEquals(1, procNs1.getProcId());
292    assertFalse(queue.waitNamespaceExclusiveLock(procNs1, nsName1));
293
294    // namespace table has higher priority so we still return procedure for it
295    Procedure<?> procNs2 = queue.poll();
296    assertEquals(4, procNs2.getProcId());
297    assertFalse(queue.waitNamespaceExclusiveLock(procNs2, nsName2));
298    queue.wakeNamespaceExclusiveLock(procNs2, nsName2);
299
300    // add procNs2 back in the queue
301    queue.yield(procNs2);
302
303    // again
304    procNs2 = queue.poll();
305    assertEquals(4, procNs2.getProcId());
306    assertFalse(queue.waitNamespaceExclusiveLock(procNs2, nsName2));
307
308    // ns1 and ns2 are both locked so we get nothing
309    assertNull(queue.poll());
310
311    // release the ns1 lock
312    queue.wakeNamespaceExclusiveLock(procNs1, nsName1);
313
314    // we are now able to execute table of ns1
315    long procId = queue.poll().getProcId();
316    assertEquals(2, procId);
317
318    // release ns2
319    queue.wakeNamespaceExclusiveLock(procNs2, nsName2);
320
321    // we are now able to execute table of ns2
322    procId = queue.poll().getProcId();
323    assertEquals(3, procId);
324  }
325
326  @Test
327  public void testVerifyNamespaceXLock() throws Exception {
328    String nsName = "ns1";
329    TableName tableName = TableName.valueOf(nsName, testMethodName);
330    queue.addBack(
331      new TestNamespaceProcedure(1, nsName, TableProcedureInterface.TableOperationType.CREATE));
332    queue.addBack(
333      new TestTableProcedure(2, tableName, TableProcedureInterface.TableOperationType.READ));
334
335    // Fetch the ns item and take the xlock
336    Procedure<?> proc = queue.poll();
337    assertEquals(1, proc.getProcId());
338    assertEquals(false, queue.waitNamespaceExclusiveLock(proc, nsName));
339
340    // the table operation can't be executed because the ns is locked
341    assertEquals(null, queue.poll(0));
342
343    // release the ns lock
344    queue.wakeNamespaceExclusiveLock(proc, nsName);
345
346    proc = queue.poll();
347    assertEquals(2, proc.getProcId());
348    assertEquals(false, queue.waitTableExclusiveLock(proc, tableName));
349    queue.wakeTableExclusiveLock(proc, tableName);
350  }
351
352  @Test
353  public void testXLockWaitingForExecutingSharedLockToRelease() {
354    final TableName tableName = TableName.valueOf(testMethodName);
355    final RegionInfo regionA = RegionInfoBuilder.newBuilder(tableName)
356      .setStartKey(Bytes.toBytes("a")).setEndKey(Bytes.toBytes("b")).build();
357
358    queue.addBack(new TestRegionProcedure(1, tableName,
359      TableProcedureInterface.TableOperationType.REGION_ASSIGN, regionA));
360    queue.addBack(
361      new TestTableProcedure(2, tableName, TableProcedureInterface.TableOperationType.EDIT));
362
363    // Fetch the 1st item and take the shared lock
364    Procedure<?> proc = queue.poll();
365    assertEquals(1, proc.getProcId());
366    assertEquals(false, queue.waitRegion(proc, regionA));
367
368    // the xlock operation in the queue can't be executed
369    assertEquals(null, queue.poll(0));
370
371    // release the shared lock
372    queue.wakeRegion(proc, regionA);
373
374    // Fetch the 2nd item and take the xlock
375    proc = queue.poll();
376    assertEquals(2, proc.getProcId());
377    assertEquals(false, queue.waitTableExclusiveLock(proc, tableName));
378
379    queue.addBack(new TestRegionProcedure(3, tableName,
380      TableProcedureInterface.TableOperationType.REGION_UNASSIGN, regionA));
381
382    // everything is locked by the table operation
383    assertEquals(null, queue.poll(0));
384
385    // release the table xlock
386    queue.wakeTableExclusiveLock(proc, tableName);
387
388    // grab the last item in the queue
389    proc = queue.poll();
390    assertEquals(3, proc.getProcId());
391
392    // lock and unlock the region
393    assertEquals(false, queue.waitRegion(proc, regionA));
394    assertEquals(null, queue.poll(0));
395    queue.wakeRegion(proc, regionA);
396  }
397
398  @Test
399  public void testVerifyRegionLocks() throws Exception {
400    final TableName tableName = TableName.valueOf(testMethodName);
401    final RegionInfo regionA = RegionInfoBuilder.newBuilder(tableName)
402      .setStartKey(Bytes.toBytes("a")).setEndKey(Bytes.toBytes("b")).build();
403    final RegionInfo regionB = RegionInfoBuilder.newBuilder(tableName)
404      .setStartKey(Bytes.toBytes("b")).setEndKey(Bytes.toBytes("c")).build();
405    final RegionInfo regionC = RegionInfoBuilder.newBuilder(tableName)
406      .setStartKey(Bytes.toBytes("c")).setEndKey(Bytes.toBytes("d")).build();
407
408    queue.addBack(
409      new TestTableProcedure(1, tableName, TableProcedureInterface.TableOperationType.EDIT));
410    queue.addBack(new TestRegionProcedure(2, tableName,
411      TableProcedureInterface.TableOperationType.REGION_MERGE, regionA, regionB));
412    queue.addBack(new TestRegionProcedure(3, tableName,
413      TableProcedureInterface.TableOperationType.REGION_SPLIT, regionA));
414    queue.addBack(new TestRegionProcedure(4, tableName,
415      TableProcedureInterface.TableOperationType.REGION_SPLIT, regionB));
416    queue.addBack(new TestRegionProcedure(5, tableName,
417      TableProcedureInterface.TableOperationType.REGION_UNASSIGN, regionC));
418
419    // Fetch the 1st item and take the write lock
420    Procedure<?> proc = queue.poll();
421    assertEquals(1, proc.getProcId());
422    assertEquals(false, queue.waitTableExclusiveLock(proc, tableName));
423
424    // everything is locked by the table operation
425    assertEquals(null, queue.poll(0));
426
427    // release the table lock
428    queue.wakeTableExclusiveLock(proc, tableName);
429
430    // Fetch the 2nd item and the the lock on regionA and regionB
431    Procedure<?> mergeProc = queue.poll();
432    assertEquals(2, mergeProc.getProcId());
433    assertEquals(false, queue.waitRegions(mergeProc, tableName, regionA, regionB));
434
435    // Fetch the 3rd item and the try to lock region A which will fail
436    // because already locked. this procedure will go in waiting.
437    // (this stuff will be explicit until we get rid of the zk-lock)
438    Procedure<?> procA = queue.poll();
439    assertEquals(3, procA.getProcId());
440    assertEquals(true, queue.waitRegions(procA, tableName, regionA));
441
442    // Fetch the 4th item, same story as the 3rd
443    Procedure<?> procB = queue.poll();
444    assertEquals(4, procB.getProcId());
445    assertEquals(true, queue.waitRegions(procB, tableName, regionB));
446
447    // Fetch the 5th item, since it is a non-locked region we are able to execute it
448    Procedure<?> procC = queue.poll();
449    assertEquals(5, procC.getProcId());
450    assertEquals(false, queue.waitRegions(procC, tableName, regionC));
451
452    // 3rd and 4th are in the region suspended queue
453    assertEquals(null, queue.poll(0));
454
455    // Release region A-B from merge operation (procId=2)
456    queue.wakeRegions(mergeProc, tableName, regionA, regionB);
457
458    // Fetch the 3rd item, now the lock on the region is available
459    procA = queue.poll();
460    assertEquals(3, procA.getProcId());
461    assertEquals(false, queue.waitRegions(procA, tableName, regionA));
462
463    // Fetch the 4th item, now the lock on the region is available
464    procB = queue.poll();
465    assertEquals(4, procB.getProcId());
466    assertEquals(false, queue.waitRegions(procB, tableName, regionB));
467
468    // release the locks on the regions
469    queue.wakeRegions(procA, tableName, regionA);
470    queue.wakeRegions(procB, tableName, regionB);
471    queue.wakeRegions(procC, tableName, regionC);
472  }
473
474  @Test
475  public void testVerifySubProcRegionLocks() throws Exception {
476    final TableName tableName = TableName.valueOf(testMethodName);
477    final RegionInfo regionA = RegionInfoBuilder.newBuilder(tableName)
478      .setStartKey(Bytes.toBytes("a")).setEndKey(Bytes.toBytes("b")).build();
479    final RegionInfo regionB = RegionInfoBuilder.newBuilder(tableName)
480      .setStartKey(Bytes.toBytes("b")).setEndKey(Bytes.toBytes("c")).build();
481    final RegionInfo regionC = RegionInfoBuilder.newBuilder(tableName)
482      .setStartKey(Bytes.toBytes("c")).setEndKey(Bytes.toBytes("d")).build();
483
484    queue.addBack(
485      new TestTableProcedure(1, tableName, TableProcedureInterface.TableOperationType.ENABLE));
486
487    // Fetch the 1st item from the queue, "the root procedure" and take the table lock
488    Procedure<?> rootProc = queue.poll();
489    assertEquals(1, rootProc.getProcId());
490    assertEquals(false, queue.waitTableExclusiveLock(rootProc, tableName));
491    assertEquals(null, queue.poll(0));
492
493    // Execute the 1st step of the root-proc.
494    // we should get 3 sub-proc back, one for each region.
495    // (this step is done by the executor/rootProc, we are simulating it)
496    Procedure<?>[] subProcs = new Procedure[] {
497      new TestRegionProcedure(1, 2, tableName,
498        TableProcedureInterface.TableOperationType.REGION_EDIT, regionA),
499      new TestRegionProcedure(1, 3, tableName,
500        TableProcedureInterface.TableOperationType.REGION_EDIT, regionB),
501      new TestRegionProcedure(1, 4, tableName,
502        TableProcedureInterface.TableOperationType.REGION_EDIT, regionC), };
503
504    // at this point the rootProc is going in a waiting state
505    // and the sub-procedures will be added in the queue.
506    // (this step is done by the executor, we are simulating it)
507    for (int i = subProcs.length - 1; i >= 0; --i) {
508      queue.addFront(subProcs[i]);
509    }
510    assertEquals(subProcs.length, queue.size());
511
512    // we should be able to fetch and execute all the sub-procs,
513    // since they are operating on different regions
514    for (int i = 0; i < subProcs.length; ++i) {
515      TestRegionProcedure regionProc = (TestRegionProcedure) queue.poll(0);
516      assertEquals(subProcs[i].getProcId(), regionProc.getProcId());
517      assertEquals(false, queue.waitRegions(regionProc, tableName, regionProc.getRegionInfo()));
518    }
519
520    // nothing else in the queue
521    assertEquals(null, queue.poll(0));
522
523    // release all the region locks
524    for (int i = 0; i < subProcs.length; ++i) {
525      TestRegionProcedure regionProc = (TestRegionProcedure) subProcs[i];
526      queue.wakeRegions(regionProc, tableName, regionProc.getRegionInfo());
527    }
528
529    // nothing else in the queue
530    assertEquals(null, queue.poll(0));
531
532    // release the table lock (for the root procedure)
533    queue.wakeTableExclusiveLock(rootProc, tableName);
534  }
535
536  @Test
537  public void testInheritedRegionXLock() {
538    final TableName tableName = TableName.valueOf(testMethodName);
539    final RegionInfo region = RegionInfoBuilder.newBuilder(tableName)
540      .setStartKey(Bytes.toBytes("a")).setEndKey(Bytes.toBytes("b")).build();
541
542    queue.addBack(new TestRegionProcedure(1, tableName,
543      TableProcedureInterface.TableOperationType.REGION_SPLIT, region));
544    queue.addBack(new TestRegionProcedure(1, 2, tableName,
545      TableProcedureInterface.TableOperationType.REGION_UNASSIGN, region));
546    queue.addBack(new TestRegionProcedure(3, tableName,
547      TableProcedureInterface.TableOperationType.REGION_EDIT, region));
548
549    // fetch the root proc and take the lock on the region
550    Procedure<?> rootProc = queue.poll();
551    assertEquals(1, rootProc.getProcId());
552    assertEquals(false, queue.waitRegion(rootProc, region));
553
554    // fetch the sub-proc and take the lock on the region (inherited lock)
555    Procedure<?> childProc = queue.poll();
556    assertEquals(2, childProc.getProcId());
557    assertEquals(false, queue.waitRegion(childProc, region));
558
559    // proc-3 will be fetched but it can't take the lock
560    Procedure<?> proc = queue.poll();
561    assertEquals(3, proc.getProcId());
562    assertEquals(true, queue.waitRegion(proc, region));
563
564    // release the child lock
565    queue.wakeRegion(childProc, region);
566
567    // nothing in the queue (proc-3 is suspended)
568    assertEquals(null, queue.poll(0));
569
570    // release the root lock
571    queue.wakeRegion(rootProc, region);
572
573    // proc-3 should be now available
574    proc = queue.poll();
575    assertEquals(3, proc.getProcId());
576    assertEquals(false, queue.waitRegion(proc, region));
577    queue.wakeRegion(proc, region);
578  }
579
580  @Test
581  public void testSuspendedProcedure() throws Exception {
582    final TableName tableName = TableName.valueOf(testMethodName);
583
584    queue.addBack(
585      new TestTableProcedure(1, tableName, TableProcedureInterface.TableOperationType.READ));
586    queue.addBack(
587      new TestTableProcedure(2, tableName, TableProcedureInterface.TableOperationType.READ));
588
589    Procedure<?> proc = queue.poll();
590    assertEquals(1, proc.getProcId());
591
592    // suspend
593    ProcedureEvent<?> event = new ProcedureEvent<>("testSuspendedProcedureEvent");
594    assertEquals(true, event.suspendIfNotReady(proc));
595
596    proc = queue.poll();
597    assertEquals(2, proc.getProcId());
598    assertEquals(null, queue.poll(0));
599
600    // resume
601    event.wake(queue);
602
603    proc = queue.poll();
604    assertEquals(1, proc.getProcId());
605    assertEquals(null, queue.poll(0));
606  }
607
608  private static RegionInfo[] generateRegionInfo(final TableName tableName) {
609    return new RegionInfo[] {
610      RegionInfoBuilder.newBuilder(tableName).setStartKey(Bytes.toBytes("a"))
611        .setEndKey(Bytes.toBytes("b")).build(),
612      RegionInfoBuilder.newBuilder(tableName).setStartKey(Bytes.toBytes("b"))
613        .setEndKey(Bytes.toBytes("c")).build(),
614      RegionInfoBuilder.newBuilder(tableName).setStartKey(Bytes.toBytes("c"))
615        .setEndKey(Bytes.toBytes("d")).build() };
616  }
617
618  @Test
619  public void testParentXLockAndChildrenSharedLock() throws Exception {
620    final TableName tableName = TableName.valueOf(testMethodName);
621    final RegionInfo[] regions = generateRegionInfo(tableName);
622    final TestRegionProcedure[] childProcs = new TestRegionProcedure[regions.length];
623    for (int i = 0; i < regions.length; ++i) {
624      childProcs[i] = new TestRegionProcedure(1, 2 + i, tableName,
625        TableProcedureInterface.TableOperationType.REGION_ASSIGN, regions[i]);
626    }
627    testInheritedXLockAndChildrenSharedLock(tableName,
628      new TestTableProcedure(1, tableName, TableProcedureInterface.TableOperationType.CREATE),
629      childProcs);
630  }
631
632  @Test
633  public void testRootXLockAndChildrenSharedLock() throws Exception {
634    final TableName tableName = TableName.valueOf(testMethodName);
635    final RegionInfo[] regions = generateRegionInfo(tableName);
636    final TestRegionProcedure[] childProcs = new TestRegionProcedure[regions.length];
637    for (int i = 0; i < regions.length; ++i) {
638      childProcs[i] = new TestRegionProcedure(1, 2, 3 + i, tableName,
639        TableProcedureInterface.TableOperationType.REGION_ASSIGN, regions[i]);
640    }
641    testInheritedXLockAndChildrenSharedLock(tableName,
642      new TestTableProcedure(1, tableName, TableProcedureInterface.TableOperationType.CREATE),
643      childProcs);
644  }
645
646  private void releaseTableExclusiveLockAndComplete(Procedure<?> proc, TableName tableName) {
647    // release xlock
648    queue.wakeTableExclusiveLock(proc, tableName);
649    // mark the procedure as complete
650    queue.completionCleanup(proc);
651  }
652
653  private void testInheritedXLockAndChildrenSharedLock(final TableName tableName,
654    final TestTableProcedure rootProc, final TestRegionProcedure[] childProcs) throws Exception {
655    queue.addBack(rootProc);
656
657    // fetch and acquire first xlock proc
658    Procedure<?> parentProc = queue.poll();
659    assertEquals(rootProc, parentProc);
660    assertEquals(false, queue.waitTableExclusiveLock(parentProc, tableName));
661
662    // add child procedure
663    for (int i = 0; i < childProcs.length; ++i) {
664      queue.addFront(childProcs[i]);
665    }
666
667    // add another xlock procedure (no parent)
668    queue.addBack(
669      new TestTableProcedure(100, tableName, TableProcedureInterface.TableOperationType.EDIT));
670
671    // fetch and execute child
672    for (int i = 0; i < childProcs.length; ++i) {
673      TestRegionProcedure childProc = (TestRegionProcedure) queue.poll();
674      LOG.debug("fetch children " + childProc);
675      assertEquals(false, queue.waitRegions(childProc, tableName, childProc.getRegionInfo()));
676      queue.wakeRegions(childProc, tableName, childProc.getRegionInfo());
677    }
678
679    // nothing available, until xlock release
680    assertEquals(null, queue.poll(0));
681
682    // release xlock
683    releaseTableExclusiveLockAndComplete(parentProc, tableName);
684
685    // fetch the other xlock proc
686    Procedure<?> proc = queue.poll();
687    assertEquals(100, proc.getProcId());
688    assertEquals(false, queue.waitTableExclusiveLock(proc, tableName));
689    releaseTableExclusiveLockAndComplete(proc, tableName);
690  }
691
692  @Test
693  public void testParentXLockAndChildrenXLock() throws Exception {
694    final TableName tableName = TableName.valueOf(testMethodName);
695    testInheritedXLockAndChildrenXLock(tableName,
696      new TestTableProcedure(1, tableName, TableProcedureInterface.TableOperationType.EDIT),
697      new TestTableProcedure(1, 2, tableName, TableProcedureInterface.TableOperationType.EDIT));
698  }
699
700  @Test
701  public void testRootXLockAndChildrenXLock() throws Exception {
702    final TableName tableName = TableName.valueOf(testMethodName);
703    // simulate 3 procedures: 1 (root), (2) child of root, (3) child of proc-2
704    testInheritedXLockAndChildrenXLock(tableName,
705      new TestTableProcedure(1, tableName, TableProcedureInterface.TableOperationType.EDIT),
706      new TestTableProcedure(1, 1, 2, tableName, TableProcedureInterface.TableOperationType.EDIT),
707      new TestTableProcedure(1, 2, 3, tableName, TableProcedureInterface.TableOperationType.EDIT));
708  }
709
710  private void testInheritedXLockAndChildrenXLock(final TableName tableName,
711    final TestTableProcedure rootProc, final TestTableProcedure... childProcs) throws Exception {
712    procedures.put(rootProc.getProcId(), rootProc);
713    for (TestTableProcedure childProc : childProcs) {
714      procedures.put(childProc.getProcId(), childProc);
715    }
716    queue.addBack(rootProc);
717
718    // fetch and acquire first xlock proc
719    Procedure<?> parentProc = queue.poll();
720    assertSame(rootProc, parentProc);
721    assertEquals(false, queue.waitTableExclusiveLock(parentProc, tableName));
722
723    TestTableProcedure childProc = childProcs[childProcs.length - 1];
724    // add child procedure
725    queue.addFront(childProc);
726
727    // fetch the other xlock proc
728    Procedure<?> proc = queue.poll();
729    assertSame(childProc, proc);
730    assertEquals(false, queue.waitTableExclusiveLock(proc, tableName));
731    releaseTableExclusiveLockAndComplete(proc, tableName);
732
733    // release xlock
734    releaseTableExclusiveLockAndComplete(proc, tableName);
735  }
736
737  @Test
738  public void testYieldWithXLockHeld() throws Exception {
739    final TableName tableName = TableName.valueOf(testMethodName);
740
741    queue.addBack(
742      new TestTableProcedure(1, tableName, TableProcedureInterface.TableOperationType.EDIT));
743    queue.addBack(
744      new TestTableProcedure(2, tableName, TableProcedureInterface.TableOperationType.EDIT));
745
746    // fetch from the queue and acquire xlock for the first proc
747    Procedure<?> proc = queue.poll();
748    assertEquals(1, proc.getProcId());
749    assertEquals(false, queue.waitTableExclusiveLock(proc, tableName));
750
751    // nothing available, until xlock release
752    assertEquals(null, queue.poll(0));
753
754    // put the proc in the queue
755    queue.yield(proc);
756
757    // fetch from the queue, it should be the one with just added back
758    proc = queue.poll();
759    assertEquals(1, proc.getProcId());
760
761    // release the xlock
762    releaseTableExclusiveLockAndComplete(proc, tableName);
763
764    proc = queue.poll();
765    assertEquals(2, proc.getProcId());
766  }
767
768  @Test
769  public void testYieldWithSharedLockHeld() throws Exception {
770    final TableName tableName = TableName.valueOf(testMethodName);
771
772    queue.addBack(
773      new TestTableProcedure(1, tableName, TableProcedureInterface.TableOperationType.READ));
774    queue.addBack(
775      new TestTableProcedure(2, tableName, TableProcedureInterface.TableOperationType.READ));
776    queue.addBack(
777      new TestTableProcedure(3, tableName, TableProcedureInterface.TableOperationType.EDIT));
778
779    // fetch and acquire the first shared-lock
780    Procedure<?> proc1 = queue.poll();
781    assertEquals(1, proc1.getProcId());
782    assertEquals(false, queue.waitTableSharedLock(proc1, tableName));
783
784    // fetch and acquire the second shared-lock
785    Procedure<?> proc2 = queue.poll();
786    assertEquals(2, proc2.getProcId());
787    assertEquals(false, queue.waitTableSharedLock(proc2, tableName));
788
789    // nothing available, until xlock release
790    assertEquals(null, queue.poll(0));
791
792    // put the procs back in the queue
793    queue.yield(proc1);
794    queue.yield(proc2);
795
796    // fetch from the queue, it should fetch the ones with just added back
797    proc1 = queue.poll();
798    assertEquals(1, proc1.getProcId());
799    proc2 = queue.poll();
800    assertEquals(2, proc2.getProcId());
801
802    // release the xlock
803    queue.wakeTableSharedLock(proc1, tableName);
804    queue.wakeTableSharedLock(proc2, tableName);
805
806    Procedure<?> proc3 = queue.poll();
807    assertEquals(3, proc3.getProcId());
808  }
809
810  public static class TestTableProcedure extends TestProcedure implements TableProcedureInterface {
811    private final TableOperationType opType;
812    private final TableName tableName;
813
814    public TestTableProcedure() {
815      throw new UnsupportedOperationException("recovery should not be triggered here");
816    }
817
818    public TestTableProcedure(long procId, TableName tableName, TableOperationType opType) {
819      this(-1, procId, tableName, opType);
820    }
821
822    public TestTableProcedure(long parentProcId, long procId, TableName tableName,
823      TableOperationType opType) {
824      this(-1, parentProcId, procId, tableName, opType);
825    }
826
827    public TestTableProcedure(long rootProcId, long parentProcId, long procId, TableName tableName,
828      TableOperationType opType) {
829      super(procId, parentProcId, rootProcId, null);
830      this.tableName = tableName;
831      this.opType = opType;
832    }
833
834    @Override
835    public TableName getTableName() {
836      return tableName;
837    }
838
839    @Override
840    public TableOperationType getTableOperationType() {
841      return opType;
842    }
843
844    @Override
845    public void toStringClassDetails(final StringBuilder sb) {
846      sb.append(getClass().getSimpleName());
847      sb.append("(table=");
848      sb.append(getTableName());
849      sb.append(")");
850    }
851  }
852
853  public static class TestTableProcedureWithEvent extends TestTableProcedure {
854    private final ProcedureEvent<?> event;
855
856    public TestTableProcedureWithEvent(long procId, TableName tableName,
857      TableOperationType opType) {
858      super(procId, tableName, opType);
859      event = new ProcedureEvent<>(tableName + " procId=" + procId);
860    }
861
862    public ProcedureEvent<?> getEvent() {
863      return event;
864    }
865  }
866
867  public static class TestRegionProcedure extends TestTableProcedure {
868    private final RegionInfo[] regionInfos;
869
870    public TestRegionProcedure() {
871      throw new UnsupportedOperationException("recovery should not be triggered here");
872    }
873
874    public TestRegionProcedure(long procId, TableName tableName, TableOperationType opType,
875      RegionInfo... regionInfos) {
876      this(-1, procId, tableName, opType, regionInfos);
877    }
878
879    public TestRegionProcedure(long parentProcId, long procId, TableName tableName,
880      TableOperationType opType, RegionInfo... regionInfos) {
881      this(-1, parentProcId, procId, tableName, opType, regionInfos);
882    }
883
884    public TestRegionProcedure(long rootProcId, long parentProcId, long procId, TableName tableName,
885      TableOperationType opType, RegionInfo... regionInfos) {
886      super(rootProcId, parentProcId, procId, tableName, opType);
887      this.regionInfos = regionInfos;
888    }
889
890    public RegionInfo[] getRegionInfo() {
891      return regionInfos;
892    }
893
894    @Override
895    public void toStringClassDetails(final StringBuilder sb) {
896      sb.append(getClass().getSimpleName());
897      sb.append("(regions=");
898      sb.append(Arrays.toString(getRegionInfo()));
899      sb.append(")");
900    }
901  }
902
903  public static class TestNamespaceProcedure extends TestProcedure
904    implements TableProcedureInterface {
905    private final TableOperationType opType;
906    private final String nsName;
907
908    public TestNamespaceProcedure() {
909      throw new UnsupportedOperationException("recovery should not be triggered here");
910    }
911
912    public TestNamespaceProcedure(long procId, String nsName, TableOperationType opType) {
913      super(procId);
914      this.nsName = nsName;
915      this.opType = opType;
916    }
917
918    @Override
919    public TableName getTableName() {
920      return TableProcedureInterface.DUMMY_NAMESPACE_TABLE_NAME;
921    }
922
923    @Override
924    public TableOperationType getTableOperationType() {
925      return opType;
926    }
927
928    @Override
929    public void toStringClassDetails(final StringBuilder sb) {
930      sb.append(getClass().getSimpleName());
931      sb.append("(ns=");
932      sb.append(nsName);
933      sb.append(")");
934    }
935  }
936
937  public static class TestPeerProcedure extends TestProcedure implements PeerProcedureInterface {
938    private final String peerId;
939    private final PeerOperationType opType;
940
941    public TestPeerProcedure(long procId, String peerId, PeerOperationType opType) {
942      super(procId);
943      this.peerId = peerId;
944      this.opType = opType;
945    }
946
947    @Override
948    public String getPeerId() {
949      return peerId;
950    }
951
952    @Override
953    public PeerOperationType getPeerOperationType() {
954      return opType;
955    }
956  }
957
958  public static class TestGlobalProcedure extends TestProcedure
959    implements GlobalProcedureInterface {
960    private final String globalId;
961
962    public TestGlobalProcedure(long procId, String globalId) {
963      super(procId);
964      this.globalId = globalId;
965    }
966
967    @Override
968    public String getGlobalId() {
969      return globalId;
970    }
971  }
972
973  private static LockProcedure createLockProcedure(LockType lockType, long procId)
974    throws Exception {
975    LockProcedure procedure = new LockProcedure();
976
977    Field typeField = LockProcedure.class.getDeclaredField("type");
978    typeField.setAccessible(true);
979    typeField.set(procedure, lockType);
980
981    Method setProcIdMethod = Procedure.class.getDeclaredMethod("setProcId", long.class);
982    setProcIdMethod.setAccessible(true);
983    setProcIdMethod.invoke(procedure, procId);
984
985    return procedure;
986  }
987
988  private static LockProcedure createExclusiveLockProcedure(long procId) throws Exception {
989    return createLockProcedure(LockType.EXCLUSIVE, procId);
990  }
991
992  private static LockProcedure createSharedLockProcedure(long procId) throws Exception {
993    return createLockProcedure(LockType.SHARED, procId);
994  }
995
996  private static void assertLockResource(LockedResource resource, LockedResourceType resourceType,
997    String resourceName) {
998    assertEquals(resourceType, resource.getResourceType());
999    assertEquals(resourceName, resource.getResourceName());
1000  }
1001
1002  private static void assertExclusiveLock(LockedResource resource, Procedure<?> procedure) {
1003    assertEquals(LockType.EXCLUSIVE, resource.getLockType());
1004    assertEquals(procedure, resource.getExclusiveLockOwnerProcedure());
1005    assertEquals(0, resource.getSharedLockCount());
1006  }
1007
1008  private static void assertSharedLock(LockedResource resource, int lockCount) {
1009    assertEquals(LockType.SHARED, resource.getLockType());
1010    assertEquals(lockCount, resource.getSharedLockCount());
1011  }
1012
1013  @Test
1014  public void testListLocksServer() throws Exception {
1015    LockProcedure procedure = createExclusiveLockProcedure(0);
1016    queue.waitServerExclusiveLock(procedure, ServerName.valueOf("server1,1234,0"));
1017
1018    List<LockedResource> resources = queue.getLocks();
1019    assertEquals(1, resources.size());
1020
1021    LockedResource serverResource = resources.get(0);
1022    assertLockResource(serverResource, LockedResourceType.SERVER, "server1,1234,0");
1023    assertExclusiveLock(serverResource, procedure);
1024    assertTrue(serverResource.getWaitingProcedures().isEmpty());
1025  }
1026
1027  @Test
1028  public void testListLocksNamespace() throws Exception {
1029    LockProcedure procedure = createExclusiveLockProcedure(1);
1030    queue.waitNamespaceExclusiveLock(procedure, "ns1");
1031
1032    List<LockedResource> locks = queue.getLocks();
1033    assertEquals(2, locks.size());
1034
1035    LockedResource namespaceResource = locks.get(0);
1036    assertLockResource(namespaceResource, LockedResourceType.NAMESPACE, "ns1");
1037    assertExclusiveLock(namespaceResource, procedure);
1038    assertTrue(namespaceResource.getWaitingProcedures().isEmpty());
1039
1040    LockedResource tableResource = locks.get(1);
1041    assertLockResource(tableResource, LockedResourceType.TABLE,
1042      TableProcedureInterface.DUMMY_NAMESPACE_TABLE_NAME.getNameAsString());
1043    assertSharedLock(tableResource, 1);
1044    assertTrue(tableResource.getWaitingProcedures().isEmpty());
1045  }
1046
1047  @Test
1048  public void testListLocksTable() throws Exception {
1049    LockProcedure procedure = createExclusiveLockProcedure(2);
1050    queue.waitTableExclusiveLock(procedure, TableName.valueOf("ns2", "table2"));
1051
1052    List<LockedResource> locks = queue.getLocks();
1053    assertEquals(2, locks.size());
1054
1055    LockedResource namespaceResource = locks.get(0);
1056    assertLockResource(namespaceResource, LockedResourceType.NAMESPACE, "ns2");
1057    assertSharedLock(namespaceResource, 1);
1058    assertTrue(namespaceResource.getWaitingProcedures().isEmpty());
1059
1060    LockedResource tableResource = locks.get(1);
1061    assertLockResource(tableResource, LockedResourceType.TABLE, "ns2:table2");
1062    assertExclusiveLock(tableResource, procedure);
1063    assertTrue(tableResource.getWaitingProcedures().isEmpty());
1064  }
1065
1066  @Test
1067  public void testListLocksRegion() throws Exception {
1068    LockProcedure procedure = createExclusiveLockProcedure(3);
1069    RegionInfo regionInfo =
1070      RegionInfoBuilder.newBuilder(TableName.valueOf("ns3", "table3")).build();
1071
1072    queue.waitRegion(procedure, regionInfo);
1073
1074    List<LockedResource> resources = queue.getLocks();
1075    assertEquals(3, resources.size());
1076
1077    LockedResource namespaceResource = resources.get(0);
1078    assertLockResource(namespaceResource, LockedResourceType.NAMESPACE, "ns3");
1079    assertSharedLock(namespaceResource, 1);
1080    assertTrue(namespaceResource.getWaitingProcedures().isEmpty());
1081
1082    LockedResource tableResource = resources.get(1);
1083    assertLockResource(tableResource, LockedResourceType.TABLE, "ns3:table3");
1084    assertSharedLock(tableResource, 1);
1085    assertTrue(tableResource.getWaitingProcedures().isEmpty());
1086
1087    LockedResource regionResource = resources.get(2);
1088    assertLockResource(regionResource, LockedResourceType.REGION, regionInfo.getEncodedName());
1089    assertExclusiveLock(regionResource, procedure);
1090    assertTrue(regionResource.getWaitingProcedures().isEmpty());
1091  }
1092
1093  @Test
1094  public void testListLocksPeer() throws Exception {
1095    String peerId = "1";
1096    LockProcedure procedure = createExclusiveLockProcedure(4);
1097    queue.waitPeerExclusiveLock(procedure, peerId);
1098
1099    List<LockedResource> locks = queue.getLocks();
1100    assertEquals(1, locks.size());
1101
1102    LockedResource resource = locks.get(0);
1103    assertLockResource(resource, LockedResourceType.PEER, peerId);
1104    assertExclusiveLock(resource, procedure);
1105    assertTrue(resource.getWaitingProcedures().isEmpty());
1106
1107    // Try to acquire the exclusive lock again with same procedure
1108    assertFalse(queue.waitPeerExclusiveLock(procedure, peerId));
1109
1110    // Try to acquire the exclusive lock again with new procedure
1111    LockProcedure procedure2 = createExclusiveLockProcedure(5);
1112    assertTrue(queue.waitPeerExclusiveLock(procedure2, peerId));
1113
1114    // Same peerId, still only has 1 LockedResource
1115    locks = queue.getLocks();
1116    assertEquals(1, locks.size());
1117
1118    resource = locks.get(0);
1119    assertLockResource(resource, LockedResourceType.PEER, peerId);
1120    // LockedResource owner still is the origin procedure
1121    assertExclusiveLock(resource, procedure);
1122    // The new procedure should in the waiting list
1123    assertEquals(1, resource.getWaitingProcedures().size());
1124  }
1125
1126  @Test
1127  public void testListLocksGlobal() throws Exception {
1128    String globalId = "1";
1129    LockProcedure procedure = createExclusiveLockProcedure(4);
1130    queue.waitGlobalExclusiveLock(procedure, globalId);
1131
1132    List<LockedResource> locks = queue.getLocks();
1133    assertEquals(1, locks.size());
1134
1135    LockedResource resource = locks.get(0);
1136    assertLockResource(resource, LockedResourceType.GLOBAL, globalId);
1137    assertExclusiveLock(resource, procedure);
1138    assertTrue(resource.getWaitingProcedures().isEmpty());
1139
1140    // Try to acquire the exclusive lock again with same procedure
1141    assertFalse(queue.waitGlobalExclusiveLock(procedure, globalId));
1142
1143    // Try to acquire the exclusive lock again with new procedure
1144    LockProcedure procedure2 = createExclusiveLockProcedure(5);
1145    assertTrue(queue.waitGlobalExclusiveLock(procedure2, globalId));
1146
1147    // Same peerId, still only has 1 LockedResource
1148    locks = queue.getLocks();
1149    assertEquals(1, locks.size());
1150
1151    resource = locks.get(0);
1152    assertLockResource(resource, LockedResourceType.GLOBAL, globalId);
1153    // LockedResource owner still is the origin procedure
1154    assertExclusiveLock(resource, procedure);
1155    // The new procedure should in the waiting list
1156    assertEquals(1, resource.getWaitingProcedures().size());
1157  }
1158
1159  @Test
1160  public void testListLocksWaiting() throws Exception {
1161    LockProcedure procedure1 = createExclusiveLockProcedure(1);
1162    queue.waitTableExclusiveLock(procedure1, TableName.valueOf("ns4", "table4"));
1163
1164    LockProcedure procedure2 = createSharedLockProcedure(2);
1165    queue.waitTableSharedLock(procedure2, TableName.valueOf("ns4", "table4"));
1166
1167    LockProcedure procedure3 = createExclusiveLockProcedure(3);
1168    queue.waitTableExclusiveLock(procedure3, TableName.valueOf("ns4", "table4"));
1169
1170    List<LockedResource> resources = queue.getLocks();
1171    assertEquals(2, resources.size());
1172
1173    LockedResource namespaceResource = resources.get(0);
1174    assertLockResource(namespaceResource, LockedResourceType.NAMESPACE, "ns4");
1175    assertSharedLock(namespaceResource, 1);
1176    assertTrue(namespaceResource.getWaitingProcedures().isEmpty());
1177
1178    LockedResource tableLock = resources.get(1);
1179    assertLockResource(tableLock, LockedResourceType.TABLE, "ns4:table4");
1180    assertExclusiveLock(tableLock, procedure1);
1181
1182    List<Procedure<?>> waitingProcedures = tableLock.getWaitingProcedures();
1183    assertEquals(2, waitingProcedures.size());
1184
1185    LockProcedure waitingProcedure2 = (LockProcedure) waitingProcedures.get(0);
1186    assertEquals(LockType.SHARED, waitingProcedure2.getType());
1187    assertEquals(procedure2, waitingProcedure2);
1188
1189    LockProcedure waitingProcedure3 = (LockProcedure) waitingProcedures.get(1);
1190    assertEquals(LockType.EXCLUSIVE, waitingProcedure3.getType());
1191    assertEquals(procedure3, waitingProcedure3);
1192  }
1193
1194  @Test
1195  public void testAcquireSharedLockWhileParentHoldingExclusiveLock() {
1196    TableName tableName = TableName.valueOf(testMethodName);
1197    RegionInfo regionInfo = RegionInfoBuilder.newBuilder(tableName).build();
1198
1199    TestTableProcedure parentProc = new TestTableProcedure(1, tableName, TableOperationType.EDIT);
1200    TestRegionProcedure proc =
1201      new TestRegionProcedure(1, 2, tableName, TableOperationType.REGION_EDIT, regionInfo);
1202    queue.addBack(parentProc);
1203
1204    assertSame(parentProc, queue.poll());
1205    assertFalse(queue.waitTableExclusiveLock(parentProc, tableName));
1206
1207    // The queue for this table should be added back to run queue as the parent has the xlock, so we
1208    // can poll it out.
1209    queue.addFront(proc);
1210    assertSame(proc, queue.poll());
1211    // the parent has xlock on the table, and it is OK for us to acquire shared lock on the table,
1212    // this is what this test wants to confirm
1213    assertFalse(queue.waitRegion(proc, regionInfo));
1214
1215    queue.wakeRegion(proc, regionInfo);
1216    queue.wakeTableExclusiveLock(parentProc, tableName);
1217  }
1218}