file_locking_unittest.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. // Copyright 2015 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "base/command_line.h"
  5. #include "base/files/file.h"
  6. #include "base/files/file_util.h"
  7. #include "base/files/scoped_temp_dir.h"
  8. #include "base/test/multiprocess_test.h"
  9. #include "base/test/test_timeouts.h"
  10. #include "base/threading/platform_thread.h"
  11. #include "base/time/time.h"
  12. #include "build/build_config.h"
  13. #include "testing/gtest/include/gtest/gtest.h"
  14. #include "testing/multiprocess_func_list.h"
  15. using base::File;
  16. using base::FilePath;
  17. namespace {
  18. // Flag for the parent to share a temp dir to the child.
  19. const char kTempDirFlag[] = "temp-dir";
  20. // Flags to control how the process locks the file.
  21. const char kFileLockShared[] = "file-lock-shared";
  22. const char kFileLockExclusive[] = "file-lock-exclusive";
  23. // Flags to control how the subprocess unlocks the file.
  24. const char kFileUnlock[] = "file-unlock";
  25. const char kCloseUnlock[] = "close-unlock";
  26. const char kExitUnlock[] = "exit-unlock";
  27. // File to lock in temp dir.
  28. const char kLockFile[] = "lockfile";
  29. // Constants for various requests and responses, used as |signal_file| parameter
  30. // to signal/wait helpers.
  31. const char kSignalLockFileLocked[] = "locked.signal";
  32. const char kSignalLockFileClose[] = "close.signal";
  33. const char kSignalLockFileClosed[] = "closed.signal";
  34. const char kSignalLockFileUnlock[] = "unlock.signal";
  35. const char kSignalLockFileUnlocked[] = "unlocked.signal";
  36. const char kSignalExit[] = "exit.signal";
  37. // Signal an event by creating a file which didn't previously exist.
  38. bool SignalEvent(const FilePath& signal_dir, const char* signal_file) {
  39. File file(signal_dir.AppendASCII(signal_file),
  40. File::FLAG_CREATE | File::FLAG_WRITE);
  41. return file.IsValid();
  42. }
  43. // Check whether an event was signaled.
  44. bool CheckEvent(const FilePath& signal_dir, const char* signal_file) {
  45. File file(signal_dir.AppendASCII(signal_file),
  46. File::FLAG_OPEN | File::FLAG_READ);
  47. return file.IsValid();
  48. }
  49. // Busy-wait for an event to be signaled, returning false for timeout.
  50. bool WaitForEventWithTimeout(const FilePath& signal_dir,
  51. const char* signal_file,
  52. const base::TimeDelta& timeout) {
  53. const base::Time finish_by = base::Time::Now() + timeout;
  54. while (!CheckEvent(signal_dir, signal_file)) {
  55. if (base::Time::Now() > finish_by)
  56. return false;
  57. base::PlatformThread::Sleep(base::Milliseconds(10));
  58. }
  59. return true;
  60. }
  61. // Wait forever for the event to be signaled (should never return false).
  62. bool WaitForEvent(const FilePath& signal_dir, const char* signal_file) {
  63. return WaitForEventWithTimeout(signal_dir, signal_file,
  64. base::TimeDelta::Max());
  65. }
  66. // Keep these in sync so StartChild*() can refer to correct test main.
  67. #define ChildMain ChildLockUnlock
  68. #define ChildMainString "ChildLockUnlock"
  69. // Subprocess to test getting a file lock then releasing it. |kTempDirFlag|
  70. // must pass in an existing temporary directory for the lockfile and signal
  71. // files. One of the following flags must be passed to determine how to unlock
  72. // the lock file:
  73. // - |kFileUnlock| calls Unlock() to unlock.
  74. // - |kCloseUnlock| calls Close() while the lock is held.
  75. // - |kExitUnlock| exits while the lock is held.
  76. MULTIPROCESS_TEST_MAIN(ChildMain) {
  77. base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  78. const FilePath temp_path = command_line->GetSwitchValuePath(kTempDirFlag);
  79. CHECK(base::DirectoryExists(temp_path));
  80. const bool use_shared_lock = command_line->HasSwitch(kFileLockShared);
  81. const bool use_exclusive_lock = command_line->HasSwitch(kFileLockExclusive);
  82. CHECK_NE(use_shared_lock, use_exclusive_lock);
  83. const File::LockMode mode =
  84. use_exclusive_lock ? File::LockMode::kExclusive : File::LockMode::kShared;
  85. // Immediately lock the file.
  86. File file(temp_path.AppendASCII(kLockFile),
  87. File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE);
  88. CHECK(file.IsValid());
  89. CHECK_EQ(File::FILE_OK, file.Lock(mode));
  90. CHECK(SignalEvent(temp_path, kSignalLockFileLocked));
  91. if (command_line->HasSwitch(kFileUnlock)) {
  92. // Wait for signal to unlock, then unlock the file.
  93. CHECK(WaitForEvent(temp_path, kSignalLockFileUnlock));
  94. CHECK_EQ(File::FILE_OK, file.Unlock());
  95. CHECK(SignalEvent(temp_path, kSignalLockFileUnlocked));
  96. } else if (command_line->HasSwitch(kCloseUnlock)) {
  97. // Wait for the signal to close, then close the file.
  98. CHECK(WaitForEvent(temp_path, kSignalLockFileClose));
  99. file.Close();
  100. CHECK(!file.IsValid());
  101. CHECK(SignalEvent(temp_path, kSignalLockFileClosed));
  102. } else {
  103. CHECK(command_line->HasSwitch(kExitUnlock));
  104. }
  105. // Wait for signal to exit, so that unlock or close can be distinguished from
  106. // exit.
  107. CHECK(WaitForEvent(temp_path, kSignalExit));
  108. return 0;
  109. }
  110. } // namespace
  111. class FileLockingTest : public testing::Test {
  112. public:
  113. FileLockingTest() = default;
  114. FileLockingTest(const FileLockingTest&) = delete;
  115. FileLockingTest& operator=(const FileLockingTest&) = delete;
  116. protected:
  117. void SetUp() override {
  118. testing::Test::SetUp();
  119. // Setup the temp dir and the lock file.
  120. ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
  121. lock_file_.Initialize(
  122. temp_dir_.GetPath().AppendASCII(kLockFile),
  123. File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE);
  124. ASSERT_TRUE(lock_file_.IsValid());
  125. }
  126. bool SignalEvent(const char* signal_file) {
  127. return ::SignalEvent(temp_dir_.GetPath(), signal_file);
  128. }
  129. bool WaitForEventOrTimeout(const char* signal_file) {
  130. return ::WaitForEventWithTimeout(temp_dir_.GetPath(), signal_file,
  131. TestTimeouts::action_timeout());
  132. }
  133. // Start a child process set to use the specified locking mode and unlock
  134. // action, and wait for it to lock the file.
  135. void StartChildAndSignalLock(File::LockMode lock_mode,
  136. const char* unlock_action) {
  137. // Create a temporary dir and spin up a ChildLockExit subprocess against it.
  138. const FilePath temp_path = temp_dir_.GetPath();
  139. base::CommandLine child_command_line(
  140. base::GetMultiProcessTestChildBaseCommandLine());
  141. child_command_line.AppendSwitchPath(kTempDirFlag, temp_path);
  142. child_command_line.AppendSwitch(unlock_action);
  143. switch (lock_mode) {
  144. case File::LockMode::kExclusive:
  145. child_command_line.AppendSwitch(kFileLockExclusive);
  146. break;
  147. case File::LockMode::kShared:
  148. child_command_line.AppendSwitch(kFileLockShared);
  149. break;
  150. }
  151. lock_child_ = base::SpawnMultiProcessTestChild(
  152. ChildMainString, child_command_line, base::LaunchOptions());
  153. ASSERT_TRUE(lock_child_.IsValid());
  154. // Wait for the child to lock the file.
  155. ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileLocked));
  156. }
  157. // Signal the child to exit cleanly.
  158. void ExitChildCleanly() {
  159. ASSERT_TRUE(SignalEvent(kSignalExit));
  160. int rv = -1;
  161. ASSERT_TRUE(WaitForMultiprocessTestChildExit(
  162. lock_child_, TestTimeouts::action_timeout(), &rv));
  163. ASSERT_EQ(0, rv);
  164. }
  165. base::ScopedTempDir temp_dir_;
  166. base::File lock_file_;
  167. base::Process lock_child_;
  168. };
  169. // Test that locks are released by Unlock().
  170. TEST_F(FileLockingTest, LockAndUnlockExclusive) {
  171. StartChildAndSignalLock(File::LockMode::kExclusive, kFileUnlock);
  172. ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
  173. ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
  174. ASSERT_TRUE(SignalEvent(kSignalLockFileUnlock));
  175. ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileUnlocked));
  176. ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
  177. ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
  178. ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
  179. ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
  180. ExitChildCleanly();
  181. }
  182. TEST_F(FileLockingTest, LockAndUnlockShared) {
  183. StartChildAndSignalLock(File::LockMode::kShared, kFileUnlock);
  184. ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
  185. ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
  186. ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
  187. ASSERT_TRUE(SignalEvent(kSignalLockFileUnlock));
  188. ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileUnlocked));
  189. ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
  190. ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
  191. ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
  192. ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
  193. ExitChildCleanly();
  194. }
  195. // Test that locks are released on Close().
  196. TEST_F(FileLockingTest, UnlockOnCloseExclusive) {
  197. StartChildAndSignalLock(File::LockMode::kExclusive, kCloseUnlock);
  198. ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
  199. ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
  200. ASSERT_TRUE(SignalEvent(kSignalLockFileClose));
  201. ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileClosed));
  202. ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
  203. ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
  204. ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
  205. ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
  206. ExitChildCleanly();
  207. }
  208. TEST_F(FileLockingTest, UnlockOnCloseShared) {
  209. StartChildAndSignalLock(File::LockMode::kShared, kCloseUnlock);
  210. ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
  211. ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
  212. ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
  213. ASSERT_TRUE(SignalEvent(kSignalLockFileClose));
  214. ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileClosed));
  215. ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
  216. ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
  217. ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
  218. ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
  219. ExitChildCleanly();
  220. }
  221. // Test that locks are released on exit.
  222. TEST_F(FileLockingTest, UnlockOnExitExclusive) {
  223. StartChildAndSignalLock(File::LockMode::kExclusive, kExitUnlock);
  224. ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
  225. ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
  226. ExitChildCleanly();
  227. ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
  228. ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
  229. ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
  230. ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
  231. }
  232. TEST_F(FileLockingTest, UnlockOnExitShared) {
  233. StartChildAndSignalLock(File::LockMode::kShared, kExitUnlock);
  234. ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
  235. ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
  236. ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
  237. ExitChildCleanly();
  238. ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
  239. ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
  240. ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
  241. ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
  242. }
  243. // Test that killing the process releases the lock. This should cover crashing.
  244. // Flaky on Android (http://crbug.com/747518)
  245. #if BUILDFLAG(IS_ANDROID)
  246. #define MAYBE_UnlockOnTerminate DISABLED_UnlockOnTerminate
  247. #else
  248. #define MAYBE_UnlockOnTerminate UnlockOnTerminate
  249. #endif
  250. TEST_F(FileLockingTest, MAYBE_UnlockOnTerminate) {
  251. // The child will wait for an exit which never arrives.
  252. StartChildAndSignalLock(File::LockMode::kExclusive, kExitUnlock);
  253. ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
  254. ASSERT_TRUE(TerminateMultiProcessTestChild(lock_child_, 0, true));
  255. ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
  256. ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
  257. ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
  258. ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
  259. }