TEST(LockManager, MultipleConflict) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

    MMAPV1LockerImpl locker;
    TrackingLockGrantNotification notify;

    LockRequest request[6];
    for (int i = 0; i < 6; i++) {
        request[i].initNew(&locker, &notify);

        if (i == 0) {
            ASSERT(LOCK_OK == lockMgr.lock(resId, &request[i], MODE_X));
        } else {
            ASSERT(LOCK_WAITING == lockMgr.lock(resId, &request[i], MODE_X));
        }

        ASSERT(request[i].mode == MODE_X);
        ASSERT(request[i].recursiveCount == 1);
    }

    ASSERT(notify.numNotifies == 0);

    // Free them one by one and make sure they get granted in the correct order
    for (int i = 0; i < 6; i++) {
        lockMgr.unlock(&request[i]);

        if (i < 5) {
            ASSERT(notify.numNotifies == i + 1);
        }
    }
}
Exemple #2
0
    TEST(LockManager, Downgrade) {
        LockManager lockMgr;
        const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

        LockState locker1;
        TrackingLockGrantNotification notify1;
        LockRequest request1;
        request1.initNew(&locker1, &notify1);
        ASSERT(LOCK_OK == lockMgr.lock(resId, &request1, MODE_X));

        LockState locker2;
        TrackingLockGrantNotification notify2;
        LockRequest request2;
        request2.initNew(&locker2, &notify2);
        ASSERT(LOCK_WAITING == lockMgr.lock(resId, &request2, MODE_S));
        ASSERT(request2.recursiveCount == 1);

        lockMgr.downgrade(&request1, MODE_S);
        ASSERT(notify2.numNotifies == 1);
        ASSERT(request2.recursiveCount == 1);

        lockMgr.unlock(&request1);
        ASSERT(request1.recursiveCount == 0);

        lockMgr.unlock(&request2);
        ASSERT(request2.recursiveCount == 0);
    }
TEST(LockManager, CancelWaitingConversionStrongModes) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

    MMAPV1LockerImpl locker1;
    MMAPV1LockerImpl locker2;

    LockRequestCombo request1(&locker1);
    LockRequestCombo request2(&locker2);

    // First request granted right away
    ASSERT(LOCK_OK == lockMgr.lock(resId, &request1, MODE_S));
    ASSERT(request1.numNotifies == 0);

    // Second request is granted right away
    ASSERT(LOCK_OK == lockMgr.lock(resId, &request2, MODE_S));
    ASSERT(request2.numNotifies == 0);

    // Convert second request to conflicting
    ASSERT(LOCK_WAITING == lockMgr.convert(resId, &request2, MODE_X));
    ASSERT(request2.mode == MODE_S);
    ASSERT(request2.convertMode == MODE_X);
    ASSERT(request2.numNotifies == 0);

    // Cancel the conflicting upgrade
    lockMgr.unlock(&request2);
    ASSERT(request2.mode == MODE_S);
    ASSERT(request2.convertMode == MODE_NONE);
    ASSERT(request2.numNotifies == 0);

    // Free the remaining locks so the LockManager destructor does not complain
    lockMgr.unlock(&request1);
    lockMgr.unlock(&request2);
}
Exemple #4
0
    TEST(LockManager, ConflictingConversionInTheMiddle) {
        LockManager lockMgr;
        const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

        LockState locker;
        TrackingLockGrantNotification notify;

        LockRequest request[3];
        for (int i = 0; i < 3; i++) {
            request[i].initNew(&locker, &notify);
            lockMgr.lock(resId, &request[i], MODE_S);
        }

        // Upgrade the one in the middle (not the first one)
        ASSERT(LOCK_WAITING == lockMgr.lock(resId, &request[1], MODE_X));

        ASSERT(notify.numNotifies == 0);

        // Release the two shared modes
        lockMgr.unlock(&request[0]);
        ASSERT(notify.numNotifies == 0);

        lockMgr.unlock(&request[2]);
        ASSERT(notify.numNotifies == 1);

        ASSERT(request[1].mode == MODE_X);

        // Request 1 should be unlocked twice
        lockMgr.unlock(&request[1]);
        lockMgr.unlock(&request[1]);
    }
