summaryrefslogtreecommitdiff
path: root/tools/slang-unit-test/unit-test-lock-file.cpp
blob: 33e787a1d1757263f7dbcf154a5abfc9a70cc6a5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// unit-test-lock-file.cpp
#include "tools/unit-test/slang-unit-test.h"

#include "../../source/core/slang-io.h"

#include <atomic>
#include <future>
#include <thread>
#include <vector>

using namespace Slang;

SLANG_UNIT_TEST(lockFile)
{
    static String fileName = Path::simplify(Path::getParentDirectory(Path::getExecutablePath()) + "/test_lock_file");

    // Open/close lock file.
    {
        LockFile file;
        SLANG_CHECK(file.isOpen() == false);
        SLANG_CHECK_ABORT(file.open(fileName) == SLANG_OK);
        SLANG_CHECK(file.isOpen() == true);
        SLANG_CHECK(File::exists(fileName) == true);
        file.close();
        SLANG_CHECK(file.isOpen() == false);
    }

    // Test using multiple threads.
    {
        static std::atomic<uint32_t> lockCounter;
        static std::atomic<uint32_t> unlockCounter;

        struct LockTask
        {
            std::thread thread;
            std::promise<void> startPromise;
            std::future<void> startFuture;
            LockFile lockFile;
            SlangResult openResult = false;
            SlangResult tryLockSharedResult = false;
            SlangResult tryLockExclusiveResult = false;
            SlangResult lockResult = false;
            SlangResult unlockResult = false;
            uint32_t lockIteration = 0;
            uint32_t unlockIteration = 0;

            LockTask()
                : startFuture(startPromise.get_future())
            {
                openResult = lockFile.open(fileName);
            }

            void run()
            {
                tryLockSharedResult = lockFile.tryLock(LockFile::LockType::Shared);
                tryLockExclusiveResult = lockFile.tryLock(LockFile::LockType::Exclusive);
                startPromise.set_value();
                lockResult = lockFile.lock(LockFile::LockType::Exclusive);
                lockIteration = lockCounter.fetch_add(1);
                std::this_thread::sleep_for(std::chrono::milliseconds(1));
                unlockIteration = unlockCounter.fetch_add(1);
                unlockResult = lockFile.unlock();
            }
        };

        // Acquire lock from main thread.
        LockFile lockFile;
        SLANG_CHECK(lockFile.open(fileName) == SLANG_OK);
        SLANG_CHECK(lockFile.lock(LockFile::LockType::Exclusive) == SLANG_OK);

        // Make sure we cannot acquire the lock in non-blocking mode from a second instance.
        LockFile lockFile2;
        SLANG_CHECK(lockFile2.open(fileName) == SLANG_OK);
        SLANG_CHECK(lockFile2.tryLock(LockFile::LockType::Shared) == SLANG_E_TIME_OUT);
        SLANG_CHECK(lockFile2.tryLock(LockFile::LockType::Exclusive) == SLANG_E_TIME_OUT);

        // Start a number of threads and wait for them to start up.
        // Each thread immediately tries to acquire the lock in non-blocking mode (expected to fail).
        // Next each thread acquires the lock in blocking mode.
        std::vector<LockTask> tasks(32);
        for (auto& task : tasks)
        {
            task.thread = std::thread(&LockTask::run, &task);
            task.startFuture.wait();
        }

        // Make sure none of the threads were able to acquire the lock yet.
        std::this_thread::sleep_for(std::chrono::milliseconds(5));
        SLANG_CHECK(lockCounter == 0);

        // Release the lock from the main thread. This will allow all the other
        // threads to acquire the lock, one after the other.
        SLANG_CHECK(lockFile.unlock() == SLANG_OK);

        // Wait for all threads to finish and make sure they behaved as expected.
        std::vector<bool> lockIterationUsed(tasks.size(), false);
        std::vector<bool> unlockIterationUsed(tasks.size(), false);
        for (auto& task : tasks)
        {
            task.thread.join();

            SLANG_CHECK(task.openResult == SLANG_OK);
            SLANG_CHECK(task.tryLockSharedResult == SLANG_E_TIME_OUT);
            SLANG_CHECK(task.tryLockExclusiveResult == SLANG_E_TIME_OUT);
            SLANG_CHECK(task.lockResult == SLANG_OK);
            SLANG_CHECK(task.unlockResult == SLANG_OK);
            SLANG_CHECK(task.lockIteration < lockIterationUsed.size());
            SLANG_CHECK(task.unlockIteration < unlockIterationUsed.size());
            SLANG_CHECK(task.unlockIteration == task.lockIteration);
            SLANG_CHECK(lockIterationUsed[task.lockIteration] == false);
            SLANG_CHECK(unlockIterationUsed[task.unlockIteration] == false);
            lockIterationUsed[task.lockIteration] = true;
            unlockIterationUsed[task.unlockIteration] = true;
        }

        // Ensure all threads did manage to acquire the lock.
        SLANG_CHECK(lockCounter == tasks.size());
        SLANG_CHECK(unlockCounter == tasks.size());

        // Check that we can now acquire the lock in non-blocking mode.
        SLANG_CHECK(lockFile2.tryLock(LockFile::LockType::Exclusive) == SLANG_OK);
        SLANG_CHECK(lockFile2.unlock() == SLANG_OK);
    }

    // Cleanup.
    File::remove(fileName);
    SLANG_CHECK(File::exists(fileName) == false);
}