public void asyncClientTimeoutTest() throws Exception {
    // init asyncClientManager
    ClientManager<TEndPoint, AsyncDataNodeInternalServiceClient> asyncClusterManager =
        (ClientManager<TEndPoint, AsyncDataNodeInternalServiceClient>)
            new IClientManager.Factory<TEndPoint, AsyncDataNodeInternalServiceClient>()
                .createClientManager(new TestAsyncDataNodeInternalServiceClientPoolFactory());

    // normal RPC
    AsyncDataNodeInternalServiceClient asyncClient = asyncClusterManager.borrowClient(endPoint);
    CountDownLatch latch = new CountDownLatch(1);
    AtomicBoolean failed = new AtomicBoolean(false);
    CountDownLatch finalLatch = latch;
    AtomicBoolean finalFailed = failed;
    asyncClient.merge(
        new AsyncMethodCallback<TSStatus>() {
          @Override
          public void onComplete(TSStatus response) {
            finalLatch.countDown();
          }

          @Override
          public void onError(Exception exception) {
            finalFailed.set(true);
            finalLatch.countDown();
          }
        });
    latch.await();
    if (failed.get()) {
      Assert.fail("There should be no timeout here");
    }
    Assert.assertEquals(0, asyncClusterManager.getPool().getNumActive(endPoint));
    Assert.assertEquals(1, asyncClusterManager.getPool().getNumIdle(endPoint));

    // timeout RPC
    asyncClient = asyncClusterManager.borrowClient(endPoint);
    latch = new CountDownLatch(1);
    failed = new AtomicBoolean(false);
    AtomicBoolean finalFailed1 = failed;
    CountDownLatch finalLatch1 = latch;
    asyncClient.clearCache(
        Collections.singleton(CacheClearOptions.DEFAULT.ordinal()),
        new AsyncMethodCallback<TSStatus>() {
          @Override
          public void onComplete(TSStatus response) {
            finalFailed1.set(true);
            finalLatch1.countDown();
          }

          @Override
          public void onError(Exception exception) {
            finalLatch1.countDown();
          }
        });
    latch.await();
    if (failed.get()) {
      Assert.fail("A timeout exception should occur here");
    }
    Assert.assertEquals(0, asyncClusterManager.getPool().getNumActive(endPoint));
    Assert.assertEquals(0, asyncClusterManager.getPool().getNumIdle(endPoint));

    asyncClusterManager.close();
  }
We put all tests together to avoid frequent restarts of thrift Servers, which can cause "bind
address already used" problems in macOS CI environments. The reason for this may be about this
<a
href="https://stackoverflow.com/questions/51998042/macos-so-reuseaddr-so-reuseport-not-consistent-with-linux">blog</a>
iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/client/ClientManagerTest.java