TEST(LockManager, ConflictingConversion) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

    MMAPV1LockerImpl locker1;
    MMAPV1LockerImpl locker2;

    LockRequestCombo request1(&locker1);
    LockRequestCombo request2(&locker2);

    // The S requests are granted right away
    ASSERT(LOCK_OK == lockMgr.lock(resId, &request1, MODE_S));
    ASSERT(request1.numNotifies == 0);

    ASSERT(LOCK_OK == lockMgr.lock(resId, &request2, MODE_S));
    ASSERT(request2.numNotifies == 0);

    // Convert first request to conflicting
    ASSERT(LOCK_WAITING == lockMgr.convert(resId, &request1, MODE_X));
    ASSERT(request1.numNotifies == 0);

    // Free the second lock and make sure the first is granted
    lockMgr.unlock(&request2);
    ASSERT(request1.mode == MODE_X);
    ASSERT(request1.numNotifies == 1);
    ASSERT(request2.numNotifies == 0);

    // Frees the first reference, mode remains X
    lockMgr.unlock(&request1);
    ASSERT(request1.mode == MODE_X);
    ASSERT(request1.recursiveCount == 1);

    lockMgr.unlock(&request1);
}
// Lock conflict matrix tests
static void checkConflict(LockMode existingMode, LockMode newMode, bool hasConflict) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

    MMAPV1LockerImpl lockerExisting;
    TrackingLockGrantNotification notifyExisting;
    LockRequest requestExisting;
    requestExisting.initNew(&lockerExisting, &notifyExisting);

    ASSERT(LOCK_OK == lockMgr.lock(resId, &requestExisting, existingMode));

    MMAPV1LockerImpl lockerNew;
    TrackingLockGrantNotification notifyNew;
    LockRequest requestNew;
    requestNew.initNew(&lockerNew, &notifyNew);

    LockResult result = lockMgr.lock(resId, &requestNew, newMode);
    if (hasConflict) {
        ASSERT_EQUALS(LOCK_WAITING, result);
    } else {
        ASSERT_EQUALS(LOCK_OK, result);
    }

    lockMgr.unlock(&requestNew);
    lockMgr.unlock(&requestExisting);
}
TEST(LockManager, ConflictCancelWaiting) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

    MMAPV1LockerImpl locker1;
    TrackingLockGrantNotification notify1;

    MMAPV1LockerImpl locker2;
    TrackingLockGrantNotification notify2;

    LockRequest request1;
    request1.initNew(&locker1, &notify1);

    LockRequest request2;
    request2.initNew(&locker2, &notify2);

    // First request granted right away
    ASSERT(LOCK_OK == lockMgr.lock(resId, &request1, MODE_S));
    ASSERT(notify1.numNotifies == 0);

    ASSERT(LOCK_WAITING == lockMgr.lock(resId, &request2, MODE_X));

    // Release second request (which is still in the WAITING mode)
    lockMgr.unlock(&request2);
    ASSERT(notify2.numNotifies == 0);

    ASSERT(request1.mode == MODE_S);
    ASSERT(request1.recursiveCount == 1);

    // Release second acquire
    lockMgr.unlock(&request1);
}
Exemple #8
0
    TEST(LockManager, GrantRecursiveNonCompatibleConvertDown) {
        LockManager lockMgr;
        const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

        LockState locker;
        TrackingLockGrantNotification notify;

        LockRequest request;
        request.initNew(&locker, &notify);

        ASSERT(LOCK_OK == lockMgr.lock(resId, &request, MODE_X));
        ASSERT(request.mode == MODE_X);
        ASSERT(request.recursiveCount == 1);
        ASSERT(notify.numNotifies == 0);

        // Acquire again, in *non-compatible*, but less strict mode
        ASSERT(LOCK_OK == lockMgr.lock(resId, &request, MODE_S));
        ASSERT(request.mode == MODE_X);
        ASSERT(request.recursiveCount == 2);
        ASSERT(notify.numNotifies == 0);

        // Release first acquire
        lockMgr.unlock(&request);
        ASSERT(request.mode == MODE_X);
        ASSERT(request.recursiveCount == 1);

        // Release second acquire
        lockMgr.unlock(&request);
        ASSERT(request.recursiveCount == 0);
    }
