public void evictionTest() throws Exception {
    List<SyncDataNodeInternalServiceClient> evictionTestClients = new ArrayList<>();
    int maxClientForEachNode = 2;
    long minIdleDuration = TimeUnit.SECONDS.toMillis(10);
    long evictionRunsDuration = TimeUnit.SECONDS.toMillis(2);

    // init syncClientManager and set minIdleDuation and evictionRunsDuration
    ClientManager<TEndPoint, SyncDataNodeInternalServiceClient> syncClusterManager =
        (ClientManager<TEndPoint, SyncDataNodeInternalServiceClient>)
            new IClientManager.Factory<TEndPoint, SyncDataNodeInternalServiceClient>()
                .createClientManager(
                    new TestSyncDataNodeInternalServiceClientPoolFactory() {
                      @Override
                      public GenericKeyedObjectPool<TEndPoint, SyncDataNodeInternalServiceClient>
                          createClientPool(
                              ClientManager<TEndPoint, SyncDataNodeInternalServiceClient> manager) {
                        return new GenericKeyedObjectPool<>(
                            new SyncDataNodeInternalServiceClient.Factory(
                                manager, new ThriftClientProperty.Builder().build()),
                            new ClientPoolProperty.Builder<SyncDataNodeInternalServiceClient>()
                                .setMaxClientNumForEachNode(maxClientForEachNode)
                                .setMinIdleTimeForClient(minIdleDuration)
                                .setTimeBetweenEvictionRuns(evictionRunsDuration)
                                .build()
                                .getConfig());
                      }
                    });

    // get one sync client
    SyncDataNodeInternalServiceClient syncClient1 = syncClusterManager.borrowClient(endPoint);
    evictionTestClients.add(syncClient1);
    Assert.assertNotNull(syncClient1);
    Assert.assertEquals(syncClient1.getTEndpoint(), endPoint);
    Assert.assertEquals(syncClient1.getClientManager(), syncClusterManager);
    Assert.assertTrue(syncClient1.getInputProtocol().getTransport().isOpen());
    Assert.assertEquals(1, syncClusterManager.getPool().getNumActive(endPoint));
    Assert.assertEquals(0, syncClusterManager.getPool().getNumIdle(endPoint));

    // get another sync client
    SyncDataNodeInternalServiceClient syncClient2 = syncClusterManager.borrowClient(endPoint);
    evictionTestClients.add(syncClient2);
    Assert.assertNotNull(syncClient2);
    Assert.assertEquals(syncClient2.getTEndpoint(), endPoint);
    Assert.assertEquals(syncClient2.getClientManager(), syncClusterManager);
    Assert.assertTrue(syncClient2.getInputProtocol().getTransport().isOpen());
    Assert.assertEquals(2, syncClusterManager.getPool().getNumActive(endPoint));
    Assert.assertEquals(0, syncClusterManager.getPool().getNumIdle(endPoint));

    // return one sync client
    syncClient1.close();
    Assert.assertEquals(1, syncClusterManager.getPool().getNumActive(endPoint));
    Assert.assertEquals(1, syncClusterManager.getPool().getNumIdle(endPoint));

    // return another sync client
    syncClient2.close();
    Assert.assertEquals(0, syncClusterManager.getPool().getNumActive(endPoint));
    Assert.assertEquals(2, syncClusterManager.getPool().getNumIdle(endPoint));

    long start = System.currentTimeMillis();
    while (syncClusterManager.getPool().getNumIdle() > 0
        || (syncClient1.getInputProtocol().getTransport().isOpen()
            || syncClient2.getInputProtocol().getTransport().isOpen())) {
      for (SyncDataNodeInternalServiceClient evictionTestClient : evictionTestClients) {
        // if this client is evicted, skip it
        if (!evictionTestClient.getInputProtocol().getTransport().isOpen()) continue;
        // test eviction
        long current = System.currentTimeMillis();
        // for each idle client, its theoretical max idle time is `minIdleDuration` +
        // `evictionRunsDuration`. Taking into account the difference in thread scheduling rates of
        // different machines, here we multiply by 6
        if ((current - start) > (minIdleDuration + evictionRunsDuration) * 6) {
          Assert.fail("Evict invalid client failed");
        }
      }
      Thread.sleep(100);
    }
    // since the two clients are idle for more than 10s, which exceeds `minIdleDuration`, they
    // should be destroyed.
    Assert.assertEquals(0, syncClusterManager.getPool().getNumActive(endPoint));
    Assert.assertEquals(0, syncClusterManager.getPool().getNumIdle(endPoint));
  }
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