@VisibleForTesting
    protected static TFeLocksItem resolveLockInfo(Database db) {
        var lock = db.getRwLock();
        TFeLocksItem lockItem = new TFeLocksItem();
        lockItem.setLock_type("DATABASE");
        lockItem.setLock_object(db.getFullName());

        Thread owner = lock.getOwner();
        Set<Thread> sharedLockThreads = lock.getSharedLockThreads();
        long currentTime = System.currentTimeMillis();

        if (owner != null) {
            lockItem.setLock_mode("EXCLUSIVE");
            lockItem.setGranted(true);
            JsonObject ownerInfo = new JsonObject();
            ownerInfo.addProperty("threadId", owner.getId());
            ownerInfo.addProperty("threadName", owner.getName());
            lockItem.setThread_info(ownerInfo.toString());

            // wait start
            long lockStartTime = lock.getExclusiveLockStartTimeMs();
            lockItem.setStart_time(lockStartTime);
            lockItem.setHold_time_ms(currentTime - lockStartTime);
        } else if (CollectionUtils.isNotEmpty(sharedLockThreads)) {
            lockItem.setLock_mode("SHARED");
            lockItem.setGranted(true);

            // lock start
            long lockStart = SetUtils.emptyIfNull(sharedLockThreads).stream()
                    .map(lock::getSharedLockStartTimeMs)
                    .filter(x -> x > 0)
                    .min(Comparator.naturalOrder()).orElse(0L);
            lockItem.setStart_time(lockStart);
            lockItem.setHold_time_ms(currentTime - lockStart);

            // thread info
            JsonArray sharedLockInfo = new JsonArray();
            for (Thread thread : SetUtils.emptyIfNull(sharedLockThreads)) {
                JsonObject lockInfo = new JsonObject();
                lockInfo.addProperty("threadId", thread.getId());
                lockInfo.addProperty("threadName", thread.getName());
                sharedLockInfo.add(lockInfo);
            }
            lockItem.setThread_info(sharedLockInfo.toString());

        } else {
            lockItem.setGranted(false);
        }

        // waiters
        lockItem.setWaiter_list(LockChecker.getLockWaiterInfoJsonArray(lock.getQueuedThreads()).toString());

        return lockItem;
    }
@Test
    public void testResolveLockItem() throws InterruptedException {
        Config.lock_manager_enabled = false;

        Database db = new Database(1, "test_lock");

        // empty lock
        {
            TFeLocksItem item = SysFeLocks.resolveLockInfo(db);
            assertEquals("TFeLocksItem(lock_type:DATABASE, lock_object:test_lock, granted:false, waiter_list:[])",
                    item.toString());
        }

        // exclusive owner
        {
            Locker locker = new Locker();
            locker.lockDatabase(db, LockType.WRITE);
            TFeLocksItem item = SysFeLocks.resolveLockInfo(db);

            assertEquals("EXCLUSIVE", item.getLock_mode());
            assertTrue(item.isGranted());
            assertTrue(item.getStart_time() > 0);
            assertTrue(item.getHold_time_ms() >= 0);
            assertEquals("[]", item.getWaiter_list());

            // add a waiter
            Thread waiter = new Thread(() -> {
                locker.lockDatabase(db, LockType.WRITE);
                locker.unLockDatabase(db, LockType.WRITE);
            }, "waiter");
            waiter.start();

            while (waiter.getState() != State.WAITING) {
                Thread.sleep(1000);
            }

            item = SysFeLocks.resolveLockInfo(db);
            assertEquals(String.format("[{\"threadId\":%d,\"threadName\":\"%s\"}]", waiter.getId(), waiter.getName()),
                    item.getWaiter_list());

            locker.unLockDatabase(db, LockType.WRITE);
        }

        // shared lock
        {
            Locker locker = new Locker();
            locker.lockDatabase(db, LockType.READ);
            TFeLocksItem item = SysFeLocks.resolveLockInfo(db);

            assertEquals("SHARED", item.getLock_mode());
            assertTrue(item.isGranted());
            assertTrue(item.getStart_time() > 0);
            assertTrue(item.getHold_time_ms() >= 0);
            assertEquals("[]", item.getWaiter_list());

            // add a waiter
            Thread waiter = new Thread(() -> {
                locker.lockDatabase(db, LockType.WRITE);
                locker.unLockDatabase(db, LockType.WRITE);
            }, "waiter");
            waiter.start();

            // 1. start waiter (blocked)
            // 2. waiter acquired the write lock
            // 3. two threads share the lock
            // 4. two threads release the lock

            while (waiter.getState() != State.WAITING) {
                Thread.sleep(1000);
            }

            item = SysFeLocks.resolveLockInfo(db);
            assertEquals(String.format("[{\"threadId\":%d,\"threadName\":\"%s\"}]", waiter.getId(), waiter.getName()),
                    item.getWaiter_list());
            locker.unLockDatabase(db, LockType.READ);
        }

        Config.lock_manager_enabled = true;
    }