TEST(LockManager, GrantRecursiveNonCompatibleConvertDown) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

    MMAPV1LockerImpl locker;
    LockRequestCombo request(&locker);

    ASSERT(LOCK_OK == lockMgr.lock(resId, &request, MODE_X));
    ASSERT(request.mode == MODE_X);
    ASSERT(request.recursiveCount == 1);
    ASSERT(request.numNotifies == 0);

    // Acquire again, in *non-compatible*, but less strict mode
    ASSERT(LOCK_OK == lockMgr.convert(resId, &request, MODE_S));
    ASSERT(request.mode == MODE_X);
    ASSERT(request.recursiveCount == 2);
    ASSERT(request.numNotifies == 0);

    // Release first acquire
    lockMgr.unlock(&request);
    ASSERT(request.mode == MODE_X);
    ASSERT(request.recursiveCount == 1);

    // Release second acquire
    lockMgr.unlock(&request);
    ASSERT(request.recursiveCount == 0);
}
TEST(LockManager, GrantMultipleFIFOOrder) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

    std::unique_ptr<MMAPV1LockerImpl> locker[6];
    for (int i = 0; i < 6; i++) {
        locker[i].reset(new MMAPV1LockerImpl());
    }

    TrackingLockGrantNotification notify[6];

    LockRequest request[6];
    for (int i = 0; i < 6; i++) {
        request[i].initNew(locker[i].get(), &notify[i]);
        lockMgr.lock(resId, &request[i], MODE_X);

        ASSERT(request[i].mode == MODE_X);
        ASSERT(request[i].recursiveCount == 1);
    }

    // Release the last held lock and ensure the next one, based on time is granted
    for (int i = 0; i < 5; i++) {
        lockMgr.unlock(&request[i]);

        ASSERT(notify[i + 1].numNotifies == 1);
        ASSERT(notify[i + 1].lastResId == resId);
        ASSERT(notify[i + 1].lastResult == LOCK_OK);
    }

    // Release the last one
    lockMgr.unlock(&request[5]);
}
TEST(LockManager, ConflictCancelMultipleWaiting) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

    MMAPV1LockerImpl locker;
    TrackingLockGrantNotification notify;

    LockRequest request[6];
    for (int i = 0; i < 6; i++) {
        request[i].initNew(&locker, &notify);
        lockMgr.lock(resId, &request[i], MODE_X);

        ASSERT(request[i].mode == MODE_X);
        ASSERT(request[i].recursiveCount == 1);
    }

    ASSERT(notify.numNotifies == 0);

    // Free the second (waiting)
    lockMgr.unlock(&request[1]);

    // Free the last
    lockMgr.unlock(&request[5]);

    // Free one in the middle
    lockMgr.unlock(&request[3]);

    // Free the remaining so the LockMgr does not compain about leaked locks
    lockMgr.unlock(&request[2]);
    lockMgr.unlock(&request[4]);
    lockMgr.unlock(&request[0]);
}
Exemple #12
0
    TEST(LockManager, Conflict) {
        LockManager lockMgr;
        const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

        LockState locker1;
        TrackingLockGrantNotification notify1;

        LockState locker2;
        TrackingLockGrantNotification notify2;        

        LockRequest request1;
        request1.initNew(&locker1, &notify1);

        LockRequest request2;
        request2.initNew(&locker2, &notify2);

        // First request granted right away
        ASSERT(LOCK_OK == lockMgr.lock(resId, &request1, MODE_S));
        ASSERT(request1.recursiveCount == 1);
        ASSERT(notify1.numNotifies == 0);

        // Second request must block
        ASSERT(LOCK_WAITING == lockMgr.lock(resId, &request2, MODE_X));
        ASSERT(request2.mode == MODE_X);
        ASSERT(request2.recursiveCount == 1);
        ASSERT(notify2.numNotifies == 0);

        // Release first request
        lockMgr.unlock(&request1);
        ASSERT(request1.recursiveCount == 0);
        ASSERT(notify1.numNotifies == 0);

        ASSERT(request2.mode == MODE_X);
        ASSERT(request2.recursiveCount == 1);
        ASSERT(notify2.numNotifies == 1);
        ASSERT(notify2.lastResult == LOCK_OK);

        // Release second acquire
        lockMgr.unlock(&request2);
        ASSERT(request2.recursiveCount == 0);

        ASSERT(notify1.numNotifies == 0);
        ASSERT(notify2.numNotifies == 1);
    }
