1
2
3
4
5
6 #include <soulng/util/FileLocking.hpp>
7 #include <soulng/util/Log.hpp>
8 #include <soulng/util/Path.hpp>
9 #include <condition_variable>
10 #include <memory>
11 #include <mutex>
12 #include <map>
13 #include <set>
14
15 namespace soulng { namespace util {
16
17 bool debugFileLocking = false;
18
19 class FileLockTable
20 {
21 public:
22 static void Init();
23 static void Done();
24 static FileLockTable& Instance() { return *instance; }
25 void LockFile(const std::string& filePath, LockKind lockKind);
26 void UnlockFile(const std::string& filePath, LockKind lockKind);
27 bool NoReaders(const std::string& filePath);
28 bool NoWriters(const std::string& filePath);
29 private:
30 static std::unique_ptr<FileLockTable> instance;
31 FileLockTable();
32 std::mutex mtx;
33 std::map<std::string, int> readMap;
34 std::set<std::string> writeLocks;
35 std::condition_variable releaseRead;
36 std::condition_variable releaseWrite;
37 bool waitingReadLock;
38 bool waitingWriteLock;
39 };
40
41 std::unique_ptr<FileLockTable> FileLockTable::instance;
42
43 FileLockTable::FileLockTable() : waitingReadLock(false), waitingWriteLock(false)
44 {
45 }
46
47 void FileLockTable::Init()
48 {
49 instance.reset(new FileLockTable());
50 }
51
52 void FileLockTable::Done()
53 {
54 instance.reset();
55 }
56
57 void FileLockTable::LockFile(const std::string& filePath, LockKind lockKind)
58 {
59 std::unique_lock<std::mutex> lock(mtx);
60 if (lockKind == LockKind::write)
61 {
62 auto itRead = readMap.find(filePath);
63 if (itRead != readMap.cend())
64 {
65 waitingReadLock = true;
66 if (debugFileLocking)
67 {
68 LogMessage(-1, "File '" + filePath + " locked for reading, number of readers is " + std::to_string(itRead->second));
69 }
70 releaseRead.wait(lock, [this filePath] { return NoReaders(filePath); });
71 if (debugFileLocking)
72 {
73 LogMessage(-1, "File '" + filePath + "': readers exited");
74 }
75 waitingReadLock = false;
76 }
77 auto itWrite = writeLocks.find(filePath);
78 if (itWrite != writeLocks.cend())
79 {
80 waitingWriteLock = true;
81 if (debugFileLocking)
82 {
83 LogMessage(-1, "File '" + filePath + " locked for writing");
84 }
85 releaseWrite.wait(lock, [this filePath] { return NoWriters(filePath); });
86 if (debugFileLocking)
87 {
88 LogMessage(-1, "File '" + filePath + ": writer exited");
89 }
90 waitingWriteLock = false;
91 }
92 }
93 else if (lockKind == LockKind::read)
94 {
95 auto itWrite = writeLocks.find(filePath);
96 if (itWrite != writeLocks.cend())
97 {
98 waitingWriteLock = true;
99 if (debugFileLocking)
100 {
101 LogMessage(-1, "File '" + filePath + " locked for writing");
102 }
103 releaseWrite.wait(lock, [this filePath] { return NoWriters(filePath); });
104 if (debugFileLocking)
105 {
106 LogMessage(-1, "File '" + filePath + ": writer exited");
107 }
108 waitingWriteLock = false;
109 }
110 }
111 if (lockKind == LockKind::write)
112 {
113 writeLocks.insert(filePath);
114 }
115 else if (lockKind == LockKind::read)
116 {
117 ++readMap[filePath];
118 }
119 }
120
121 bool FileLockTable::NoReaders(const std::string& filePath)
122 {
123 auto it = readMap.find(filePath);
124 if (it != readMap.end())
125 {
126 int numReaders = it->second;
127 return numReaders == 0;
128 }
129 return true;
130 }
131
132 bool FileLockTable::NoWriters(const std::string& filePath)
133 {
134 return writeLocks.find(filePath) == writeLocks.cend();
135 }
136
137 void FileLockTable::UnlockFile(const std::string& filePath, LockKind lockKind)
138 {
139 std::unique_lock<std::mutex> lock(mtx);
140 if (lockKind == LockKind::read)
141 {
142 int& numReaders = readMap[filePath];
143 --numReaders;
144 if (numReaders == 0)
145 {
146 readMap.erase(filePath);
147 if (waitingReadLock)
148 {
149 releaseRead.notify_all();
150 }
151 }
152 }
153 else if (lockKind == LockKind::write)
154 {
155 writeLocks.erase(filePath);
156 if (waitingWriteLock)
157 {
158 releaseWrite.notify_all();
159 }
160 }
161 }
162
163 void LockFile(const std::string& filePath, LockKind lockKind)
164 {
165 FileLockTable::Instance().LockFile(GetFullPath(filePath), lockKind);
166 }
167
168 void UnlockFile(const std::string& filePath, LockKind lockKind)
169 {
170 FileLockTable::Instance().UnlockFile(GetFullPath(filePath), lockKind);
171 }
172
173 void InitFileLocking()
174 {
175 FileLockTable::Init();
176 }
177
178 void DoneFileLocking()
179 {
180 FileLockTable::Done();
181 }
182
183 } }