@Test
  public void testTXGrantorMigration() throws Exception {
    // first make sure some other VM is the grantor
    Host.getHost(0).getVM(0).invoke("become lock grantor", () -> {
      TXLockService.createDTLS(system);
      TXLockService vm0dtls = TXLockService.getDTLS();
      DLockService vm0dlock = ((TXLockServiceImpl) vm0dtls).getInternalDistributedLockService();
      vm0dlock.becomeLockGrantor();
    });

    TXLockService.createDTLS(system);
    checkDLockRecoverGrantorMessageProcessor();

    /*
     * call TXRecoverGrantorMessageProcessor.process directly to make sure that correct behavior
     * occurs
     */

    // get txLock and hold it
    final List regionLockReqs = new ArrayList();

    Map<Object, Boolean> keymap = new HashMap<>();
    keymap.put("KEY-1", true);
    keymap.put("KEY-2", true);
    keymap.put("KEY-3", true);
    keymap.put("KEY-4", true);
    regionLockReqs
        .add(new TXRegionLockRequestImpl(SEPARATOR + "testTXRecoverGrantorMessageProcessor2",
            keymap));

    TXLockService dtls = TXLockService.getDTLS();
    TXLockId txLockId = dtls.txLock(regionLockReqs, Collections.EMPTY_SET);

    final DLockService dlock = ((TXLockServiceImpl) dtls).getInternalDistributedLockService();

    // GEODE-2024: now cause grantor migration while holding the recoveryReadLock.
    // It will lock up in TXRecoverGrantorMessageProcessor until the recoveryReadLock
    // is released. Demonstrate that dtls.release() does not block forever and releases the
    // recoveryReadLock
    // allowing grantor migration to finish

    // create an observer that will block recovery messages from being processed
    MessageObserver observer = new MessageObserver();
    DistributionMessageObserver.setInstance(observer);

    try {
      System.out.println("starting thread to take over being lock grantor from vm0");

      // become the grantor - this will block waiting for a reply to the message blocked by the
      // observer
      Thread thread = new Thread(dlock::becomeLockGrantor);
      thread.setName("TXLockServiceDUnitTest thread2");
      thread.setDaemon(true);
      thread.start();

      await("waiting for recovery to begin").until(observer::isPreventingProcessing);


      // spawn a thread that will unblock message processing
      // so that TXLockServiceImpl's "recovering" variable will be set
      System.out.println("starting a thread to unblock recovery in 5 seconds");
      Thread unblockThread = new Thread(() -> {
        try {
          Thread.sleep(5000);
        } catch (InterruptedException e) {
          throw new RuntimeException("sleep interrupted");
        }
        System.out.println("releasing block of recovery message processing");
        observer.releasePreventionOfProcessing();
      });
      unblockThread.setName("TXLockServiceDUnitTest unblockThread");
      unblockThread.setDaemon(true);
      unblockThread.start();

      // release txLock - this will block until unblockThread tells the observer
      // that it can process its message. Then it should release the recovery read-lock
      // allowing the grantor to finish recovery
      System.out.println("releasing transaction locks, which should block for a bit");
      dtls.release(txLockId);

      await("waiting for recovery to finish").until(() -> {
        return !((TXLockServiceImpl) dtls).isRecovering();
      });
    } finally {
      observer.releasePreventionOfProcessing();
      DistributionMessageObserver.setInstance(null);
    }
  }
Returns a previously created (or new, if this is the first time this method is called in this
VM) distributed system which is somewhat configurable via hydra test parameters.
geode-core/src/distributedTest/java/org/apache/geode/internal/cache/locks/TXLockServiceDUnitTest.java