TEST(LockManager, ConvertUpgrade) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

    MMAPV1LockerImpl locker1;
    LockRequestCombo request1(&locker1);
    ASSERT(LOCK_OK == lockMgr.lock(resId, &request1, MODE_S));

    MMAPV1LockerImpl locker2;
    LockRequestCombo request2(&locker2);
    ASSERT(LOCK_OK == lockMgr.lock(resId, &request2, MODE_S));

    // Upgrade the S lock to X
    ASSERT(LOCK_WAITING == lockMgr.convert(resId, &request1, MODE_X));

    ASSERT(!lockMgr.unlock(&request1));
    ASSERT(lockMgr.unlock(&request1));

    ASSERT(lockMgr.unlock(&request2));
}
TEST(LockManager, CompatibleFirstGrantAlreadyQueued) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_GLOBAL, 0);

    // This tests the following behavior:
    //   Lock held in X, queue: S IX IS, where S is compatibleFirst.
    //   Once X unlocks both the S and IS requests should proceed.

    MMAPV1LockerImpl locker1;
    LockRequestCombo request1(&locker1);

    MMAPV1LockerImpl locker2;
    LockRequestCombo request2(&locker2);
    request2.compatibleFirst = true;

    MMAPV1LockerImpl locker3;
    LockRequestCombo request3(&locker3);

    MMAPV1LockerImpl locker4;
    LockRequestCombo request4(&locker4);

    // Hold the lock in X and establish the S IX IS queue.
    ASSERT(LOCK_OK == lockMgr.lock(resId, &request1, MODE_X));
    ASSERT(LOCK_WAITING == lockMgr.lock(resId, &request2, MODE_S));
    ASSERT(LOCK_WAITING == lockMgr.lock(resId, &request3, MODE_IX));
    ASSERT(LOCK_WAITING == lockMgr.lock(resId, &request4, MODE_IS));

    // Now unlock, so all readers should be able to proceed, while the IX remains queued.
    ASSERT(lockMgr.unlock(&request1));
    ASSERT(request2.lastResult == LOCK_OK);
    ASSERT(request3.lastResult == LOCK_INVALID);
    ASSERT(request4.lastResult == LOCK_OK);

    // Now unlock the S lock, and the IX succeeds as well.
    ASSERT(lockMgr.unlock(&request2));
    ASSERT(request3.lastResult == LOCK_OK);

    // Unlock remaining
    ASSERT(lockMgr.unlock(&request4));
    ASSERT(lockMgr.unlock(&request3));
}
Exemple #15
0
    TEST(LockManager, ConflictCancelWaitingConversion) {
        LockManager lockMgr;
        const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

        LockState locker1;
        TrackingLockGrantNotification notify1;

        LockState locker2;
        TrackingLockGrantNotification notify2;

        LockRequest request1;
        request1.initNew(&locker1, &notify1);

        LockRequest request2;
        request2.initNew(&locker2, &notify2);

        // First request granted right away
        ASSERT(LOCK_OK == lockMgr.lock(resId, &request1, MODE_S));
        ASSERT(notify1.numNotifies == 0);

        // Second request is granted right away
        ASSERT(LOCK_OK == lockMgr.lock(resId, &request2, MODE_S));
        ASSERT(notify2.numNotifies == 0);

        // Convert second request to conflicting
        ASSERT(LOCK_WAITING == lockMgr.lock(resId, &request2, MODE_X));
        ASSERT(request2.mode == MODE_S);
        ASSERT(request2.convertMode == MODE_X);
        ASSERT(notify2.numNotifies == 0);

        // Cancel the conflicting upgrade
        lockMgr.unlock(&request2);
        ASSERT(request2.mode == MODE_S);
        ASSERT(request2.convertMode == MODE_NONE);
        ASSERT(notify2.numNotifies == 0);

        // Free the remaining locks so the LockManager destructor does not complain
        lockMgr.unlock(&request1);
        lockMgr.unlock(&request2);
    }
