blob: 312d9a1e294bbe3e7ff3b29b87b559f680e08cf9 (
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
129
130
131
132
133
134
135
136
137
138
|
// 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;
static const String fileName = Path::simplify(Path::getParentDirectory(Path::getExecutablePath()) + "/test_lock_file");
SLANG_UNIT_TEST(lockFileOpenClose)
{
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);
// Cleanup.
File::remove(fileName);
SLANG_CHECK(File::exists(fileName) == false);
}
SLANG_UNIT_TEST(lockFileSync)
{
// aarch64 builds currently fail to run multi-threaded tests within the test-server.
// Tests work fine without the test-server, which is puzzling. For now we disable them.
#if SLANG_PROCESSOR_ARM_64
SLANG_IGNORE_TEST
#endif
// 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);
}
|