1 // =================================
  2 // Copyright (c) 2021 Seppo Laakko
  3 // Distributed under the MIT license
  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& filePathLockKind lockKind);
 26     void UnlockFile(const std::string& filePathLockKind 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::stringint> 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& filePathLockKind 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& filePathLockKind 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& filePathLockKind lockKind)
164 {
165     FileLockTable::Instance().LockFile(GetFullPath(filePath)lockKind);
166 }
167 
168 void UnlockFile(const std::string& filePathLockKind 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 } } // namespace soulng::util