Exemple #16
0
    TEST(LockManager, ConflictingConversion) {
        LockManager lockMgr;
        const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

        LockState locker1;
        TrackingLockGrantNotification notify1;

        LockState locker2;
        TrackingLockGrantNotification notify2;

        LockRequest request1;
        request1.initNew(&locker1, &notify1);

        LockRequest request2;
        request2.initNew(&locker2, &notify2);

        // First request granted right away
        ASSERT(LOCK_OK == lockMgr.lock(resId, &request1, MODE_S));
        ASSERT(notify1.numNotifies == 0);

        ASSERT(LOCK_OK == lockMgr.lock(resId, &request2, MODE_S));
        ASSERT(notify2.numNotifies == 0);

        // Convert first request to conflicting
        ASSERT(LOCK_WAITING == lockMgr.lock(resId, &request1, MODE_X));
        ASSERT(notify1.numNotifies == 0);

        // Free the second lock and make sure the first is granted
        lockMgr.unlock(&request2);
        ASSERT(request1.mode == MODE_X);
        ASSERT(notify1.numNotifies == 1);
        ASSERT(notify2.numNotifies == 0);

        // Frees the first reference, mode remains X
        lockMgr.unlock(&request1);
        ASSERT(request1.mode == MODE_X);
        ASSERT(request1.recursiveCount == 1);

        lockMgr.unlock(&request1);
    }
TEST(LockManager, CompatibleFirstCancelWaiting) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_GLOBAL, 0);

    MMAPV1LockerImpl lockerSInitial;
    LockRequestCombo requestSInitial(&lockerSInitial);
    ASSERT(LOCK_OK == lockMgr.lock(resId, &requestSInitial, MODE_S));

    MMAPV1LockerImpl lockerX;
    LockRequestCombo requestX(&lockerX);
    ASSERT(LOCK_WAITING == lockMgr.lock(resId, &requestX, MODE_X));

    MMAPV1LockerImpl lockerPending;
    LockRequestCombo requestPending(&lockerPending);
    requestPending.compatibleFirst = true;
    ASSERT(LOCK_WAITING == lockMgr.lock(resId, &requestPending, MODE_S));

    // S1 is not granted yet, so the policy should still be FIFO
    {
        MMAPV1LockerImpl lockerS;
        LockRequestCombo requestS(&lockerS);
        ASSERT(LOCK_WAITING == lockMgr.lock(resId, &requestS, MODE_S));
        ASSERT(lockMgr.unlock(&requestS));
    }

    // Unlock S1, the policy should still be FIFO
    ASSERT(lockMgr.unlock(&requestPending));

    {
        MMAPV1LockerImpl lockerS;
        LockRequestCombo requestS(&lockerS);
        ASSERT(LOCK_WAITING == lockMgr.lock(resId, &requestS, MODE_S));
        ASSERT(lockMgr.unlock(&requestS));
    }

    // Unlock remaining locks to keep the leak detection logic happy
    ASSERT(lockMgr.unlock(&requestSInitial));
    ASSERT(lockMgr.unlock(&requestX));
}
TEST(LockManager, Downgrade) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

    MMAPV1LockerImpl locker1;
    LockRequestCombo request1(&locker1);
    ASSERT(LOCK_OK == lockMgr.lock(resId, &request1, MODE_X));

    MMAPV1LockerImpl locker2;
    LockRequestCombo request2(&locker2);
    ASSERT(LOCK_WAITING == lockMgr.lock(resId, &request2, MODE_S));

    // Downgrade the X request to S
    lockMgr.downgrade(&request1, MODE_S);

    ASSERT(request2.numNotifies == 1);
    ASSERT(request2.lastResult == LOCK_OK);
    ASSERT(request2.recursiveCount == 1);

    ASSERT(lockMgr.unlock(&request1));
    ASSERT(lockMgr.unlock(&request2));
}
TEST(LockManager, Fairness) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_GLOBAL, 0);

    // Start with some 'regular' intent locks
    MMAPV1LockerImpl lockerIS;
    LockRequestCombo requestIS(&lockerIS);
    ASSERT(LOCK_OK == lockMgr.lock(resId, &requestIS, MODE_IS));

    MMAPV1LockerImpl lockerIX;
    LockRequestCombo requestIX(&lockerIX);
    ASSERT(LOCK_OK == lockMgr.lock(resId, &requestIX, MODE_IX));

    // Now a conflicting lock comes
    MMAPV1LockerImpl lockerX;
    LockRequestCombo requestX(&lockerX);
    ASSERT(LOCK_WAITING == lockMgr.lock(resId, &requestX, MODE_X));

    // Now, whoever comes next should be blocked
    MMAPV1LockerImpl lockerIX1;
    LockRequestCombo requestIX1(&lockerIX1);
    ASSERT(LOCK_WAITING == lockMgr.lock(resId, &requestIX1, MODE_IX));

    // Freeing the first two locks should grant the X lock
    ASSERT(lockMgr.unlock(&requestIS));
    ASSERT(lockMgr.unlock(&requestIX));
    ASSERT_EQ(LOCK_OK, requestX.lastResult);
    ASSERT_EQ(1, requestX.numNotifies);
    ASSERT_EQ(LOCK_INVALID, requestIX1.lastResult);
    ASSERT_EQ(0, requestIX1.numNotifies);

    ASSERT(lockMgr.unlock(&requestX));
    ASSERT_EQ(LOCK_OK, requestIX1.lastResult);
    ASSERT_EQ(1, requestIX1.numNotifies);

    // Unlock all locks so we don't assert for leaked locks
    ASSERT(lockMgr.unlock(&requestIX1));
}
TEST(LockManager, CompatibleFirstImmediateGrant) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_GLOBAL, 0);

    MMAPV1LockerImpl locker1;
    LockRequestCombo request1(&locker1);

    MMAPV1LockerImpl locker2;
    LockRequestCombo request2(&locker2);
    request2.compatibleFirst = true;

    MMAPV1LockerImpl locker3;
    LockRequestCombo request3(&locker3);

    // Lock all in IS mode
    ASSERT(LOCK_OK == lockMgr.lock(resId, &request1, MODE_IS));
    ASSERT(LOCK_OK == lockMgr.lock(resId, &request2, MODE_IS));
    ASSERT(LOCK_OK == lockMgr.lock(resId, &request3, MODE_IS));

    // Now an exclusive mode comes, which would block
    MMAPV1LockerImpl lockerX;
    LockRequestCombo requestX(&lockerX);

    ASSERT(LOCK_WAITING == lockMgr.lock(resId, &requestX, MODE_X));

    // If an S comes, it should be granted, because of request2
    {
        MMAPV1LockerImpl lockerS;
        LockRequestCombo requestS(&lockerS);
        ASSERT(LOCK_OK == lockMgr.lock(resId, &requestS, MODE_S));
        ASSERT(lockMgr.unlock(&requestS));
    }

    // If request1 goes away, the policy should still be compatible-first, because of request2
    ASSERT(lockMgr.unlock(&request1));

    // If S comes again, it should be granted, because of request2 still there
    {
        MMAPV1LockerImpl lockerS;
        LockRequestCombo requestS(&lockerS);
        ASSERT(LOCK_OK == lockMgr.lock(resId, &requestS, MODE_S));
        ASSERT(lockMgr.unlock(&requestS));
    }

    // With request2 gone the policy should go back to FIFO, even though request3 is active
    ASSERT(lockMgr.unlock(&request2));

    {
        MMAPV1LockerImpl lockerS;
        LockRequestCombo requestS(&lockerS);
        ASSERT(LOCK_WAITING == lockMgr.lock(resId, &requestS, MODE_S));
        ASSERT(lockMgr.unlock(&requestS));
    }

    // Unlock request3 to keep the lock mgr not assert for leaked locks
    ASSERT(lockMgr.unlock(&request3));
    ASSERT(lockMgr.unlock(&requestX));
}
TEST(LockManager, Conflict) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

    MMAPV1LockerImpl locker1;
    MMAPV1LockerImpl locker2;

    LockRequestCombo request1(&locker1);
    LockRequestCombo request2(&locker2);

    // First request granted right away
    ASSERT(LOCK_OK == lockMgr.lock(resId, &request1, MODE_S));
    ASSERT(request1.recursiveCount == 1);
    ASSERT(request1.numNotifies == 0);

    // Second request must block
    ASSERT(LOCK_WAITING == lockMgr.lock(resId, &request2, MODE_X));
    ASSERT(request2.mode == MODE_X);
    ASSERT(request2.recursiveCount == 1);
    ASSERT(request2.numNotifies == 0);

    // Release first request
    lockMgr.unlock(&request1);
    ASSERT(request1.recursiveCount == 0);
    ASSERT(request1.numNotifies == 0);

    ASSERT(request2.mode == MODE_X);
    ASSERT(request2.recursiveCount == 1);
    ASSERT(request2.numNotifies == 1);
    ASSERT(request2.lastResult == LOCK_OK);

    // Release second acquire
    lockMgr.unlock(&request2);
    ASSERT(request2.recursiveCount == 0);

    ASSERT(request1.numNotifies == 0);
    ASSERT(request2.numNotifies == 1);
}
TEST(LockManager, EnqueueAtFront) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

    MMAPV1LockerImpl lockerX;
    LockRequestCombo requestX(&lockerX);

    ASSERT(LOCK_OK == lockMgr.lock(resId, &requestX, MODE_X));

    // The subsequent request will block
    MMAPV1LockerImpl lockerLow;
    LockRequestCombo requestLow(&lockerLow);

    ASSERT(LOCK_WAITING == lockMgr.lock(resId, &requestLow, MODE_X));

    // This is a "queue jumping request", which will go before locker 2 above
    MMAPV1LockerImpl lockerHi;
    LockRequestCombo requestHi(&lockerHi);
    requestHi.enqueueAtFront = true;

    ASSERT(LOCK_WAITING == lockMgr.lock(resId, &requestHi, MODE_X));

    // Once the X request is gone, lockerHi should be granted, because it's queue jumping
    ASSERT(lockMgr.unlock(&requestX));

    ASSERT(requestHi.lastResId == resId);
    ASSERT(requestHi.lastResult == LOCK_OK);

    // Finally lockerLow should be granted
    ASSERT(lockMgr.unlock(&requestHi));

    ASSERT(requestLow.lastResId == resId);
    ASSERT(requestLow.lastResult == LOCK_OK);

    // This avoids the lock manager asserting on leaked locks
    ASSERT(lockMgr.unlock(&requestLow));
}
TEST(LockManager, Grant) {
    LockManager lockMgr;
    const ResourceId resId(RESOURCE_COLLECTION, std::string("TestDB.collection"));

    MMAPV1LockerImpl locker;
    TrackingLockGrantNotification notify;

    LockRequest request;
    request.initNew(&locker, &notify);

    ASSERT(LOCK_OK == lockMgr.lock(resId, &request, MODE_S));
    ASSERT(request.mode == MODE_S);
    ASSERT(request.recursiveCount == 1);
    ASSERT(notify.numNotifies == 0);

    lockMgr.unlock(&request);
    ASSERT(request.recursiveCount == 0);
}